mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Added week start
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -24,6 +25,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
|
||||
'timezone' => ['required', 'timezone:all'],
|
||||
'week_start' => ['required', Rule::enum(Weekday::class)],
|
||||
])->validateWithBag('updateProfileInformation');
|
||||
|
||||
if (isset($input['photo'])) {
|
||||
@@ -38,6 +40,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'timezone' => $input['timezone'],
|
||||
'week_start' => $input['week_start'],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
47
app/Enums/Weekday.php
Normal file
47
app/Enums/Weekday.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
enum Weekday: string
|
||||
{
|
||||
case Monday = 'monday';
|
||||
case Tuesday = 'tuesday';
|
||||
case Wednesday = 'wednesday';
|
||||
case Thursday = 'thursday';
|
||||
case Friday = 'friday';
|
||||
case Saturday = 'saturday';
|
||||
case Sunday = 'sunday';
|
||||
|
||||
public function carbonWeekDay(): int
|
||||
{
|
||||
return match ($this) {
|
||||
Weekday::Monday => Carbon::MONDAY,
|
||||
Weekday::Tuesday => Carbon::TUESDAY,
|
||||
Weekday::Wednesday => Carbon::WEDNESDAY,
|
||||
Weekday::Thursday => Carbon::THURSDAY,
|
||||
Weekday::Friday => Carbon::FRIDAY,
|
||||
Weekday::Saturday => Carbon::SATURDAY,
|
||||
Weekday::Sunday => Carbon::SUNDAY,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function toSelectArray(): array
|
||||
{
|
||||
return [
|
||||
Weekday::Monday->value => __('enum.weekday.'.Weekday::Monday->value),
|
||||
Weekday::Tuesday->value => __('enum.weekday.'.Weekday::Tuesday->value),
|
||||
Weekday::Wednesday->value => __('enum.weekday.'.Weekday::Wednesday->value),
|
||||
Weekday::Thursday->value => __('enum.weekday.'.Weekday::Thursday->value),
|
||||
Weekday::Friday->value => __('enum.weekday.'.Weekday::Friday->value),
|
||||
Weekday::Saturday->value => __('enum.weekday.'.Weekday::Saturday->value),
|
||||
Weekday::Sunday->value => __('enum.weekday.'.Weekday::Sunday->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use Database\Factories\UserFactory;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -27,6 +28,7 @@ use Laravel\Passport\HasApiTokens;
|
||||
* @property string|null $password
|
||||
* @property string $timezone
|
||||
* @property bool $is_placeholder
|
||||
* @property Weekday $week_start
|
||||
* @property Collection<Organization> $organizations
|
||||
* @property Collection<TimeEntry> $timeEntries
|
||||
*
|
||||
@@ -79,6 +81,16 @@ class User extends Authenticatable
|
||||
'email_verified_at' => 'datetime',
|
||||
'is_admin' => 'boolean',
|
||||
'is_placeholder' => 'boolean',
|
||||
'week_start' => Weekday::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The model's default values for attributes.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $attributes = [
|
||||
'week_start' => Weekday::Monday,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ use Dedoc\Scramble\Support\Generator\SecuritySchemes\OAuthFlow;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Actions\Jetstream\DeleteUser;
|
||||
use App\Actions\Jetstream\InviteOrganizationMember;
|
||||
use App\Actions\Jetstream\RemoveOrganizationMember;
|
||||
use App\Actions\Jetstream\UpdateOrganization;
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\Organization;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use App\Service\TimezoneService;
|
||||
@@ -120,6 +121,7 @@ class JetstreamServiceProvider extends ServiceProvider
|
||||
function (Request $request, array $data) {
|
||||
return array_merge($data, [
|
||||
'timezones' => $this->app->get(TimezoneService::class)->getSelectOptions(),
|
||||
'weekdays' => Weekday::toSelectArray(),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,40 +4,92 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonTimeZone;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DashboardService
|
||||
{
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function lastDays(int $days, CarbonTimeZone $timeZone): array
|
||||
private TimezoneService $timezoneService;
|
||||
|
||||
public function __construct(TimezoneService $timezoneService)
|
||||
{
|
||||
$result = [];
|
||||
$date = Carbon::now($timeZone);
|
||||
$this->timezoneService = $timezoneService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, string>
|
||||
*/
|
||||
private function lastDays(int $days, CarbonTimeZone $timeZone): Collection
|
||||
{
|
||||
$result = new Collection();
|
||||
$date = Carbon::now($timeZone)->subDays($days);
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$result[] = $date->format('Y-m-d');
|
||||
$date = $date->subDay();
|
||||
$date->addDay();
|
||||
$result->push($date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, string>
|
||||
*/
|
||||
private function daysOfThisWeek(CarbonTimeZone $timeZone, Weekday $weekday): Collection
|
||||
{
|
||||
$result = new Collection();
|
||||
$date = Carbon::now($timeZone);
|
||||
$start = $date->startOfWeek($weekday->carbonWeekDay());
|
||||
for ($i = 0; $i < 7; $i++) {
|
||||
$result->push($start->format('Y-m-d'));
|
||||
$start->addDay();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, string> $possibleDates
|
||||
* @param Builder<TimeEntry> $builder
|
||||
* @return Builder<TimeEntry>
|
||||
*/
|
||||
private function constrainDateByPossibleDates(Builder $builder, Collection $possibleDates, CarbonTimeZone $timeZone): Builder
|
||||
{
|
||||
$value1 = Carbon::createFromFormat('Y-m-d', $possibleDates->first(), $timeZone);
|
||||
$value2 = Carbon::createFromFormat('Y-m-d', $possibleDates->last(), $timeZone);
|
||||
if ($value2 === false || $value1 === false) {
|
||||
throw new \RuntimeException('Provided date is not valid');
|
||||
}
|
||||
if ($value1->gt($value2)) {
|
||||
$last = $value1;
|
||||
$first = $value2;
|
||||
} else {
|
||||
$last = $value2;
|
||||
$first = $value1;
|
||||
}
|
||||
|
||||
return $builder->whereBetween('start', [
|
||||
$first->startOfDay()->utc(),
|
||||
$last->endOfDay()->utc(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the daily tracked hours for the user
|
||||
* First value: date
|
||||
* Second value: seconds
|
||||
*
|
||||
* @return array<int, array{0: string, 1: int}>
|
||||
* @return array<int, array{date: string, duration: int}>
|
||||
*/
|
||||
public function getDailyTrackedHours(User $user, int $days): array
|
||||
{
|
||||
$timezone = new CarbonTimeZone($user->timezone);
|
||||
$timezoneShift = $timezone->getOffset(new \DateTime('now', new \DateTimeZone('UTC')));
|
||||
$timezone = $this->timezoneService->getTimezoneFromUser($user);
|
||||
$timezoneShift = $this->timezoneService->getShiftFromUtc($timezone);
|
||||
|
||||
if ($timezoneShift > 0) {
|
||||
$dateWithTimeZone = 'start + INTERVAL \''.$timezoneShift.' second\'';
|
||||
@@ -47,60 +99,67 @@ class DashboardService
|
||||
$dateWithTimeZone = 'start';
|
||||
}
|
||||
|
||||
$resultDb = TimeEntry::query()
|
||||
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as value'))
|
||||
$possibleDays = $this->lastDays($days, $timezone);
|
||||
|
||||
$query = TimeEntry::query()
|
||||
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
|
||||
->where('user_id', '=', $user->id)
|
||||
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
|
||||
->orderBy('date')
|
||||
->get()
|
||||
->pluck('value', 'date');
|
||||
->orderBy('date');
|
||||
|
||||
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
||||
$resultDb = $query->get()
|
||||
->pluck('aggregate', 'date');
|
||||
|
||||
$result = [];
|
||||
$lastDays = $this->lastDays($days, $timezone);
|
||||
|
||||
foreach ($lastDays as $day) {
|
||||
$result[] = [$day, (int) ($resultDb->get($day) ?? 0)];
|
||||
foreach ($possibleDays as $possibleDay) {
|
||||
$result[] = [
|
||||
'date' => $possibleDay,
|
||||
'duration' => (int) ($resultDb->get($possibleDay) ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics for the current week starting at Monday / Sunday
|
||||
* Statistics for the current week starting at weekday of users preference
|
||||
*
|
||||
* @return array<int, array{date: string, duration: int}>
|
||||
*/
|
||||
public function getWeeklyHistory(User $user): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'date' => '2024-02-26',
|
||||
'duration' => 3600,
|
||||
],
|
||||
[
|
||||
'date' => '2024-02-27',
|
||||
'duration' => 2000,
|
||||
],
|
||||
[
|
||||
'date' => '2024-02-28',
|
||||
'duration' => 4000,
|
||||
],
|
||||
[
|
||||
'date' => '2024-02-29',
|
||||
'duration' => 3000,
|
||||
],
|
||||
[
|
||||
'date' => '2024-03-01',
|
||||
'duration' => 5000,
|
||||
],
|
||||
[
|
||||
'date' => '2024-03-02',
|
||||
'duration' => 3000,
|
||||
],
|
||||
[
|
||||
'date' => '2024-03-03',
|
||||
'duration' => 2000,
|
||||
],
|
||||
];
|
||||
$timezone = $this->timezoneService->getTimezoneFromUser($user);
|
||||
$timezoneShift = $this->timezoneService->getShiftFromUtc($timezone);
|
||||
if ($timezoneShift > 0) {
|
||||
$dateWithTimeZone = 'start + INTERVAL \''.$timezoneShift.' second\'';
|
||||
} elseif ($timezoneShift < 0) {
|
||||
$dateWithTimeZone = 'start - INTERVAL \''.abs($timezoneShift).' second\'';
|
||||
} else {
|
||||
$dateWithTimeZone = 'start';
|
||||
}
|
||||
$possibleDays = $this->daysOfThisWeek($timezone, $user->week_start);
|
||||
|
||||
$query = TimeEntry::query()
|
||||
->select(DB::raw('DATE('.$dateWithTimeZone.') as date, round(sum(extract(epoch from (coalesce("end", now()) - start)))) as aggregate'))
|
||||
->where('user_id', '=', $user->id)
|
||||
->groupBy(DB::raw('DATE('.$dateWithTimeZone.')'))
|
||||
->orderBy('date');
|
||||
|
||||
$query = $this->constrainDateByPossibleDates($query, $possibleDays, $timezone);
|
||||
$resultDb = $query->get()
|
||||
->pluck('aggregate', 'date');
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($possibleDays as $possibleDay) {
|
||||
$result[] = [
|
||||
'date' => $possibleDay,
|
||||
'duration' => (int) ($resultDb->get($possibleDay) ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonTimeZone;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TimezoneService
|
||||
{
|
||||
@@ -19,6 +22,20 @@ class TimezoneService
|
||||
return $tzlist;
|
||||
}
|
||||
|
||||
public function getTimezoneFromUser(User $user): CarbonTimeZone
|
||||
{
|
||||
try {
|
||||
return new CarbonTimeZone($user->timezone);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('User has a invalid timezone', [
|
||||
'user_id' => $user->getKey(),
|
||||
'timezone' => $user->timezone,
|
||||
]);
|
||||
|
||||
return new CarbonTimeZone('UTC');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
@@ -37,4 +54,11 @@ class TimezoneService
|
||||
{
|
||||
return in_array($timezone, $this->getTimezones(), true);
|
||||
}
|
||||
|
||||
public function getShiftFromUtc(CarbonTimeZone $timeZone): int
|
||||
{
|
||||
$timezoneShift = $timeZone->getOffset(new DateTime('now', new DateTimeZone('UTC')));
|
||||
|
||||
return $timezoneShift;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
@@ -33,6 +34,7 @@ class UserFactory extends Factory
|
||||
'current_team_id' => null,
|
||||
'is_placeholder' => false,
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Monday,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,15 @@ return new class extends Migration
|
||||
$table->foreignUuid('current_team_id')->nullable();
|
||||
$table->string('profile_photo_path', 2048)->nullable();
|
||||
$table->string('timezone');
|
||||
$table->enum('week_start', [
|
||||
'monday',
|
||||
'tuesday',
|
||||
'wednesday',
|
||||
'thursday',
|
||||
'friday',
|
||||
'saturday',
|
||||
'sunday',
|
||||
]);
|
||||
$table->timestamps();
|
||||
|
||||
$table->uniqueIndex('email')
|
||||
|
||||
19
lang/en/enum.php
Normal file
19
lang/en/enum.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Weekday;
|
||||
|
||||
return [
|
||||
|
||||
'weekday' => [
|
||||
Weekday::Monday->value => 'Monday',
|
||||
Weekday::Tuesday->value => 'Tuesday',
|
||||
Weekday::Wednesday->value => 'Wednesday',
|
||||
Weekday::Thursday->value => 'Thursday',
|
||||
Weekday::Friday->value => 'Friday',
|
||||
Weekday::Saturday->value => 'Saturday',
|
||||
Weekday::Sunday->value => 'Sunday',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -8,3 +8,5 @@ parameters:
|
||||
|
||||
# Level 9 is the highest level
|
||||
level: 7
|
||||
|
||||
checkOctaneCompatibility: true
|
||||
|
||||
@@ -21,6 +21,7 @@ const form = useForm({
|
||||
email: props.user.email,
|
||||
photo: null as File | null,
|
||||
timezone: props.user.timezone,
|
||||
week_start: props.user.week_start,
|
||||
});
|
||||
|
||||
const verificationLinkSent = ref<boolean | null>(null);
|
||||
@@ -211,14 +212,36 @@ const page = usePage<{
|
||||
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
|
||||
<option value="" disabled>Select a Timezone</option>
|
||||
<option
|
||||
v-for="timezone in $page.props.timezones"
|
||||
:key="timezone"
|
||||
:value="timezone">
|
||||
{{ timezone }}
|
||||
v-for="(timezoneTranslated, timezoneKey) in $page.props
|
||||
.timezones"
|
||||
:key="timezoneKey"
|
||||
:value="timezoneKey">
|
||||
{{ timezoneTranslated }}
|
||||
</option>
|
||||
</select>
|
||||
<InputError :message="form.errors.timezone" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Week start -->
|
||||
<div class="col-span-6 sm:col-span-4">
|
||||
<InputLabel for="week_start" value="Start of the week" />
|
||||
<select
|
||||
name="week_start"
|
||||
id="week_start"
|
||||
v-model="form.week_start"
|
||||
required
|
||||
class="mt-1 block w-full border-input-border bg-input-background text-white focus:border-input-border-active rounded-md shadow-sm">
|
||||
<option value="" disabled>Select a week day</option>
|
||||
<option
|
||||
v-for="(weekdayTranslated, weekdayKey) in $page.props
|
||||
.weekdays"
|
||||
:key="weekdayKey"
|
||||
:value="weekdayKey">
|
||||
{{ weekdayTranslated }}
|
||||
</option>
|
||||
</select>
|
||||
<InputError :message="form.errors.week_start" class="mt-2" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
|
||||
@@ -111,6 +111,7 @@ export interface User {
|
||||
two_factor_recovery_codes?: string | null;
|
||||
two_factor_confirmed_at: string | null;
|
||||
timezone: string;
|
||||
week_start: string;
|
||||
// mutators
|
||||
profile_photo_url: string;
|
||||
// relations
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\User;
|
||||
use App\Service\TimezoneService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -24,10 +25,15 @@ class ProfileInformationTest extends TestCase
|
||||
'name' => 'Test Name',
|
||||
'email' => 'test@example.com',
|
||||
'timezone' => app(TimezoneService::class)->getTimezones()[0],
|
||||
'week_start' => Weekday::Sunday->value,
|
||||
]);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals('Test Name', $user->fresh()->name);
|
||||
$this->assertEquals('test@example.com', $user->fresh()->email);
|
||||
$response->assertValid(errorBag: 'updateProfileInformation');
|
||||
$user = $user->fresh();
|
||||
$this->assertEquals('Test Name', $user->name);
|
||||
$this->assertEquals('test@example.com', $user->email);
|
||||
$this->assertEquals(app(TimezoneService::class)->getTimezones()[0], $user->timezone);
|
||||
$this->assertEquals(Weekday::Sunday, $user->week_start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service;
|
||||
|
||||
use App\Enums\Weekday;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\DashboardService;
|
||||
@@ -15,29 +16,115 @@ class DashboardServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected DashboardService $dashboardService;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->dashboardService = app(DashboardService::class);
|
||||
}
|
||||
|
||||
public function test_daily_tracked_hours_returns_correct_values(): void
|
||||
{
|
||||
// Arrange
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'UTC'));
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
]);
|
||||
$timeEntry = TimeEntry::factory()->forUser($user)->create([
|
||||
'start' => Carbon::create(2023, 12, 31, 0, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 31, 0, 0, 40, 'UTC'),
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$service = new DashboardService();
|
||||
$result = $service->getDailyTrackedHours($user, 5);
|
||||
$result = $this->dashboardService->getDailyTrackedHours($user, 5);
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
['2024-01-01', 0],
|
||||
['2023-12-31', 40],
|
||||
['2023-12-30', 0],
|
||||
['2023-12-29', 0],
|
||||
['2023-12-28', 0],
|
||||
[
|
||||
'date' => '2023-12-28',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2023-12-29',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2023-12-30',
|
||||
'duration' => 40,
|
||||
],
|
||||
[
|
||||
'date' => '2023-12-31',
|
||||
'duration' => 40,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-01',
|
||||
'duration' => 0,
|
||||
],
|
||||
], $result);
|
||||
}
|
||||
|
||||
public function test_weekly_history_returns_correct_values(): void
|
||||
{
|
||||
// Arrange
|
||||
// Note: Is a Monday
|
||||
$this->travelTo(Carbon::create(2024, 1, 1, 12, 0, 0, 'Europe/Vienna'));
|
||||
$user = User::factory()->create([
|
||||
'timezone' => 'Europe/Vienna',
|
||||
'week_start' => Weekday::Sunday,
|
||||
]);
|
||||
// Note: This is a Sunday
|
||||
$timeEntry1 = TimeEntry::factory()->forUser($user)->create([
|
||||
// Note: The start time shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 23, 0, 0, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 40, 'UTC'),
|
||||
]);
|
||||
// Note: This is a Saturday
|
||||
$timeEntry2 = TimeEntry::factory()->forUser($user)->create([
|
||||
// Note: The start time NOT shifts in timezone Europe/Vienna to the next day
|
||||
'start' => Carbon::create(2023, 12, 30, 22, 59, 59, 'UTC'),
|
||||
'end' => Carbon::create(2023, 12, 30, 23, 0, 39, 'UTC'),
|
||||
]);
|
||||
|
||||
// Act
|
||||
$result = $this->dashboardService->getWeeklyHistory($user);
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
[
|
||||
'date' => '2023-12-31',
|
||||
'duration' => 40,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-01',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-02',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-03',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-04',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-05',
|
||||
'duration' => 0,
|
||||
],
|
||||
[
|
||||
'date' => '2024-01-06',
|
||||
'duration' => 0,
|
||||
],
|
||||
], $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user