From 77a4d5711c3877663dbddf1fe796fb319ef7e094 Mon Sep 17 00:00:00 2001 From: Ewelina Lasowy <56546832+EwelinaLasowy@users.noreply.github.com> Date: Mon, 21 Feb 2022 13:19:59 +0100 Subject: [PATCH 1/3] #41 - email notifications (#54) * change layout * change layout * #22 - wip * wip * wip * #22 - wip * #22 - wip * #22 - wip * #22 - wip * #22 - fix * #22 - wip * #22 - added some tests * #22 - wip * #22 - wip * #22 - fix * #41 - wip * #22 - wip * #22 - wip * #22 - wip * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #41 - wip * #41 - wip * #49 - laravel 9 * #41 - wip * #41 - fix * #41 - fix * Apply suggestions from code review Co-authored-by: Jacek Sawoszczuk * #41 - cr fix * #41 - cr fix Co-authored-by: Adrian Hopek Co-authored-by: Jacek Sawoszczuk --- .env.example | 2 +- .../Providers/AppServiceProvider.php | 1 + .../Providers/EventServiceProvider.php | 18 +- app/Domain/Events/VacationRequestRejected.php | 20 ++ .../VacationRequestWaitsForAdminApproval.php | 20 ++ .../VacationRequestWaitsForTechApproval.php | 20 ++ ...endApprovedVacationRequestNotification.php | 34 +++ ...ndCancelledVacationRequestNotification.php | 34 +++ ...SendCreatedVacationRequestNotification.php | 20 ++ ...endRejectedVacationRequestNotification.php | 34 +++ ...inistrativeVacationRequestNotification.php | 32 ++ ...orTechnicalVacationRequestNotification.php | 32 ++ .../VacationRequestApprovedNotification.php | 75 +++++ .../VacationRequestCancelledNotification.php | 75 +++++ .../VacationRequestCreatedNotification.php | 72 +++++ .../VacationRequestRejectedNotification.php | 75 +++++ ...questWaitsForAdminApprovalNotification.php | 75 +++++ ...equestWaitsForTechApprovalNotification.php | 75 +++++ app/Domain/VacationRequestStateManager.php | 9 + .../Observers/VacationRequestObserver.php | 2 +- .../VacationRequestActivityResource.php | 2 +- config/mail.php | 2 +- package-lock.json | 2 +- resources/js/Shared/MainMenu.vue | 2 +- resources/lang/pl.json | 24 +- .../views/vendor/mail/html/themes/mail.css | 286 ++++++++++++++++++ tests/Feature/VacationRequestTest.php | 30 +- .../Unit/VacationRequestNotificationTest.php | 70 +++++ 28 files changed, 1116 insertions(+), 27 deletions(-) create mode 100644 app/Domain/Events/VacationRequestRejected.php create mode 100644 app/Domain/Events/VacationRequestWaitsForAdminApproval.php create mode 100644 app/Domain/Events/VacationRequestWaitsForTechApproval.php create mode 100644 app/Domain/Listeners/SendApprovedVacationRequestNotification.php create mode 100644 app/Domain/Listeners/SendCancelledVacationRequestNotification.php create mode 100644 app/Domain/Listeners/SendCreatedVacationRequestNotification.php create mode 100644 app/Domain/Listeners/SendRejectedVacationRequestNotification.php create mode 100644 app/Domain/Listeners/SendWaitedForAdministrativeVacationRequestNotification.php create mode 100644 app/Domain/Listeners/SendWaitedForTechnicalVacationRequestNotification.php create mode 100644 app/Domain/Notifications/VacationRequestApprovedNotification.php create mode 100644 app/Domain/Notifications/VacationRequestCancelledNotification.php create mode 100644 app/Domain/Notifications/VacationRequestCreatedNotification.php create mode 100644 app/Domain/Notifications/VacationRequestRejectedNotification.php create mode 100644 app/Domain/Notifications/VacationRequestWaitsForAdminApprovalNotification.php create mode 100644 app/Domain/Notifications/VacationRequestWaitsForTechApprovalNotification.php create mode 100644 resources/views/vendor/mail/html/themes/mail.css create mode 100644 tests/Unit/VacationRequestNotificationTest.php diff --git a/.env.example b/.env.example index a3ccf90..661c459 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -APP_NAME="Toby HR applicaiton" +APP_NAME="Toby HR application" APP_ENV=local APP_KEY= APP_DEBUG=true diff --git a/app/Architecture/Providers/AppServiceProvider.php b/app/Architecture/Providers/AppServiceProvider.php index d7e58c9..b1dc896 100644 --- a/app/Architecture/Providers/AppServiceProvider.php +++ b/app/Architecture/Providers/AppServiceProvider.php @@ -15,6 +15,7 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y")); + Carbon::macro("toDisplayDate", fn() => $this->translatedFormat("d.m.Y")); $selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class); diff --git a/app/Architecture/Providers/EventServiceProvider.php b/app/Architecture/Providers/EventServiceProvider.php index 14bd1e2..3107b26 100644 --- a/app/Architecture/Providers/EventServiceProvider.php +++ b/app/Architecture/Providers/EventServiceProvider.php @@ -10,22 +10,34 @@ use Toby\Domain\Events\VacationRequestAcceptedByTechnical; use Toby\Domain\Events\VacationRequestApproved; use Toby\Domain\Events\VacationRequestCancelled; use Toby\Domain\Events\VacationRequestCreated; +use Toby\Domain\Events\VacationRequestRejected; use Toby\Domain\Events\VacationRequestStateChanged; +use Toby\Domain\Events\VacationRequestWaitsForAdminApproval; +use Toby\Domain\Events\VacationRequestWaitsForTechApproval; use Toby\Domain\Listeners\CreateVacationRequestActivity; use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest; use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest; use Toby\Domain\Listeners\HandleApprovedVacationRequest; use Toby\Domain\Listeners\HandleCancelledVacationRequest; use Toby\Domain\Listeners\HandleCreatedVacationRequest; +use Toby\Domain\Listeners\SendApprovedVacationRequestNotification; +use Toby\Domain\Listeners\SendCancelledVacationRequestNotification; +use Toby\Domain\Listeners\SendCreatedVacationRequestNotification; +use Toby\Domain\Listeners\SendRejectedVacationRequestNotification; +use Toby\Domain\Listeners\SendWaitedForAdministrativeVacationRequestNotification; +use Toby\Domain\Listeners\SendWaitedForTechnicalVacationRequestNotification; class EventServiceProvider extends ServiceProvider { protected $listen = [ VacationRequestStateChanged::class => [CreateVacationRequestActivity::class], - VacationRequestCreated::class => [HandleCreatedVacationRequest::class], + VacationRequestCreated::class => [HandleCreatedVacationRequest::class, SendCreatedVacationRequestNotification::class], VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class], VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class], - VacationRequestApproved::class => [HandleApprovedVacationRequest::class], - VacationRequestCancelled::class => [HandleCancelledVacationRequest::class], + VacationRequestApproved::class => [HandleApprovedVacationRequest::class, SendApprovedVacationRequestNotification::class], + VacationRequestRejected::class => [SendRejectedVacationRequestNotification::class], + VacationRequestCancelled::class => [HandleCancelledVacationRequest::class, SendCancelledVacationRequestNotification::class], + VacationRequestWaitsForTechApproval::class => [SendWaitedForTechnicalVacationRequestNotification::class], + VacationRequestWaitsForAdminApproval::class => [SendWaitedForAdministrativeVacationRequestNotification::class], ]; } diff --git a/app/Domain/Events/VacationRequestRejected.php b/app/Domain/Events/VacationRequestRejected.php new file mode 100644 index 0000000..5378736 --- /dev/null +++ b/app/Domain/Events/VacationRequestRejected.php @@ -0,0 +1,20 @@ +getUsersForNotifications() as $user) { + $user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $user)); + } + + $event->vacationRequest->user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $event->vacationRequest->user)); + } + + protected function getUsersForNotifications(): Collection + { + return User::query() + ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) + ->get(); + } +} diff --git a/app/Domain/Listeners/SendCancelledVacationRequestNotification.php b/app/Domain/Listeners/SendCancelledVacationRequestNotification.php new file mode 100644 index 0000000..ec64eaf --- /dev/null +++ b/app/Domain/Listeners/SendCancelledVacationRequestNotification.php @@ -0,0 +1,34 @@ +getUsersForNotifications() as $user) { + $user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $user)); + } + + $event->vacationRequest->user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $event->vacationRequest->user)); + } + + protected function getUsersForNotifications(): Collection + { + return User::query() + ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) + ->get(); + } +} diff --git a/app/Domain/Listeners/SendCreatedVacationRequestNotification.php b/app/Domain/Listeners/SendCreatedVacationRequestNotification.php new file mode 100644 index 0000000..5aece51 --- /dev/null +++ b/app/Domain/Listeners/SendCreatedVacationRequestNotification.php @@ -0,0 +1,20 @@ +vacationRequest->user->notify(new VacationRequestCreatedNotification($event->vacationRequest)); + } +} diff --git a/app/Domain/Listeners/SendRejectedVacationRequestNotification.php b/app/Domain/Listeners/SendRejectedVacationRequestNotification.php new file mode 100644 index 0000000..3835d34 --- /dev/null +++ b/app/Domain/Listeners/SendRejectedVacationRequestNotification.php @@ -0,0 +1,34 @@ +getUsersForNotifications() as $user) { + $user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $user)); + } + + $event->vacationRequest->user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $event->vacationRequest->user)); + } + + protected function getUsersForNotifications(): Collection + { + return User::query() + ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) + ->get(); + } +} diff --git a/app/Domain/Listeners/SendWaitedForAdministrativeVacationRequestNotification.php b/app/Domain/Listeners/SendWaitedForAdministrativeVacationRequestNotification.php new file mode 100644 index 0000000..0637521 --- /dev/null +++ b/app/Domain/Listeners/SendWaitedForAdministrativeVacationRequestNotification.php @@ -0,0 +1,32 @@ +getUsersForNotifications() as $user) { + $user->notify(new VacationRequestWaitsForAdminApprovalNotification($event->vacationRequest, $user)); + } + } + + protected function getUsersForNotifications(): Collection + { + return User::query() + ->where("role", [Role::AdministrativeApprover]) + ->get(); + } +} diff --git a/app/Domain/Listeners/SendWaitedForTechnicalVacationRequestNotification.php b/app/Domain/Listeners/SendWaitedForTechnicalVacationRequestNotification.php new file mode 100644 index 0000000..3a0ef75 --- /dev/null +++ b/app/Domain/Listeners/SendWaitedForTechnicalVacationRequestNotification.php @@ -0,0 +1,32 @@ +getUsersForNotifications() as $user) { + $user->notify(new VacationRequestWaitsForTechApprovalNotification($event->vacationRequest, $user)); + } + } + + protected function getUsersForNotifications(): Collection + { + return User::query() + ->where("role", [Role::TechnicalApprover]) + ->get(); + } +} diff --git a/app/Domain/Notifications/VacationRequestApprovedNotification.php b/app/Domain/Notifications/VacationRequestApprovedNotification.php new file mode 100644 index 0000000..0466062 --- /dev/null +++ b/app/Domain/Notifications/VacationRequestApprovedNotification.php @@ -0,0 +1,75 @@ + $this->vacationRequest, + ], + ); + + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->user->first_name; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + $requester = $this->vacationRequest->user->fullName; + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title has been approved", [ + "title" => $title, + ])) + ->line(__("The vacation request :title for user :requester has been approved.", [ + "title" => $title, + "requester" => $requester, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/Notifications/VacationRequestCancelledNotification.php b/app/Domain/Notifications/VacationRequestCancelledNotification.php new file mode 100644 index 0000000..2b19940 --- /dev/null +++ b/app/Domain/Notifications/VacationRequestCancelledNotification.php @@ -0,0 +1,75 @@ + $this->vacationRequest, + ], + ); + + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->user->first_name; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + $requester = $this->vacationRequest->user->fullName; + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title has been cancelled", [ + "title" => $title, + ])) + ->line(__("The vacation request :title for user :requester has been cancelled.", [ + "title" => $title, + "requester" => $requester, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/Notifications/VacationRequestCreatedNotification.php b/app/Domain/Notifications/VacationRequestCreatedNotification.php new file mode 100644 index 0000000..75c9549 --- /dev/null +++ b/app/Domain/Notifications/VacationRequestCreatedNotification.php @@ -0,0 +1,72 @@ + $this->vacationRequest, + ], + ); + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->vacationRequest->user->first_name; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + $appName = config("app.name"); + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title has been created", [ + "title" => $title, + ])) + ->line(__("The vacation request :title has been created correctly in the :appName.", [ + "title" => $title, + "appName" => $appName, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/Notifications/VacationRequestRejectedNotification.php b/app/Domain/Notifications/VacationRequestRejectedNotification.php new file mode 100644 index 0000000..98775cd --- /dev/null +++ b/app/Domain/Notifications/VacationRequestRejectedNotification.php @@ -0,0 +1,75 @@ + $this->vacationRequest, + ], + ); + + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->user->first_name; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + $requester = $this->vacationRequest->user->fullName; + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title has been rejected", [ + "title" => $title, + ])) + ->line(__("The vacation request :title for user :requester has been rejected.", [ + "title" => $title, + "requester" => $requester, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/Notifications/VacationRequestWaitsForAdminApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForAdminApprovalNotification.php new file mode 100644 index 0000000..2dc718c --- /dev/null +++ b/app/Domain/Notifications/VacationRequestWaitsForAdminApprovalNotification.php @@ -0,0 +1,75 @@ + $this->vacationRequest, + ], + ); + + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->user->first_name; + $requester = $this->vacationRequest->user->fullName; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title is waiting for your approval", [ + "title" => $title, + ])) + ->line(__("The vacation request :title from user: :requester is waiting for your approval.", [ + "title" => $title, + "requester" => $requester, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/Notifications/VacationRequestWaitsForTechApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForTechApprovalNotification.php new file mode 100644 index 0000000..703cbee --- /dev/null +++ b/app/Domain/Notifications/VacationRequestWaitsForTechApprovalNotification.php @@ -0,0 +1,75 @@ + $this->vacationRequest, + ], + ); + + return $this->buildMailMessage($url); + } + + protected function buildMailMessage(string $url): MailMessage + { + $user = $this->user->first_name; + $requester = $this->vacationRequest->user->fullName; + $title = $this->vacationRequest->name; + $type = $this->vacationRequest->type->label(); + $from = $this->vacationRequest->from->toDisplayDate(); + $to = $this->vacationRequest->to->toDisplayDate(); + $days = $this->vacationRequest->vacations()->count(); + + return (new MailMessage()) + ->greeting(__("Hi :user!", [ + "user" => $user, + ])) + ->subject(__("Vacation request :title is waiting for your approval", [ + "title" => $title, + ])) + ->line(__("The vacation request :title from user: :requester is waiting for your approval.", [ + "title" => $title, + "requester" => $requester, + ])) + ->line(__("Vacation type: :type", [ + "type" => $type, + ])) + ->line(__("From :from to :to (number of days: :days)", [ + "from" => $from, + "to" => $to, + "days" => $days, + ])) + ->action(__("Click here for details"), $url); + } +} diff --git a/app/Domain/VacationRequestStateManager.php b/app/Domain/VacationRequestStateManager.php index c3e3e2d..4256112 100644 --- a/app/Domain/VacationRequestStateManager.php +++ b/app/Domain/VacationRequestStateManager.php @@ -12,6 +12,9 @@ use Toby\Domain\Events\VacationRequestAcceptedByTechnical; use Toby\Domain\Events\VacationRequestApproved; use Toby\Domain\Events\VacationRequestCancelled; use Toby\Domain\Events\VacationRequestCreated; +use Toby\Domain\Events\VacationRequestRejected; +use Toby\Domain\Events\VacationRequestWaitsForAdminApproval; +use Toby\Domain\Events\VacationRequestWaitsForTechApproval; use Toby\Eloquent\Models\VacationRequest; class VacationRequestStateManager @@ -39,6 +42,8 @@ class VacationRequestStateManager public function reject(VacationRequest $vacationRequest): void { $this->changeState($vacationRequest, VacationRequestState::Rejected); + + $this->dispatcher->dispatch(new VacationRequestRejected($vacationRequest)); } public function cancel(VacationRequest $vacationRequest): void @@ -65,11 +70,15 @@ class VacationRequestStateManager public function waitForTechnical(VacationRequest $vacationRequest): void { $this->changeState($vacationRequest, VacationRequestState::WaitingForTechnical); + + $this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest)); } public function waitForAdministrative(VacationRequest $vacationRequest): void { $this->changeState($vacationRequest, VacationRequestState::WaitingForAdministrative); + + $this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest)); } protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void diff --git a/app/Eloquent/Observers/VacationRequestObserver.php b/app/Eloquent/Observers/VacationRequestObserver.php index eec46bc..ecbc5b1 100644 --- a/app/Eloquent/Observers/VacationRequestObserver.php +++ b/app/Eloquent/Observers/VacationRequestObserver.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Toby\Eloquent\Observers; use Illuminate\Contracts\Auth\Factory as Auth; -use Illuminate\Events\Dispatcher; +use Illuminate\Contracts\Events\Dispatcher; use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Events\VacationRequestStateChanged; use Toby\Eloquent\Models\User; diff --git a/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php b/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php index c513785..e751f89 100644 --- a/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php +++ b/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php @@ -13,7 +13,7 @@ class VacationRequestActivityResource extends JsonResource public function toArray($request): array { return [ - "date" => $this->created_at->format("d.m.Y"), + "date" => $this->created_at->toDisplayDate(), "time" => $this->created_at->format("H:i"), "user" => $this->user ? $this->user->fullName : __("System"), "state" => $this->to, diff --git a/config/mail.php b/config/mail.php index 6461e38..0bbffd9 100644 --- a/config/mail.php +++ b/config/mail.php @@ -35,7 +35,7 @@ return [ "name" => env("MAIL_FROM_NAME", "Example"), ], "markdown" => [ - "theme" => "default", + "theme" => "mail", "paths" => [ resource_path("views/vendor/mail"), ], diff --git a/package-lock.json b/package-lock.json index 958999c..1d8d8f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "toby", + "name": "application", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/resources/js/Shared/MainMenu.vue b/resources/js/Shared/MainMenu.vue index 5318860..cb75cdd 100644 --- a/resources/js/Shared/MainMenu.vue +++ b/resources/js/Shared/MainMenu.vue @@ -390,4 +390,4 @@ export default { } }, } - \ No newline at end of file + diff --git a/resources/lang/pl.json b/resources/lang/pl.json index d512566..2366fe2 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -42,7 +42,27 @@ "Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.", "Vacation request has been created.": "Wniosek urlopowy został utworzony.", "Vacation request has been accepted.": "Wniosek urlopowy został zaakceptowany.", + "Vacation request has been approved.": "Wniosek urlopowy został zatwierdzony.", "Vacation request has been rejected.": "Wniosek urlopowy został odrzucony.", - "Vacation request has been cancelled.": "Wniosek urlopowy został anulowany." - + "Vacation request has been cancelled.": "Wniosek urlopowy został anulowany.", + "Hi :user!": "Cześć :user!", + "The vacation request :title has changed state to :state.": "Wniosek urlopowy :title zmienił status na :state.", + "Vacation request :title": "Wniosek urlopowy :title", + "Regards": "Z poważaniem", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Jeżeli masz problemy z kliknięciem przycisku \":actionText\", skopiuj i wklej poniższy adres w pasek przeglądarki:", + "All rights reserved.": "Wszelkie prawa zastrzeżone", + "Show vacation request": "Pokaż wniosek", + "Vacation request :title has been created" : "Wniosek :title został utworzony", + "The vacation request :title has been created correctly in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title.", + "Vacation type: :type": "Rodzaj wniosku: :type", + "From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)", + "Click here for details": "Kliknij, aby zobaczyć szczegóły", + "Vacation request :title is waiting for your approval": "Wniosek urlopowy :title czeka na zaakceptowanie", + "The vacation request :title from user: :requester is waiting for your approval.": "Wniosek urlopowy :title od użytkownika :requester czeka na Twoją akceptację.", + "Vacation request :title has been approved": "Wniosek urlopowy :title został zatwierdzony", + "The vacation request :title for user :requester has been approved.": "Wniosek urlopowy :title od użytkownika :requester został zatwierdzony.", + "Vacation request :title has been cancelled": "Wniosek urlopowy :title został anulowany", + "The vacation request :title for user :requester has been cancelled.": "Wniosek urlopowy :title od użytkownika :requester został anulowany.", + "Vacation request :title has been rejected": "Wniosek urlopowy :title został odrzucony", + "The vacation request :title for user :requester has been rejected.": "Wniosek urlopowy :title od użytkownika :requester został odrzucony." } diff --git a/resources/views/vendor/mail/html/themes/mail.css b/resources/views/vendor/mail/html/themes/mail.css new file mode 100644 index 0000000..d2f67d3 --- /dev/null +++ b/resources/views/vendor/mail/html/themes/mail.css @@ -0,0 +1,286 @@ +/* Base */ + +body, body *:not(html):not(style):not(br):not(tr):not(code) { + font-family: Avenir, Helvetica, sans-serif; + box-sizing: border-box; +} + +body { + background-color: #f5f8fa; + color: #74787e; + height: 100%; + hyphens: auto; + line-height: 1.4; + margin: 0; + -moz-hyphens: auto; + -ms-word-break: break-all; + width: 100% !important; + -webkit-hyphens: auto; + -webkit-text-size-adjust: none; + word-break: break-all; + word-break: break-word; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #3c5f97; +} + +a img { + border: none; +} + +/* Typography */ + +h1 { + color: #2F3133; + font-size: 19px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h2 { + color: #2F3133; + font-size: 16px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h3 { + color: #2F3133; + font-size: 14px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +p { + color: #74787e; + font-size: 16px; + line-height: 1.5em; + margin-top: 0; + text-align: left; +} + +p.sub { + font-size: 12px; +} + +img { + max-width: 100%; +} + +/* Layout */ + +.wrapper { + background-color: #f5f8fa; + margin: 0; + padding: 0; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +.content { + margin: 0; + padding: 0; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: #3c5f97; + font-size: 19px; + font-weight: bold; + text-decoration: none; + text-shadow: 0 1px 0 #ffffff; +} + +/* Body */ + +.body { + background-color: #ffffff; + border-bottom: 1px solid #edeff2; + border-top: 1px solid #edeff2; + margin: 0; + padding: 0; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +.inner-body { + background-color: #ffffff; + margin: 0 auto; + padding: 0; + width: 570px; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; +} + +/* Subcopy */ + +.subcopy { + border-top: 1px solid #edeff2; + margin-top: 25px; + padding-top: 25px; +} + +.subcopy p { + font-size: 12px; +} + +/* Footer */ + +.footer { + margin: 0 auto; + padding: 0; + text-align: center; + width: 570px; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; +} + +.footer p { + color: #aeaeae; + font-size: 12px; + text-align: center; +} + +/* Tables */ + +.table table { + margin: 30px auto; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +.table th { + color: #74787e; + border-bottom: 1px solid #edeff2; + padding-bottom: 8px; +} + +.table td { + color: #74787e; + font-size: 15px; + line-height: 18px; + padding: 10px 0; +} + +.content-cell { + padding: 35px; +} + +/* Buttons */ + +.action { + margin: 30px auto; + padding: 0; + text-align: center; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +.button { + border-radius: 3px; + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); + color: #ffffff; + display: inline-block; + text-decoration: none; + -webkit-text-size-adjust: none; +} + +.button-blue, .button-primary { + background-color: #3c5f97; + border-top: 10px solid #3c5f97; + border-right: 18px solid #3c5f97; + border-bottom: 10px solid #3c5f97; + border-left: 18px solid #3c5f97; +} + +.button-green, .button-success { + background-color: #22c55e; + border-top: 10px solid #22c55e; + border-right: 18px solid #22c55e; + border-bottom: 10px solid #22c55e; + border-left: 18px solid #22c55e; +} + +.button-red, .button-error { + background-color: #ef4444; + border-top: 10px solid #ef4444; + border-right: 18px solid #ef4444; + border-bottom: 10px solid #ef4444; + border-left: 18px solid #ef4444; +} + +/* Panels */ + +.panel { + margin: 0 0 21px; +} + +.panel-content { + background-color: #edeff2; + padding: 16px; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + margin-bottom: 0; + padding-bottom: 0; +} + +/* Promotions */ + +.promotion { + background-color: #FFFFFF; + border: 2px dashed #9BA2AB; + margin: 0; + margin-bottom: 25px; + margin-top: 25px; + padding: 24px; + width: 100%; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; +} + +.promotion h1 { + text-align: center; +} + +.promotion p { + font-size: 15px; + text-align: center; +} diff --git a/tests/Feature/VacationRequestTest.php b/tests/Feature/VacationRequestTest.php index 0841d1e..95c9c79 100644 --- a/tests/Feature/VacationRequestTest.php +++ b/tests/Feature/VacationRequestTest.php @@ -6,17 +6,19 @@ namespace Tests\Feature; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Event; use Inertia\Testing\AssertableInertia as Assert; use Tests\FeatureTestCase; use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Enums\VacationType; +use Toby\Domain\Events\VacationRequestAcceptedByAdministrative; +use Toby\Domain\Events\VacationRequestAcceptedByTechnical; +use Toby\Domain\Events\VacationRequestRejected; use Toby\Domain\PolishHolidaysRetriever; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\YearPeriod; -use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar; class VacationRequestTest extends FeatureTestCase { @@ -28,8 +30,6 @@ class VacationRequestTest extends FeatureTestCase { parent::setUp(); - Bus::fake(); - $this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class); } @@ -90,6 +90,8 @@ class VacationRequestTest extends FeatureTestCase public function testTechnicalApproverCanApproveVacationRequest(): void { + Event::fake(VacationRequestAcceptedByTechnical::class); + $user = User::factory()->createQuietly(); $technicalApprover = User::factory()->createQuietly(); $currentYearPeriod = YearPeriod::current(); @@ -106,13 +108,13 @@ class VacationRequestTest extends FeatureTestCase ->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical") ->assertSessionHasNoErrors(); - $this->assertDatabaseHas("vacation_requests", [ - "state" => VacationRequestState::WaitingForAdministrative, - ]); + Event::assertDispatched(VacationRequestAcceptedByTechnical::class); } public function testAdministrativeApproverCanApproveVacationRequest(): void { + Event::fake(VacationRequestAcceptedByAdministrative::class); + $user = User::factory()->createQuietly(); $administrativeApprover = User::factory()->createQuietly(); @@ -129,20 +131,18 @@ class VacationRequestTest extends FeatureTestCase ->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative") ->assertSessionHasNoErrors(); - $this->assertDatabaseHas("vacation_requests", [ - "state" => VacationRequestState::Approved, - ]); - - Bus::assertDispatched(SendVacationRequestDaysToGoogleCalendar::class); + Event::assertDispatched(VacationRequestAcceptedByAdministrative::class); } public function testTechnicalApproverCanRejectVacationRequest(): void { + Event::fake(VacationRequestRejected::class); + $user = User::factory()->createQuietly(); $technicalApprover = User::factory()->createQuietly(); $currentYearPeriod = YearPeriod::current(); - $vacationLimit = VacationLimit::factory([ + VacationLimit::factory([ "days" => 20, ]) ->for($user) @@ -161,9 +161,7 @@ class VacationRequestTest extends FeatureTestCase ->post("/vacation-requests/{$vacationRequest->id}/reject") ->assertSessionHasNoErrors(); - $this->assertDatabaseHas("vacation_requests", [ - "state" => VacationRequestState::Rejected, - ]); + Event::assertDispatched(VacationRequestRejected::class); } public function testUserCannotCreateVacationRequestIfHeExceedsHisVacationLimit(): void diff --git a/tests/Unit/VacationRequestNotificationTest.php b/tests/Unit/VacationRequestNotificationTest.php new file mode 100644 index 0000000..b6e693f --- /dev/null +++ b/tests/Unit/VacationRequestNotificationTest.php @@ -0,0 +1,70 @@ +stateManager = $this->app->make(VacationRequestStateManager::class); + + $this->createCurrentYearPeriod(); + } + + public function testAfterChangingVacationRequestStateNotificationAreSentToUsers(): void + { + Notification::fake(); + + $user = User::factory([ + "role" => Role::Employee, + ])->createQuietly(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->createQuietly(); + $administrativeApprover = User::factory([ + "role" => Role::AdministrativeApprover, + ])->createQuietly(); + + $currentYearPeriod = YearPeriod::current(); + + /** @var VacationRequest $vacationRequest */ + $vacationRequest = VacationRequest::factory([ + "type" => VacationType::Vacation->value, + "state" => VacationRequestState::Created, + "from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(), + "to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(), + "comment" => "Comment for the vacation request.", + ]) + ->for($user) + ->for($currentYearPeriod) + ->create(); + + $this->stateManager->waitForTechnical($vacationRequest); + + Notification::assertSentTo($technicalApprover, VacationRequestWaitsForTechApprovalNotification::class); + Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForTechApprovalNotification::class); + } +} From 39b464388c323c99501e36d670a05aa58fed0f49 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Mon, 21 Feb 2022 16:09:45 +0100 Subject: [PATCH 2/3] #39 - generate timesheet (#56) * #39 - wip * #39 - fix * #39 - wip * #39 - wip * #39 - wip * Update app/Domain/Enums/Month.php Co-authored-by: Marcin Tracz * #39 - cr fixes Co-authored-by: EwelinaLasowy Co-authored-by: Marcin Tracz --- app/Domain/CalendarGenerator.php | 28 +- app/Domain/Enums/Month.php | 53 ++ app/Domain/TimesheetExport.php | 37 + app/Domain/TimesheetPerUserSheet.php | 222 ++++++ .../Http/Controllers/TimesheetController.php | 35 + .../VacationCalendarController.php | 14 +- composer.json | 1 + composer.lock | 696 +++++++++++++++--- config/excel.php | 104 +++ resources/js/Pages/Calendar.vue | 27 +- resources/lang/pl.json | 6 + routes/web.php | 5 +- 12 files changed, 1085 insertions(+), 143 deletions(-) create mode 100644 app/Domain/Enums/Month.php create mode 100644 app/Domain/TimesheetExport.php create mode 100644 app/Domain/TimesheetPerUserSheet.php create mode 100644 app/Infrastructure/Http/Controllers/TimesheetController.php create mode 100644 config/excel.php diff --git a/app/Domain/CalendarGenerator.php b/app/Domain/CalendarGenerator.php index f1d72f2..052e10c 100644 --- a/app/Domain/CalendarGenerator.php +++ b/app/Domain/CalendarGenerator.php @@ -4,9 +4,8 @@ declare(strict_types=1); namespace Toby\Domain; -use Carbon\CarbonImmutable; -use Carbon\CarbonInterface; use Carbon\CarbonPeriod; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Toby\Domain\Enums\VacationRequestState; use Toby\Eloquent\Helpers\YearPeriodRetriever; @@ -20,33 +19,16 @@ class CalendarGenerator ) { } - public function generate(YearPeriod $yearPeriod, string $month): array + public function generate(Carbon $month): array { - $date = CarbonImmutable::create($yearPeriod->year, $this->monthNameToNumber($month)); - $period = CarbonPeriod::create($date->startOfMonth(), $date->endOfMonth()); + $period = CarbonPeriod::create($month->copy()->startOfMonth(), $month->copy()->endOfMonth()); + $yearPeriod = YearPeriod::findByYear($month->year); + $holidays = $yearPeriod->holidays()->pluck("date"); return $this->generateCalendar($period, $holidays); } - protected function monthNameToNumber($name): int - { - return match ($name) { - default => CarbonInterface::JANUARY, - "february" => CarbonInterface::FEBRUARY, - "march" => CarbonInterface::MARCH, - "april" => CarbonInterface::APRIL, - "may" => CarbonInterface::MAY, - "june" => CarbonInterface::JUNE, - "july" => CarbonInterface::JULY, - "august" => CarbonInterface::AUGUST, - "september" => CarbonInterface::SEPTEMBER, - "october" => CarbonInterface::OCTOBER, - "november" => CarbonInterface::NOVEMBER, - "december" => CarbonInterface::DECEMBER, - }; - } - protected function generateCalendar(CarbonPeriod $period, Collection $holidays): array { $calendar = []; diff --git a/app/Domain/Enums/Month.php b/app/Domain/Enums/Month.php new file mode 100644 index 0000000..a6d2921 --- /dev/null +++ b/app/Domain/Enums/Month.php @@ -0,0 +1,53 @@ + CarbonInterface::JANUARY, + self::February => CarbonInterface::FEBRUARY, + self::March => CarbonInterface::MARCH, + self::April => CarbonInterface::APRIL, + self::May => CarbonInterface::MAY, + self::June => CarbonInterface::JUNE, + self::July => CarbonInterface::JULY, + self::August => CarbonInterface::AUGUST, + self::September => CarbonInterface::SEPTEMBER, + self::October => CarbonInterface::OCTOBER, + self::November => CarbonInterface::NOVEMBER, + self::December => CarbonInterface::DECEMBER, + }; + } + + public static function current(): Month + { + return Month::from(Str::lower(Carbon::now()->englishMonth)); + } + + public static function fromNameOrCurrent(string $name): Month + { + return Month::tryFrom($name) ?? Month::current(); + } +} diff --git a/app/Domain/TimesheetExport.php b/app/Domain/TimesheetExport.php new file mode 100644 index 0000000..f0ed480 --- /dev/null +++ b/app/Domain/TimesheetExport.php @@ -0,0 +1,37 @@ +users + ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month)) + ->toArray(); + } + + public function forUsers(Collection $users): static + { + $this->users = $users; + + return $this; + } + + public function forMonth(Carbon $month): static + { + $this->month = $month; + + return $this; + } +} diff --git a/app/Domain/TimesheetPerUserSheet.php b/app/Domain/TimesheetPerUserSheet.php new file mode 100644 index 0000000..7563473 --- /dev/null +++ b/app/Domain/TimesheetPerUserSheet.php @@ -0,0 +1,222 @@ +user->fullName; + } + + public function headings(): array + { + $types = VacationType::cases(); + + $headings = [ + __("Date"), + __("Day of week"), + __("Start date"), + __("End date"), + __("Worked hours"), + ]; + + foreach ($types as $type) { + $headings[] = $type->label(); + } + + return $headings; + } + + public function generator(): Generator + { + $period = CarbonPeriod::create($this->month->copy()->startOfMonth(), $this->month->copy()->endOfMonth()); + $vacations = $this->getVacationsForPeriod($this->user, $period); + $holidays = $this->getHolidaysForPeriod($period); + + foreach ($period as $day) { + $vacationsForDay = $vacations->get($day->toDateString(), new Collection()); + $workedThisDay = $this->checkIfWorkedThisDay($day, $holidays, $vacationsForDay); + + $row = [ + Date::dateTimeToExcel($day), + $day->translatedFormat("l"), + $workedThisDay ? $this->toExcelTime(Carbon::createFromTime(static::START_HOUR)) : null, + $workedThisDay ? $this->toExcelTime(Carbon::createFromTime(static::END_HOUR)) : null, + $workedThisDay ? static::HOURS_PER_DAY : null, + ]; + + foreach (VacationType::cases() as $type) { + $row[] = $vacationsForDay->has($type->value) ? static::HOURS_PER_DAY : null; + } + + yield $row; + } + } + + public function styles(Worksheet $sheet): void + { + $lastRow = $sheet->getHighestRow(); + $lastColumn = $sheet->getHighestColumn(); + + $sheet->getStyle("A1:{$lastColumn}1") + ->getFont()->setBold(true); + + $sheet->getStyle("A1:{$lastColumn}1") + ->getAlignment() + ->setVertical(Alignment::VERTICAL_CENTER); + + $sheet->getStyle("A1:{$lastColumn}1") + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB("D9D9D9"); + + $sheet->getStyle("C1:{$lastColumn}{$lastRow}") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER); + + $sheet->getStyle("A2:A{$lastRow}") + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + + $sheet->getStyle("C1:D{$lastRow}") + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); + + $sheet->getStyle("A2:A{$lastRow}") + ->getFont() + ->setBold(true); + + for ($i = 2; $i < $lastRow; $i++) { + $date = Date::excelToDateTimeObject($sheet->getCell("A{$i}")->getValue()); + + if (Carbon::createFromInterface($date)->isWeekend()) { + $sheet->getStyle("A{$i}:{$lastColumn}{$i}") + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB("FEE2E2"); + } + } + + $sheet->getStyle("A1:{$lastColumn}{$lastRow}") + ->getBorders() + ->getAllBorders() + ->setBorderStyle(Border::BORDER_THIN) + ->getColor() + ->setRGB("B7B7B7"); + } + + public static function afterSheet(AfterSheet $event): void + { + $sheet = $event->getSheet(); + $lastRow = $sheet->getDelegate()->getHighestRow(); + + $sheet->append([ + __("Sum:"), + null, + null, + null, + "=SUM(E2:E{$lastRow})", + ]); + + $lastRow++; + + $sheet->getDelegate()->getStyle("A{$lastRow}") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_RIGHT); + + $sheet->getDelegate()->getStyle("A{$lastRow}") + ->getFont() + ->setBold(true); + + $sheet->getDelegate()->getStyle("E{$lastRow}") + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER); + + $sheet->getDelegate()->mergeCells("A{$lastRow}:D{$lastRow}"); + + $sheet->getDelegate()->getStyle("A{$lastRow}:E{$lastRow}") + ->getBorders() + ->getAllBorders() + ->setBorderStyle(Border::BORDER_THIN) + ->getColor() + ->setRGB("B7B7B7"); + } + + protected function getVacationsForPeriod(User $user, CarbonPeriod $period): Collection + { + return $user->vacations() + ->with("vacationRequest") + ->whereBetween("date", [$period->start, $period->end]) + ->whereRelation("vacationRequest", "state", VacationRequestState::Approved->value) + ->get() + ->groupBy( + [ + fn(Vacation $vacation) => $vacation->date->toDateString(), + fn(Vacation $vacation) => $vacation->vacationRequest->type->value, + ], + ); + } + + protected function getHolidaysForPeriod(CarbonPeriod $period): Collection + { + return Holiday::query() + ->whereBetween("date", [$period->start, $period->end]) + ->pluck("date"); + } + + protected function toExcelTime(Carbon $time): float + { + $excelTimestamp = Date::dateTimeToExcel($time); + $excelDate = floor($excelTimestamp); + + return $excelTimestamp - $excelDate; + } + + protected function checkIfWorkedThisDay(CarbonInterface $day, Collection $holidays, Collection $vacations): bool + { + return $day->isWeekday() && $holidays->doesntContain($day) && $vacations->isEmpty(); + } +} diff --git a/app/Infrastructure/Http/Controllers/TimesheetController.php b/app/Infrastructure/Http/Controllers/TimesheetController.php new file mode 100644 index 0000000..9f7fcd7 --- /dev/null +++ b/app/Infrastructure/Http/Controllers/TimesheetController.php @@ -0,0 +1,35 @@ +selected(); + $carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber()); + + $users = User::query() + ->orderBy("last_name") + ->orderBy("first_name") + ->get(); + + $filename = "{$carbonMonth->translatedFormat("F Y")}.xlsx"; + + $timesheet = (new TimesheetExport()) + ->forMonth($carbonMonth) + ->forUsers($users); + + return Excel::download($timesheet, $filename); + } +} diff --git a/app/Infrastructure/Http/Controllers/VacationCalendarController.php b/app/Infrastructure/Http/Controllers/VacationCalendarController.php index d37cce6..ec7a33a 100644 --- a/app/Infrastructure/Http/Controllers/VacationCalendarController.php +++ b/app/Infrastructure/Http/Controllers/VacationCalendarController.php @@ -4,11 +4,10 @@ declare(strict_types=1); namespace Toby\Infrastructure\Http\Controllers; -use Illuminate\Http\Request; use Illuminate\Support\Carbon; -use Illuminate\Support\Str; use Inertia\Response; use Toby\Domain\CalendarGenerator; +use Toby\Domain\Enums\Month; use Toby\Eloquent\Helpers\YearPeriodRetriever; use Toby\Eloquent\Models\User; use Toby\Infrastructure\Http\Resources\UserResource; @@ -16,22 +15,25 @@ use Toby\Infrastructure\Http\Resources\UserResource; class VacationCalendarController extends Controller { public function index( - Request $request, YearPeriodRetriever $yearPeriodRetriever, CalendarGenerator $calendarGenerator, + ?string $month = null, ): Response { - $month = Str::lower($request->query("month", Carbon::now()->englishMonth)); + $month = Month::fromNameOrCurrent((string)$month); + $yearPeriod = $yearPeriodRetriever->selected(); + $carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber()); + $users = User::query() ->orderBy("last_name") ->orderBy("first_name") ->get(); - $calendar = $calendarGenerator->generate($yearPeriod, $month); + $calendar = $calendarGenerator->generate($carbonMonth); return inertia("Calendar", [ "calendar" => $calendar, - "currentMonth" => $month, + "currentMonth" => $month->value, "users" => UserResource::collection($users), ]); } diff --git a/composer.json b/composer.json index 92a4bdb..5c28597 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "laravel/telescope": "^4.6", "laravel/tinker": "^2.5", "lasserafn/php-initial-avatar-generator": "^4.2", + "maatwebsite/excel": "^3.1", "spatie/laravel-google-calendar": "^3.5" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 6733691..bf41929 100644 --- a/composer.lock +++ b/composer.lock @@ -4,64 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c0f36b87659921d0b7db0464b5d79ed5", + "content-hash": "09609461b05d589abb8bc0cbac9c653c", "packages": [ - { - "name": "asm89/stack-cors", - "version": "v2.1.1", - "source": { - "type": "git", - "url": "https://github.com/asm89/stack-cors.git", - "reference": "73e5b88775c64ccc0b84fb60836b30dc9d92ac4a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/asm89/stack-cors/zipball/73e5b88775c64ccc0b84fb60836b30dc9d92ac4a", - "reference": "73e5b88775c64ccc0b84fb60836b30dc9d92ac4a", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0", - "symfony/http-foundation": "^4|^5|^6", - "symfony/http-kernel": "^4|^5|^6" - }, - "require-dev": { - "phpunit/phpunit": "^7|^9", - "squizlabs/php_codesniffer": "^3.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "Asm89\\Stack\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alexander", - "email": "iam.asm89@gmail.com" - } - ], - "description": "Cross-origin resource sharing library and stack middleware", - "homepage": "https://github.com/asm89/stack-cors", - "keywords": [ - "cors", - "stack" - ], - "support": { - "issues": "https://github.com/asm89/stack-cors/issues", - "source": "https://github.com/asm89/stack-cors/tree/v2.1.1" - }, - "time": "2022-01-18T09:12:03+00:00" - }, { "name": "azuyalabs/yasumi", "version": "2.5.0", @@ -709,6 +653,57 @@ ], "time": "2021-10-11T09:18:27+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.14.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ], + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.14.0" + }, + "time": "2021-12-25T01:21:49+00:00" + }, { "name": "firebase/php-jwt", "version": "v5.5.1", @@ -768,25 +763,23 @@ }, { "name": "fruitcake/laravel-cors", - "version": "v2.0.5", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/fruitcake/laravel-cors.git", - "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc" + "reference": "361d71f00a0eea8b74da26ae75d0d207c53aa5b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/3a066e5cac32e2d1cdaacd6b961692778f37b5fc", - "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/361d71f00a0eea8b74da26ae75d0d207c53aa5b3", + "reference": "361d71f00a0eea8b74da26ae75d0d207c53aa5b3", "shasum": "" }, "require": { - "asm89/stack-cors": "^2.0.1", + "fruitcake/php-cors": "^1", "illuminate/contracts": "^6|^7|^8|^9", "illuminate/support": "^6|^7|^8|^9", - "php": ">=7.2", - "symfony/http-foundation": "^4|^5|^6", - "symfony/http-kernel": "^4.3.4|^5|^6" + "php": ">=7.2" }, "require-dev": { "laravel/framework": "^6|^7.24|^8", @@ -797,7 +790,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" }, "laravel": { "providers": [ @@ -833,7 +826,7 @@ ], "support": { "issues": "https://github.com/fruitcake/laravel-cors/issues", - "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.5" + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.1.0" }, "funding": [ { @@ -845,7 +838,78 @@ "type": "github" } ], - "time": "2022-01-03T14:53:04+00:00" + "time": "2022-02-19T14:17:28+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-02-20T15:07:15+00:00" }, { "name": "google/apiclient", @@ -920,7 +984,7 @@ }, { "name": "google/apiclient-services", - "version": "v0.235.0", + "version": "v0.236.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", @@ -958,7 +1022,7 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.235.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.236.0" }, "time": "2022-02-07T14:04:26+00:00" }, @@ -1437,12 +1501,12 @@ } }, "autoload": { - "psr-4": { - "Inertia\\": "src" - }, "files": [ "./helpers.php" - ] + ], + "psr-4": { + "Inertia\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2632,6 +2696,262 @@ }, "time": "2021-08-15T23:05:49+00:00" }, + { + "name": "maatwebsite/excel", + "version": "3.1.36", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "eb31f30d72c51c3fb11644b636945accbe50404f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/eb31f30d72c51c3fb11644b636945accbe50404f", + "reference": "eb31f30d72c51c3fb11644b636945accbe50404f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/support": "5.8.*|^6.0|^7.0|^8.0|^9.0", + "php": "^7.0|^8.0", + "phpoffice/phpspreadsheet": "^1.18" + }, + "require-dev": { + "orchestra/testbench": "^6.0|^7.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ], + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + } + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.36" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2022-01-27T18:34:20+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/master" + }, + "funding": [ + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2020-05-30T13:11:16+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/ab8bc271e404909db09ff2d5ffa1e538085c0f22", + "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.1" + }, + "time": "2021-06-29T15:32:53+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/c66aefcafb4f6c269510e9ac46b82619a904c576", + "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "phpcompatibility/php-compatibility": "^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.0" + }, + "time": "2021-07-01T19:01:15+00:00" + }, { "name": "meyfa/php-svg", "version": "v0.9.1", @@ -2781,6 +3101,66 @@ ], "time": "2021-10-01T21:08:31+00:00" }, + { + "name": "myclabs/php-enum", + "version": "1.8.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "b942d263c641ddb5190929ff840c68f78713e937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937", + "reference": "b942d263c641ddb5190929ff840c68f78713e937", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "support": { + "issues": "https://github.com/myclabs/php-enum/issues", + "source": "https://github.com/myclabs/php-enum/tree/1.8.3" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ], + "time": "2021-07-05T08:18:36+00:00" + }, { "name": "nesbot/carbon", "version": "2.57.0", @@ -3360,6 +3740,110 @@ }, "time": "2021-12-17T14:08:35+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.22.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a9e29b4f386a08a151a33578e80ef1747037a48", + "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.13", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.3 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "dompdf/dompdf": "^1.0", + "friendsofphp/php-cs-fixer": "^3.2", + "jpgraph/jpgraph": "^4.0", + "mpdf/mpdf": "8.0.17", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.6", + "tecnickcom/tcpdf": "^6.4" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)", + "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.22.0" + }, + "time": "2022-02-18T12:57:07+00:00" + }, { "name": "phpoption/phpoption", "version": "1.8.1", @@ -3906,25 +4390,25 @@ }, { "name": "psr/simple-cache", - "version": "3.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { @@ -3939,7 +4423,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "homepage": "http://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -3951,9 +4435,9 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + "source": "https://github.com/php-fig/simple-cache/tree/master" }, - "time": "2021-10-29T13:26:27+00:00" + "time": "2017-10-23T01:57:42+00:00" }, { "name": "psy/psysh", @@ -7445,16 +7929,16 @@ }, { "name": "phar-io/version", - "version": "3.1.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "15a90844ad40f127afd244c0cad228de2a80052a" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/15a90844ad40f127afd244c0cad228de2a80052a", - "reference": "15a90844ad40f127afd244c0cad228de2a80052a", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -7490,9 +7974,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.1" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2022-02-07T21:56:48+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "php-webdriver/webdriver", @@ -7788,16 +8272,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.10", + "version": "9.2.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" + "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/665a1ac0a763c51afc30d6d130dac0813092b17f", + "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f", "shasum": "" }, "require": { @@ -7853,7 +8337,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.11" }, "funding": [ { @@ -7861,7 +8345,7 @@ "type": "github" } ], - "time": "2021-12-05T09:12:13+00:00" + "time": "2022-02-18T12:46:09+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8106,16 +8590,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.13", + "version": "9.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "597cb647654ede35e43b137926dfdfef0fb11743" + "reference": "1883687169c017d6ae37c58883ca3994cfc34189" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/597cb647654ede35e43b137926dfdfef0fb11743", - "reference": "597cb647654ede35e43b137926dfdfef0fb11743", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1883687169c017d6ae37c58883ca3994cfc34189", + "reference": "1883687169c017d6ae37c58883ca3994cfc34189", "shasum": "" }, "require": { @@ -8193,7 +8677,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.14" }, "funding": [ { @@ -8205,7 +8689,7 @@ "type": "github" } ], - "time": "2022-01-24T07:33:35+00:00" + "time": "2022-02-18T12:54:07+00:00" }, { "name": "sebastian/cli-parser", @@ -9299,16 +9783,16 @@ }, { "name": "spatie/ignition", - "version": "1.0.4", + "version": "1.0.5", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "617c41d1bf675d95a7bd9adc826ba93d43affe7f" + "reference": "6b7bb804f4834b080f5ac941f6ac6800a485011e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/617c41d1bf675d95a7bd9adc826ba93d43affe7f", - "reference": "617c41d1bf675d95a7bd9adc826ba93d43affe7f", + "url": "https://api.github.com/repos/spatie/ignition/zipball/6b7bb804f4834b080f5ac941f6ac6800a485011e", + "reference": "6b7bb804f4834b080f5ac941f6ac6800a485011e", "shasum": "" }, "require": { @@ -9366,7 +9850,7 @@ "type": "github" } ], - "time": "2022-02-16T09:02:42+00:00" + "time": "2022-02-17T21:40:47+00:00" }, { "name": "spatie/laravel-ignition", diff --git a/config/excel.php b/config/excel.php new file mode 100644 index 0000000..216664a --- /dev/null +++ b/config/excel.php @@ -0,0 +1,104 @@ + [ + "chunk_size" => 1000, + "pre_calculate_formulas" => false, + "strict_null_comparison" => false, + "csv" => [ + "delimiter" => ",", + "enclosure" => '"', + "line_ending" => PHP_EOL, + "use_bom" => false, + "include_separator_line" => false, + "excel_compatibility" => false, + "output_encoding" => "", + ], + "properties" => [ + "creator" => "", + "lastModifiedBy" => "", + "title" => "", + "description" => "", + "subject" => "", + "keywords" => "", + "category" => "", + "manager" => "", + "company" => "", + ], + ], + + "imports" => [ + "read_only" => true, + "ignore_empty" => false, + "heading_row" => [ + "formatter" => "slug", + ], + "csv" => [ + "delimiter" => ",", + "enclosure" => '"', + "escape_character" => "\\", + "contiguous" => false, + "input_encoding" => "UTF-8", + ], + "properties" => [ + "creator" => "", + "lastModifiedBy" => "", + "title" => "", + "description" => "", + "subject" => "", + "keywords" => "", + "category" => "", + "manager" => "", + "company" => "", + ], + ], + "extension_detector" => [ + "xlsx" => Excel::XLSX, + "xlsm" => Excel::XLSX, + "xltx" => Excel::XLSX, + "xltm" => Excel::XLSX, + "xls" => Excel::XLS, + "xlt" => Excel::XLS, + "ods" => Excel::ODS, + "ots" => Excel::ODS, + "slk" => Excel::SLK, + "xml" => Excel::XML, + "gnumeric" => Excel::GNUMERIC, + "htm" => Excel::HTML, + "html" => Excel::HTML, + "csv" => Excel::CSV, + "tsv" => Excel::TSV, + "pdf" => Excel::DOMPDF, + ], + "value_binder" => [ + "default" => DefaultValueBinder::class, + ], + + "cache" => [ + "driver" => "memory", + "batch" => [ + "memory_limit" => 60000, + ], + "illuminate" => [ + "store" => null, + ], + ], + "transactions" => [ + "handler" => "db", + "db" => [ + "connection" => null, + ], + ], + + "temporary_files" => [ + "local_path" => storage_path("framework/cache/laravel-excel"), + "remote_disk" => null, + "remote_prefix" => null, + "force_resync_remote" => null, + ], +]; diff --git a/resources/js/Pages/Calendar.vue b/resources/js/Pages/Calendar.vue index 1415b0a..1a8e095 100644 --- a/resources/js/Pages/Calendar.vue +++ b/resources/js/Pages/Calendar.vue @@ -1,10 +1,20 @@