Merge branch 'main' into #22-vacation-calendar
# Conflicts: # resources/js/Pages/Dashboard.vue # resources/js/Shared/Layout/AppLayout.vue # resources/js/Shared/MainMenu.vue # routes/web.php
This commit is contained in:
		
							
								
								
									
										32
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @@ -1,17 +1,17 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
|     env: { |   env: { | ||||||
|         node: true, |     node: true, | ||||||
|     }, |     'vue/setup-compiler-macros': true, | ||||||
|     extends: [ |   }, | ||||||
|         'eslint:recommended', |   extends: [ | ||||||
|         'plugin:vue/vue3-recommended', |     'eslint:recommended', | ||||||
|     ], |     'plugin:vue/vue3-recommended', | ||||||
|     rules: { |   ], | ||||||
|         semi: [2, 'always'], |   rules: { | ||||||
|         quotes: ['error', 'single'], |     semi: [2, 'never'], | ||||||
|         indent: ['error', 4], |     quotes: ['error', 'single'], | ||||||
|         'vue/html-indent': ['error', 4], |     indent: ['error', 2], | ||||||
|         'vue/multi-word-component-names': 'off', |     'vue/html-indent': ['error', 2], | ||||||
|         'comma-dangle': ['error', 'always-multiline'], |     'comma-dangle': ['error', 'always-multiline'], | ||||||
|     }, |   }, | ||||||
| }; | } | ||||||
|   | |||||||
| @@ -5,8 +5,24 @@ declare(strict_types=1); | |||||||
| namespace Toby\Architecture\Providers; | namespace Toby\Architecture\Providers; | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByAdministrative; | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByTechnical; | ||||||
|  | use Toby\Domain\Events\VacationRequestApproved; | ||||||
|  | use Toby\Domain\Events\VacationRequestCreated; | ||||||
|  | use Toby\Domain\Events\VacationRequestStateChanged; | ||||||
|  | use Toby\Domain\Listeners\CreateVacationRequestActivity; | ||||||
|  | use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest; | ||||||
|  | use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest; | ||||||
|  | use Toby\Domain\Listeners\HandleApprovedVacationRequest; | ||||||
|  | use Toby\Domain\Listeners\HandleCreatedVacationRequest; | ||||||
|  |  | ||||||
| class EventServiceProvider extends ServiceProvider | class EventServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|     protected $listen = []; |     protected $listen = [ | ||||||
|  |         VacationRequestStateChanged::class => [CreateVacationRequestActivity::class], | ||||||
|  |         VacationRequestCreated::class => [HandleCreatedVacationRequest::class], | ||||||
|  |         VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class], | ||||||
|  |         VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class], | ||||||
|  |         VacationRequestApproved::class => [HandleApprovedVacationRequest::class], | ||||||
|  |     ]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ namespace Toby\Architecture\Providers; | |||||||
|  |  | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| use Toby\Eloquent\Observers\UserObserver; | use Toby\Eloquent\Observers\UserObserver; | ||||||
|  | use Toby\Eloquent\Observers\VacationRequestObserver; | ||||||
| use Toby\Eloquent\Observers\YearPeriodObserver; | use Toby\Eloquent\Observers\YearPeriodObserver; | ||||||
|  |  | ||||||
| class ObserverServiceProvider extends ServiceProvider | class ObserverServiceProvider extends ServiceProvider | ||||||
| @@ -16,5 +18,6 @@ class ObserverServiceProvider extends ServiceProvider | |||||||
|     { |     { | ||||||
|         User::observe(UserObserver::class); |         User::observe(UserObserver::class); | ||||||
|         YearPeriod::observe(YearPeriodObserver::class); |         YearPeriod::observe(YearPeriodObserver::class); | ||||||
|  |         VacationRequest::observe(VacationRequestObserver::class); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| declare(strict_types=1); | declare(strict_types=1); | ||||||
| 
 | 
 | ||||||
| namespace Toby\Domain; | namespace Toby\Domain\Enums; | ||||||
| 
 | 
 | ||||||
| enum EmploymentForm: string | enum EmploymentForm: string | ||||||
| { | { | ||||||
							
								
								
									
										30
									
								
								app/Domain/Enums/Role.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/Domain/Enums/Role.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Enums; | ||||||
|  |  | ||||||
|  | enum Role: string | ||||||
|  | { | ||||||
|  |     case EMPLOYEE = "employee"; | ||||||
|  |     case ADMINISTRATOR = "administrator"; | ||||||
|  |     case TECHNICAL_APPROVER = "technical_approver"; | ||||||
|  |     case ADMINISTRATIVE_APPROVER = "administrative_approver"; | ||||||
|  |  | ||||||
|  |     public function label(): string | ||||||
|  |     { | ||||||
|  |         return __($this->value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function casesToSelect(): array | ||||||
|  |     { | ||||||
|  |         $cases = collect(Role::cases()); | ||||||
|  |  | ||||||
|  |         return $cases->map( | ||||||
|  |             fn(Role $enum) => [ | ||||||
|  |                 "label" => $enum->label(), | ||||||
|  |                 "value" => $enum->value, | ||||||
|  |             ], | ||||||
|  |         )->toArray(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								app/Domain/Enums/VacationRequestState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/Domain/Enums/VacationRequestState.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Enums; | ||||||
|  |  | ||||||
|  | enum VacationRequestState: string | ||||||
|  | { | ||||||
|  |     case CREATED = "created"; | ||||||
|  |     case CANCELED = "canceled"; | ||||||
|  |     case REJECTED = "rejected"; | ||||||
|  |     case APPROVED = "approved"; | ||||||
|  |     case WAITING_FOR_TECHNICAL = "waiting_for_technical"; | ||||||
|  |     case WAITING_FOR_ADMINISTRATIVE = "waiting_for_administrative"; | ||||||
|  |     case ACCEPTED_BY_TECHNICAL = "accepted_by_technical"; | ||||||
|  |     case ACCEPTED_BY_ADMINSTRATIVE = "accepted_by_administrative"; | ||||||
|  |  | ||||||
|  |     public function label(): string | ||||||
|  |     { | ||||||
|  |         return __($this->value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function pendingStates(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             self::CREATED, | ||||||
|  |             self::WAITING_FOR_TECHNICAL, | ||||||
|  |             self::WAITING_FOR_ADMINISTRATIVE, | ||||||
|  |             self::ACCEPTED_BY_TECHNICAL, | ||||||
|  |             self::ACCEPTED_BY_ADMINSTRATIVE, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function successStates(): array | ||||||
|  |     { | ||||||
|  |         return [self::APPROVED]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function failedStates(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             self::REJECTED, | ||||||
|  |             self::CANCELED, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function filterByStatus(string $filter): array | ||||||
|  |     { | ||||||
|  |         return match ($filter) { | ||||||
|  |             "pending" => VacationRequestState::pendingStates(), | ||||||
|  |             "success" => VacationRequestState::successStates(), | ||||||
|  |             "failed" => VacationRequestState::failedStates(), | ||||||
|  |             default => VacationRequestState::cases(), | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								app/Domain/Enums/VacationType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Domain/Enums/VacationType.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Enums; | ||||||
|  |  | ||||||
|  | enum VacationType: string | ||||||
|  | { | ||||||
|  |     case VACATION = "vacation"; | ||||||
|  |     case VACATION_ON_REQUEST = "vacation_on_request"; | ||||||
|  |     case SPECIAL_VACATION = "special_vacation"; | ||||||
|  |     case CHILDCARE_VACATION = "childcare_vacation"; | ||||||
|  |     case TRAINING_VACATION = "training_vacation"; | ||||||
|  |     case UNPAID_VACATION = "unpaid_vacation"; | ||||||
|  |     case VOLUNTEERING_VACATION = "volunteering_vacation"; | ||||||
|  |     case TIME_IN_LIEU = "time_in_lieu"; | ||||||
|  |     case SICK_VACATION = "sick_vacation"; | ||||||
|  |  | ||||||
|  |     public function label(): string | ||||||
|  |     { | ||||||
|  |         return __($this->value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static function casesToSelect(): array | ||||||
|  |     { | ||||||
|  |         $cases = collect(VacationType::cases()); | ||||||
|  |  | ||||||
|  |         return $cases->map( | ||||||
|  |             fn(VacationType $enum) => [ | ||||||
|  |                 "label" => $enum->label(), | ||||||
|  |                 "value" => $enum->value, | ||||||
|  |             ], | ||||||
|  |         )->toArray(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Events; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestAcceptedByAdministrative | ||||||
|  | { | ||||||
|  |     use Dispatchable; | ||||||
|  |     use SerializesModels; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         public VacationRequest $vacationRequest, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								app/Domain/Events/VacationRequestAcceptedByTechnical.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/Domain/Events/VacationRequestAcceptedByTechnical.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Events; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestAcceptedByTechnical | ||||||
|  | { | ||||||
|  |     use Dispatchable; | ||||||
|  |     use SerializesModels; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         public VacationRequest $vacationRequest, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								app/Domain/Events/VacationRequestApproved.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/Domain/Events/VacationRequestApproved.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Events; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestApproved | ||||||
|  | { | ||||||
|  |     use Dispatchable; | ||||||
|  |     use SerializesModels; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         public VacationRequest $vacationRequest, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								app/Domain/Events/VacationRequestCreated.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/Domain/Events/VacationRequestCreated.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Events; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestCreated | ||||||
|  | { | ||||||
|  |     use Dispatchable; | ||||||
|  |     use SerializesModels; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         public VacationRequest $vacationRequest, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								app/Domain/Events/VacationRequestStateChanged.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Domain/Events/VacationRequestStateChanged.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Events; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestStateChanged | ||||||
|  | { | ||||||
|  |     use Dispatchable; | ||||||
|  |     use SerializesModels; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         public VacationRequest $vacationRequest, | ||||||
|  |         public ?VacationRequestState $from, | ||||||
|  |         public VacationRequestState $to, | ||||||
|  |         public ?User $user = null, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								app/Domain/Listeners/CreateVacationRequestActivity.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Domain/Listeners/CreateVacationRequestActivity.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Listeners; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Events\VacationRequestStateChanged; | ||||||
|  |  | ||||||
|  | class CreateVacationRequestActivity | ||||||
|  | { | ||||||
|  |     public function handle(VacationRequestStateChanged $event): void | ||||||
|  |     { | ||||||
|  |         $event->vacationRequest->activities()->create([ | ||||||
|  |             "from" => $event->from, | ||||||
|  |             "to" => $event->to, | ||||||
|  |             "user_id" => $event->user?->id, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Listeners; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByAdministrative; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  |  | ||||||
|  | class HandleAcceptedByAdministrativeVacationRequest | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function handle(VacationRequestAcceptedByAdministrative $event): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->approve($event->vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Listeners; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByTechnical; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  |  | ||||||
|  | class HandleAcceptedByTechnicalVacationRequest | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function handle(VacationRequestAcceptedByTechnical $event): void | ||||||
|  |     { | ||||||
|  |         $vacationRequest = $event->vacationRequest; | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) { | ||||||
|  |             $this->stateManager->waitForAdministrative($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $this->stateManager->approve($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								app/Domain/Listeners/HandleApprovedVacationRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/Domain/Listeners/HandleApprovedVacationRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Listeners; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
|  | use Toby\Domain\Events\VacationRequestApproved; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  |  | ||||||
|  | class HandleApprovedVacationRequest | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function handle(VacationRequestApproved $event): void | ||||||
|  |     { | ||||||
|  |         $vacationRequest = $event->vacationRequest; | ||||||
|  |  | ||||||
|  |         Log::info("approved! {$vacationRequest->id}"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								app/Domain/Listeners/HandleCreatedVacationRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/Domain/Listeners/HandleCreatedVacationRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Listeners; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Events\VacationRequestCreated; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  |  | ||||||
|  | class HandleCreatedVacationRequest | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function handle(VacationRequestCreated $event): void | ||||||
|  |     { | ||||||
|  |         $vacationRequest = $event->vacationRequest; | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) { | ||||||
|  |             $this->stateManager->waitForTechnical($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) { | ||||||
|  |             $this->stateManager->waitForAdministrative($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $this->stateManager->approve($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								app/Domain/VacationRequestStateManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								app/Domain/VacationRequestStateManager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Auth\Factory as Auth; | ||||||
|  | use Illuminate\Contracts\Events\Dispatcher; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByAdministrative; | ||||||
|  | use Toby\Domain\Events\VacationRequestAcceptedByTechnical; | ||||||
|  | use Toby\Domain\Events\VacationRequestApproved; | ||||||
|  | use Toby\Domain\Events\VacationRequestCreated; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestStateManager | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected Auth $auth, | ||||||
|  |         protected Dispatcher $dispatcher, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function markAsCreated(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::CREATED); | ||||||
|  |  | ||||||
|  |         $this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function approve(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::APPROVED); | ||||||
|  |  | ||||||
|  |         $this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function reject(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::REJECTED); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function cancel(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::CANCELED); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function acceptAsTechnical(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_TECHNICAL); | ||||||
|  |  | ||||||
|  |         $this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function acceptAsAdministrative(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_ADMINSTRATIVE); | ||||||
|  |  | ||||||
|  |         $this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function waitForTechnical(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_TECHNICAL); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function waitForAdministrative(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_ADMINISTRATIVE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void | ||||||
|  |     { | ||||||
|  |         $vacationRequest->changeStateTo($state); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								app/Domain/VacationTypeConfigRetriever.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Domain/VacationTypeConfigRetriever.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Config\Repository; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  |  | ||||||
|  | class VacationTypeConfigRetriever | ||||||
|  | { | ||||||
|  |     public const KEY_TECHNICAL_APPROVAL = "technical_approval"; | ||||||
|  |     public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval"; | ||||||
|  |     public const KEY_BILLABLE = "billable"; | ||||||
|  |     public const KEY_HAS_LIMIT = "has_limit"; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected Repository $config, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function needsTechnicalApproval(VacationType $type): bool | ||||||
|  |     { | ||||||
|  |         return $this->getConfigFor($type)[static::KEY_TECHNICAL_APPROVAL]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function needsAdministrativeApproval(VacationType $type): bool | ||||||
|  |     { | ||||||
|  |         return $this->getConfigFor($type)[static::KEY_ADMINISTRATIVE_APPROVAL]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function isBillable(VacationType $type): bool | ||||||
|  |     { | ||||||
|  |         return $this->getConfigFor($type)[static::KEY_BILLABLE]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function hasLimit(VacationType $type): bool | ||||||
|  |     { | ||||||
|  |         return $this->getConfigFor($type)[static::KEY_HAS_LIMIT]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getConfigFor(VacationType $type): array | ||||||
|  |     { | ||||||
|  |         return $this->config->get("vacation_types.{$type->value}"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class ApprovedVacationDaysInSameRange implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next) | ||||||
|  |     { | ||||||
|  |         return $next($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class DoesNotExceedLimitRule implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next) | ||||||
|  |     { | ||||||
|  |         return $next($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class MinimumOneVacationDayRule implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next) | ||||||
|  |     { | ||||||
|  |         return $next($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class PendingVacationRequestInSameRange implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next) | ||||||
|  |     { | ||||||
|  |         return $next($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class UsedVacationDaysInSameRange | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next) | ||||||
|  |     { | ||||||
|  |         return $next($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								app/Domain/Validation/Rules/VacationRequestRule.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/Domain/Validation/Rules/VacationRequestRule.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Closure; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | interface VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest, Closure $next); | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								app/Domain/Validation/VacationRequestValidator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Domain/Validation/VacationRequestValidator.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation; | ||||||
|  |  | ||||||
|  | use Illuminate\Pipeline\Pipeline; | ||||||
|  | use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange; | ||||||
|  | use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule; | ||||||
|  | use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule; | ||||||
|  | use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestValidator | ||||||
|  | { | ||||||
|  |     protected array $rules = [ | ||||||
|  |         MinimumOneVacationDayRule::class, | ||||||
|  |         DoesNotExceedLimitRule::class, | ||||||
|  |         PendingVacationRequestInSameRange::class, | ||||||
|  |         ApprovedVacationDaysInSameRange::class, | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected Pipeline $pipeline, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function validate(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->pipeline | ||||||
|  |             ->send($vacationRequest) | ||||||
|  |             ->through($this->rules) | ||||||
|  |             ->via("check"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -13,7 +13,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable; | |||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Domain\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
| @@ -21,9 +22,11 @@ use Toby\Domain\EmploymentForm; | |||||||
|  * @property string $last_name |  * @property string $last_name | ||||||
|  * @property string $email |  * @property string $email | ||||||
|  * @property string $avatar |  * @property string $avatar | ||||||
|  |  * @property Role $role | ||||||
|  * @property EmploymentForm $employment_form |  * @property EmploymentForm $employment_form | ||||||
|  * @property Carbon $employment_date |  * @property Carbon $employment_date | ||||||
|  * @property Collection $vacationLimits |  * @property Collection $vacationLimits | ||||||
|  |  * @property Collection $vacationRequests | ||||||
|  */ |  */ | ||||||
| class User extends Authenticatable | class User extends Authenticatable | ||||||
| { | { | ||||||
| @@ -34,6 +37,7 @@ class User extends Authenticatable | |||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|  |         "role" => Role::class, | ||||||
|         "employment_form" => EmploymentForm::class, |         "employment_form" => EmploymentForm::class, | ||||||
|         "employment_date" => "date", |         "employment_date" => "date", | ||||||
|     ]; |     ]; | ||||||
| @@ -47,6 +51,11 @@ class User extends Authenticatable | |||||||
|         return $this->hasMany(VacationLimit::class); |         return $this->hasMany(VacationLimit::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function vacationRequests(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(VacationRequest::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function scopeSearch(Builder $query, ?string $text): Builder |     public function scopeSearch(Builder $query, ?string $text): Builder | ||||||
|     { |     { | ||||||
|         if ($text === null) { |         if ($text === null) { | ||||||
| @@ -71,6 +80,11 @@ class User extends Authenticatable | |||||||
|         return "{$this->first_name} {$this->last_name}"; |         return "{$this->first_name} {$this->last_name}"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function hasRole(Role $role): bool | ||||||
|  |     { | ||||||
|  |         return $this->role === $role; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): UserFactory |     protected static function newFactory(): UserFactory | ||||||
|     { |     { | ||||||
|         return UserFactory::new(); |         return UserFactory::new(); | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								app/Eloquent/Models/VacationRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/Eloquent/Models/VacationRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Database\Factories\VacationRequestFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property int $id | ||||||
|  |  * @property VacationType $type | ||||||
|  |  * @property VacationRequestState $state | ||||||
|  |  * @property Carbon $from | ||||||
|  |  * @property Carbon $to | ||||||
|  |  * @property string $comment | ||||||
|  |  * @property User $user | ||||||
|  |  * @property Collection $activities | ||||||
|  |  */ | ||||||
|  | class VacationRequest extends Model | ||||||
|  | { | ||||||
|  |     use HasFactory; | ||||||
|  |  | ||||||
|  |     protected $guarded = []; | ||||||
|  |  | ||||||
|  |     protected $casts = [ | ||||||
|  |         "type" => VacationType::class, | ||||||
|  |         "state" => VacationRequestState::class, | ||||||
|  |         "from" => "date", | ||||||
|  |         "to" => "date", | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     public function user(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(User::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function activities(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(VacationRequestActivity::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function changeStateTo(VacationRequestState $state): void | ||||||
|  |     { | ||||||
|  |         $this->state = $state; | ||||||
|  |  | ||||||
|  |         $this->save(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopeStates(Builder $query, array $states): Builder | ||||||
|  |     { | ||||||
|  |         return $query->whereIn("state", $states); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected static function newFactory(): VacationRequestFactory | ||||||
|  |     { | ||||||
|  |         return VacationRequestFactory::new(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								app/Eloquent/Models/VacationRequestActivity.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Eloquent/Models/VacationRequestActivity.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property int $id | ||||||
|  |  * @property VacationRequest $vacationRequest | ||||||
|  |  * @property ?User $user | ||||||
|  |  * @property ?VacationRequestState $from | ||||||
|  |  * @property VacationRequestState $to | ||||||
|  |  */ | ||||||
|  | class VacationRequestActivity extends Model | ||||||
|  | { | ||||||
|  |     protected $guarded = []; | ||||||
|  |  | ||||||
|  |     protected $casts = [ | ||||||
|  |         "from" => VacationRequestState::class, | ||||||
|  |         "to" => VacationRequestState::class, | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     public function user(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(User::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function vacationRequest(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(VacationRequest::class); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								app/Eloquent/Observers/VacationRequestObserver.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/Eloquent/Observers/VacationRequestObserver.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Observers; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Auth\Factory as Auth; | ||||||
|  | use Illuminate\Events\Dispatcher; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Domain\Events\VacationRequestStateChanged; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestObserver | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected Auth $auth, | ||||||
|  |         protected Dispatcher $dispatcher, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function creating(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $year = $vacationRequest->from->year; | ||||||
|  |  | ||||||
|  |         $vacationRequestNumber = $vacationRequest->user->vacationRequests() | ||||||
|  |             ->whereYear("from", $year) | ||||||
|  |             ->count() + 1; | ||||||
|  |  | ||||||
|  |         $vacationRequest->name = "{$vacationRequestNumber}/${year}"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function saved(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         if ($vacationRequest->isDirty("state")) { | ||||||
|  |             $previousState = $vacationRequest->getOriginal("state"); | ||||||
|  |  | ||||||
|  |             $this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function fireStateChangedEvent( | ||||||
|  |         VacationRequest $vacationRequest, | ||||||
|  |         ?VacationRequestState $from, | ||||||
|  |         VacationRequestState $to, | ||||||
|  |     ): void { | ||||||
|  |         $event = new VacationRequestStateChanged($vacationRequest, $from, $to, $this->getAuthUser()); | ||||||
|  |  | ||||||
|  |         $this->dispatcher->dispatch($event); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getAuthUser(): ?User | ||||||
|  |     { | ||||||
|  |         /** @var User $user */ | ||||||
|  |         $user = $this->auth->guard()->user(); | ||||||
|  |  | ||||||
|  |         return $user; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Controllers; | |||||||
| use Illuminate\Http\RedirectResponse; | use Illuminate\Http\RedirectResponse; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Toby\Domain\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Infrastructure\Http\Requests\UserRequest; | use Toby\Infrastructure\Http\Requests\UserRequest; | ||||||
| use Toby\Infrastructure\Http\Resources\UserFormDataResource; | use Toby\Infrastructure\Http\Resources\UserFormDataResource; | ||||||
| @@ -35,6 +36,7 @@ class UserController extends Controller | |||||||
|     { |     { | ||||||
|         return inertia("Users/Create", [ |         return inertia("Users/Create", [ | ||||||
|             "employmentForms" => EmploymentForm::casesToSelect(), |             "employmentForms" => EmploymentForm::casesToSelect(), | ||||||
|  |             "roles" => Role::casesToSelect(), | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -52,6 +54,7 @@ class UserController extends Controller | |||||||
|         return inertia("Users/Edit", [ |         return inertia("Users/Edit", [ | ||||||
|             "user" => new UserFormDataResource($user), |             "user" => new UserFormDataResource($user), | ||||||
|             "employmentForms" => EmploymentForm::casesToSelect(), |             "employmentForms" => EmploymentForm::casesToSelect(), | ||||||
|  |             "roles" => Role::casesToSelect(), | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Controllers; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\RedirectResponse; | ||||||
|  | use Illuminate\Http\Request; | ||||||
|  | use Inertia\Response; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\Validation\VacationRequestValidator; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Http\Requests\VacationRequestRequest; | ||||||
|  | use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource; | ||||||
|  | use Toby\Infrastructure\Http\Resources\VacationRequestResource; | ||||||
|  |  | ||||||
|  | class VacationRequestController extends Controller | ||||||
|  | { | ||||||
|  |     public function index(Request $request): Response | ||||||
|  |     { | ||||||
|  |         $vacationRequests = $request->user() | ||||||
|  |             ->vacationRequests() | ||||||
|  |             ->latest() | ||||||
|  |             ->states(VacationRequestState::filterByStatus($request->query("status", "all"))) | ||||||
|  |             ->paginate(); | ||||||
|  |  | ||||||
|  |         return inertia("VacationRequest/Index", [ | ||||||
|  |             "requests" => VacationRequestResource::collection($vacationRequests), | ||||||
|  |             "filters" => $request->only("status"), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function show(VacationRequest $vacationRequest): Response | ||||||
|  |     { | ||||||
|  |         return inertia("VacationRequest/Show", [ | ||||||
|  |             "request" => new VacationRequestResource($vacationRequest), | ||||||
|  |             "activities" => VacationRequestActivityResource::collection($vacationRequest->activities), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function create(): Response | ||||||
|  |     { | ||||||
|  |         return inertia("VacationRequest/Create", [ | ||||||
|  |             "vacationTypes" => VacationType::casesToSelect(), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function store( | ||||||
|  |         VacationRequestRequest $request, | ||||||
|  |         VacationRequestValidator $vacationRequestValidator, | ||||||
|  |         VacationRequestStateManager $stateManager, | ||||||
|  |     ): RedirectResponse { | ||||||
|  |         /** @var VacationRequest $vacationRequest */ | ||||||
|  |         $vacationRequest = $request->user()->vacationRequests()->make($request->data()); | ||||||
|  |  | ||||||
|  |         $vacationRequestValidator->validate($vacationRequest); | ||||||
|  |  | ||||||
|  |         $vacationRequest->save(); | ||||||
|  |         $stateManager->markAsCreated($vacationRequest); | ||||||
|  |  | ||||||
|  |         return redirect() | ||||||
|  |             ->route("vacation.requests.index"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function reject( | ||||||
|  |         VacationRequest $vacationRequest, | ||||||
|  |         VacationRequestStateManager $stateManager, | ||||||
|  |     ): RedirectResponse { | ||||||
|  |         $stateManager->reject($vacationRequest); | ||||||
|  |  | ||||||
|  |         return redirect()->back(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function cancel( | ||||||
|  |         VacationRequest $vacationRequest, | ||||||
|  |         VacationRequestStateManager $stateManager, | ||||||
|  |     ): RedirectResponse { | ||||||
|  |         $stateManager->cancel($vacationRequest); | ||||||
|  |  | ||||||
|  |         return redirect()->back(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function acceptAsTechnical( | ||||||
|  |         VacationRequest $vacationRequest, | ||||||
|  |         VacationRequestStateManager $stateManager, | ||||||
|  |     ): RedirectResponse { | ||||||
|  |         $stateManager->acceptAsTechnical($vacationRequest); | ||||||
|  |  | ||||||
|  |         return redirect()->back(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function acceptAsAdministrative( | ||||||
|  |         VacationRequest $vacationRequest, | ||||||
|  |         VacationRequestStateManager $stateManager, | ||||||
|  |     ): RedirectResponse { | ||||||
|  |         $stateManager->acceptAsAdministrative($vacationRequest); | ||||||
|  |  | ||||||
|  |         return redirect()->back(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -23,6 +23,7 @@ use Illuminate\Routing\Middleware\ValidateSignature; | |||||||
| use Illuminate\Session\Middleware\AuthenticateSession; | use Illuminate\Session\Middleware\AuthenticateSession; | ||||||
| use Illuminate\Session\Middleware\StartSession; | use Illuminate\Session\Middleware\StartSession; | ||||||
| use Illuminate\View\Middleware\ShareErrorsFromSession; | use Illuminate\View\Middleware\ShareErrorsFromSession; | ||||||
|  | use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; | ||||||
| use Toby\Infrastructure\Http\Middleware\Authenticate; | use Toby\Infrastructure\Http\Middleware\Authenticate; | ||||||
| use Toby\Infrastructure\Http\Middleware\HandleInertiaRequests; | use Toby\Infrastructure\Http\Middleware\HandleInertiaRequests; | ||||||
| use Toby\Infrastructure\Http\Middleware\RedirectIfAuthenticated; | use Toby\Infrastructure\Http\Middleware\RedirectIfAuthenticated; | ||||||
| @@ -52,6 +53,7 @@ class Kernel extends HttpKernel | |||||||
|             HandleInertiaRequests::class, |             HandleInertiaRequests::class, | ||||||
|         ], |         ], | ||||||
|         "api" => [ |         "api" => [ | ||||||
|  |             EnsureFrontendRequestsAreStateful::class, | ||||||
|             "throttle:api", |             "throttle:api", | ||||||
|             SubstituteBindings::class, |             SubstituteBindings::class, | ||||||
|         ], |         ], | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Requests; | |||||||
| use Illuminate\Foundation\Http\FormRequest; | use Illuminate\Foundation\Http\FormRequest; | ||||||
| use Illuminate\Validation\Rule; | use Illuminate\Validation\Rule; | ||||||
| use Illuminate\Validation\Rules\Enum; | use Illuminate\Validation\Rules\Enum; | ||||||
| use Toby\Domain\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  |  | ||||||
| class UserRequest extends FormRequest | class UserRequest extends FormRequest | ||||||
| { | { | ||||||
| @@ -17,6 +18,7 @@ class UserRequest extends FormRequest | |||||||
|             "firstName" => ["required", "min:3", "max:80"], |             "firstName" => ["required", "min:3", "max:80"], | ||||||
|             "lastName" => ["required", "min:3", "max:80"], |             "lastName" => ["required", "min:3", "max:80"], | ||||||
|             "email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)], |             "email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)], | ||||||
|  |             "role" => ["required", new Enum(Role::class)], | ||||||
|             "employmentForm" => ["required", new Enum(EmploymentForm::class)], |             "employmentForm" => ["required", new Enum(EmploymentForm::class)], | ||||||
|             "employmentDate" => ["required", "date_format:Y-m-d"], |             "employmentDate" => ["required", "date_format:Y-m-d"], | ||||||
|         ]; |         ]; | ||||||
| @@ -28,6 +30,7 @@ class UserRequest extends FormRequest | |||||||
|             "first_name" => $this->get("firstName"), |             "first_name" => $this->get("firstName"), | ||||||
|             "last_name" => $this->get("lastName"), |             "last_name" => $this->get("lastName"), | ||||||
|             "email" => $this->get("email"), |             "email" => $this->get("email"), | ||||||
|  |             "role" => $this->get("role"), | ||||||
|             "employment_form" => $this->get("employmentForm"), |             "employment_form" => $this->get("employmentForm"), | ||||||
|             "employment_date" => $this->get("employmentDate"), |             "employment_date" => $this->get("employmentDate"), | ||||||
|         ]; |         ]; | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								app/Infrastructure/Http/Requests/VacationRequestRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/Infrastructure/Http/Requests/VacationRequestRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Requests; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Http\FormRequest; | ||||||
|  | use Illuminate\Validation\Rules\Enum; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Infrastructure\Http\Rules\YearPeriodExists; | ||||||
|  |  | ||||||
|  | class VacationRequestRequest extends FormRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "type" => ["required", new Enum(VacationType::class)], | ||||||
|  |             "from" => ["required", "date_format:Y-m-d", new YearPeriodExists()], | ||||||
|  |             "to" => ["required", "date_format:Y-m-d", new YearPeriodExists()], | ||||||
|  |             "comment" => ["nullable"], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function data(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "type" => $this->get("type"), | ||||||
|  |             "from" => $this->get("from"), | ||||||
|  |             "to" => $this->get("to"), | ||||||
|  |             "comment" => $this->get("comment"), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -15,6 +15,7 @@ class HolidayResource extends JsonResource | |||||||
|         return [ |         return [ | ||||||
|             "id" => $this->id, |             "id" => $this->id, | ||||||
|             "name" => $this->name, |             "name" => $this->name, | ||||||
|  |             "date" => $this->date->toDateString(), | ||||||
|             "displayDate" => $this->date->toDisplayString(), |             "displayDate" => $this->date->toDisplayString(), | ||||||
|             "dayOfWeek" => $this->date->dayName, |             "dayOfWeek" => $this->date->dayName, | ||||||
|         ]; |         ]; | ||||||
|   | |||||||
| @@ -17,8 +17,9 @@ class UserFormDataResource extends JsonResource | |||||||
|             "firstName" => $this->first_name, |             "firstName" => $this->first_name, | ||||||
|             "lastName" => $this->last_name, |             "lastName" => $this->last_name, | ||||||
|             "email" => $this->email, |             "email" => $this->email, | ||||||
|  |             "role" => $this->role, | ||||||
|             "employmentForm" => $this->employment_form, |             "employmentForm" => $this->employment_form, | ||||||
|             "employmentDate" => $this->employment_date, |             "employmentDate" => $this->employment_date->toDateString(), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ class UserResource extends JsonResource | |||||||
|             "id" => $this->id, |             "id" => $this->id, | ||||||
|             "name" => $this->fullName, |             "name" => $this->fullName, | ||||||
|             "email" => $this->email, |             "email" => $this->email, | ||||||
|             "role" => "Human Resources Manager", |             "role" => $this->role->label(), | ||||||
|             "avatar" => asset($this->avatar), |             "avatar" => asset($this->avatar), | ||||||
|             "deleted" => $this->trashed(), |             "deleted" => $this->trashed(), | ||||||
|             "employmentForm" => $this->employment_form->label(), |             "employmentForm" => $this->employment_form->label(), | ||||||
|   | |||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Resources; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\Resources\Json\JsonResource; | ||||||
|  |  | ||||||
|  | class VacationRequestActivityResource extends JsonResource | ||||||
|  | { | ||||||
|  |     public static $wrap = null; | ||||||
|  |  | ||||||
|  |     public function toArray($request): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "date" => $this->created_at->toDisplayString(), | ||||||
|  |             "who" => $this->user ? $this->user->fullName : __("System"), | ||||||
|  |             "to" => $this->to->label(), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Resources; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\Resources\Json\JsonResource; | ||||||
|  |  | ||||||
|  | class VacationRequestResource extends JsonResource | ||||||
|  | { | ||||||
|  |     public static $wrap = null; | ||||||
|  |  | ||||||
|  |     public function toArray($request): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "id" => $this->id, | ||||||
|  |             "name" => $this->name, | ||||||
|  |             "user" => new UserResource($this->user), | ||||||
|  |             "type" => $this->type->label(), | ||||||
|  |             "state" => $this->state->label(), | ||||||
|  |             "from" => $this->from->toDisplayString(), | ||||||
|  |             "to" => $this->to->toDisplayString(), | ||||||
|  |             "comment" => $this->comment, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -12,6 +12,7 @@ | |||||||
|         "guzzlehttp/guzzle": "^7.0.1", |         "guzzlehttp/guzzle": "^7.0.1", | ||||||
|         "inertiajs/inertia-laravel": "^0.5.1", |         "inertiajs/inertia-laravel": "^0.5.1", | ||||||
|         "laravel/framework": "^8.75", |         "laravel/framework": "^8.75", | ||||||
|  |         "laravel/sanctum": "^2.14", | ||||||
|         "laravel/socialite": "^5.2", |         "laravel/socialite": "^5.2", | ||||||
|         "laravel/telescope": "^4.6", |         "laravel/telescope": "^4.6", | ||||||
|         "laravel/tinker": "^2.5", |         "laravel/tinker": "^2.5", | ||||||
|   | |||||||
							
								
								
									
										429
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										429
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										20
									
								
								config/sanctum.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								config/sanctum.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Illuminate\Cookie\Middleware\EncryptCookies; | ||||||
|  | use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; | ||||||
|  |  | ||||||
|  | return [ | ||||||
|  |     "stateful" => explode(",", env("SANCTUM_STATEFUL_DOMAINS", sprintf( | ||||||
|  |         "%s%s", | ||||||
|  |         "localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1", | ||||||
|  |         env("APP_URL") ? "," . parse_url(env("APP_URL"), PHP_URL_HOST) : "", | ||||||
|  |     ))), | ||||||
|  |     "guard" => ["web"], | ||||||
|  |     "expiration" => null, | ||||||
|  |     "middleware" => [ | ||||||
|  |         "verify_csrf_token" => VerifyCsrfToken::class, | ||||||
|  |         "encrypt_cookies" => EncryptCookies::class, | ||||||
|  |     ], | ||||||
|  | ]; | ||||||
							
								
								
									
										63
									
								
								config/vacation_types.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								config/vacation_types.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  |  | ||||||
|  | return [ | ||||||
|  |     VacationType::VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => true, | ||||||
|  |     ], | ||||||
|  |     VacationType::VACATION_ON_REQUEST->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => true, | ||||||
|  |     ], | ||||||
|  |     VacationType::TIME_IN_LIEU->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::SICK_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::UNPAID_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::SPECIAL_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::CHILDCARE_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => false, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::TRAINING_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  |     VacationType::VOLUNTEERING_VACATION->value => [ | ||||||
|  |         VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_BILLABLE => true, | ||||||
|  |         VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, | ||||||
|  |     ], | ||||||
|  | ]; | ||||||
| @@ -7,7 +7,8 @@ namespace Database\Factories; | |||||||
| use Illuminate\Database\Eloquent\Factories\Factory; | use Illuminate\Database\Eloquent\Factories\Factory; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Toby\Domain\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
| class UserFactory extends Factory | class UserFactory extends Factory | ||||||
| @@ -21,6 +22,7 @@ class UserFactory extends Factory | |||||||
|             "last_name" => $this->faker->lastName(), |             "last_name" => $this->faker->lastName(), | ||||||
|             "email" => $this->faker->unique()->safeEmail(), |             "email" => $this->faker->unique()->safeEmail(), | ||||||
|             "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), |             "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), | ||||||
|  |             "role" => Role::EMPLOYEE, | ||||||
|             "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), |             "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), | ||||||
|             "remember_token" => Str::random(10), |             "remember_token" => Str::random(10), | ||||||
|         ]; |         ]; | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								database/factories/VacationRequestFactory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								database/factories/VacationRequestFactory.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Database\Factories; | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Eloquent\Factories\Factory; | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRequestFactory extends Factory | ||||||
|  | { | ||||||
|  |     protected $model = VacationRequest::class; | ||||||
|  |  | ||||||
|  |     public function definition(): array | ||||||
|  |     { | ||||||
|  |         $number = $this->faker->numberBetween(1, 20); | ||||||
|  |         $year = $this->faker->year; | ||||||
|  |  | ||||||
|  |         return [ | ||||||
|  |             "user_id" => User::factory(), | ||||||
|  |             "name" => "{$number}/{$year}", | ||||||
|  |             "type" => $this->faker->randomElement(VacationType::cases()), | ||||||
|  |             "state" => $this->faker->randomElement(VacationRequestState::cases()), | ||||||
|  |             "from" => $this->faker->date, | ||||||
|  |             "to" => $this->faker->date, | ||||||
|  |             "comment" => $this->faker->boolean ? $this->faker->paragraph() : null, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ declare(strict_types=1); | |||||||
| use Illuminate\Database\Migrations\Migration; | use Illuminate\Database\Migrations\Migration; | ||||||
| use Illuminate\Database\Schema\Blueprint; | use Illuminate\Database\Schema\Blueprint; | ||||||
| use Illuminate\Support\Facades\Schema; | use Illuminate\Support\Facades\Schema; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  |  | ||||||
| return new class() extends Migration { | return new class() extends Migration { | ||||||
|     public function up(): void |     public function up(): void | ||||||
| @@ -15,6 +16,7 @@ return new class() extends Migration { | |||||||
|             $table->string("last_name"); |             $table->string("last_name"); | ||||||
|             $table->string("email")->unique(); |             $table->string("email")->unique(); | ||||||
|             $table->string("avatar")->nullable(); |             $table->string("avatar")->nullable(); | ||||||
|  |             $table->string("role")->default(Role::EMPLOYEE->value); | ||||||
|             $table->string("employment_form"); |             $table->string("employment_form"); | ||||||
|             $table->date("employment_date"); |             $table->date("employment_date"); | ||||||
|             $table->rememberToken(); |             $table->rememberToken(); | ||||||
|   | |||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | return new class() extends Migration { | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::create("vacation_requests", function (Blueprint $table): void { | ||||||
|  |             $table->id(); | ||||||
|  |             $table->string("name"); | ||||||
|  |             $table->foreignIdFor(User::class)->constrained()->cascadeOnDelete(); | ||||||
|  |             $table->string("type"); | ||||||
|  |             $table->string("state")->nullable(); | ||||||
|  |             $table->date("from"); | ||||||
|  |             $table->date("to"); | ||||||
|  |             $table->text("comment")->nullable(); | ||||||
|  |             $table->timestamps(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::dropIfExists("vacation_requests"); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | return new class() extends Migration { | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::create("vacation_request_activities", function (Blueprint $table): void { | ||||||
|  |             $table->id(); | ||||||
|  |             $table->foreignIdFor(VacationRequest::class)->constrained()->cascadeOnDelete(); | ||||||
|  |             $table->foreignIdFor(User::class)->nullable()->constrained()->cascadeOnDelete(); | ||||||
|  |             $table->string("from")->nullable(); | ||||||
|  |             $table->string("to"); | ||||||
|  |             $table->timestamps(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::dropIfExists("vacation_request_activities"); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -11,6 +11,7 @@ use Toby\Domain\PolishHolidaysRetriever; | |||||||
| use Toby\Eloquent\Helpers\UserAvatarGenerator; | use Toby\Eloquent\Helpers\UserAvatarGenerator; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationLimit; | use Toby\Eloquent\Models\VacationLimit; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  |  | ||||||
| class DatabaseSeeder extends Seeder | class DatabaseSeeder extends Seeder | ||||||
| @@ -24,11 +25,14 @@ class DatabaseSeeder extends Seeder | |||||||
|     { |     { | ||||||
|         User::unsetEventDispatcher(); |         User::unsetEventDispatcher(); | ||||||
|         YearPeriod::unsetEventDispatcher(); |         YearPeriod::unsetEventDispatcher(); | ||||||
|  |         VacationRequest::unsetEventDispatcher(); | ||||||
|  |  | ||||||
|         User::factory(9)->create(); |         User::factory(9)->create(); | ||||||
|         User::factory([ |         User::factory([ | ||||||
|             "email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"), |             "email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"), | ||||||
|         ])->create(); |         ]) | ||||||
|  |             ->hasVacationRequests(5) | ||||||
|  |             ->create(); | ||||||
|  |  | ||||||
|         $users = User::all(); |         $users = User::all(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2681
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2681
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -22,12 +22,14 @@ | |||||||
|         "@tailwindcss/typography": "^0.5.0", |         "@tailwindcss/typography": "^0.5.0", | ||||||
|         "@vue/compiler-sfc": "^3.2.26", |         "@vue/compiler-sfc": "^3.2.26", | ||||||
|         "autoprefixer": "^10.4.2", |         "autoprefixer": "^10.4.2", | ||||||
|  |         "echarts": "^5.2.2", | ||||||
|         "flatpickr": "^4.6.9", |         "flatpickr": "^4.6.9", | ||||||
|         "laravel-mix": "^6.0.6", |         "laravel-mix": "^6.0.6", | ||||||
|         "lodash": "^4.17.21", |         "lodash": "^4.17.21", | ||||||
|         "postcss": "^8.4.5", |         "postcss": "^8.4.5", | ||||||
|         "tailwindcss": "^3.0.13", |         "tailwindcss": "^3.0.13", | ||||||
|         "vue": "^3.2.26", |         "vue": "^3.2.26", | ||||||
|  |         "vue-echarts": "^6.0.2", | ||||||
|         "vue-flatpickr-component": "^9.0.5", |         "vue-flatpickr-component": "^9.0.5", | ||||||
|         "vue-loader": "^17.0.0" |         "vue-loader": "^17.0.0" | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -47,3 +47,24 @@ | |||||||
|     -webkit-box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA; |     -webkit-box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA; | ||||||
|     box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA; |     box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ::-webkit-scrollbar { | ||||||
|  |     width: 8px; | ||||||
|  |     height: 8px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ::-webkit-scrollbar-track { | ||||||
|  |     border-radius: 100vh; | ||||||
|  |     background: transparent; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ::-webkit-scrollbar-thumb { | ||||||
|  |     border-radius: 8px; | ||||||
|  |     background: #dadce0; | ||||||
|  |     border: 4px solid transparent; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ::-webkit-scrollbar-thumb:hover { | ||||||
|  |     background: #dadce0; | ||||||
|  | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,106 +1,106 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Dodaj dzień wolny" /> |   <InertiaHead title="Dodaj dzień wolny" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|             <h2 class="text-lg leading-6 font-medium text-gray-900"> |       <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Dodaj dzień wolny |         Dodaj dzień wolny | ||||||
|             </h2> |       </h2> | ||||||
|             <p class="mt-1 text-sm text-gray-500"> |       <p class="mt-1 text-sm text-gray-500"> | ||||||
|                 Użytkownik nie będzie miał możliwości wzięcia urlopu w dzień wolny. |         Użytkownik nie będzie miał możliwości wzięcia urlopu w dzień wolny. | ||||||
|             </p> |       </p> | ||||||
|         </div> |  | ||||||
|         <form |  | ||||||
|             class="border-t border-gray-200 px-6" |  | ||||||
|             @submit.prevent="createHoliday" |  | ||||||
|         > |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="name" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Nazwa |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="name" |  | ||||||
|                         v-model="form.name" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.name" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.name }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="date" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Data |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <FlatPickr |  | ||||||
|                         id="date" |  | ||||||
|                         v-model="form.date" |  | ||||||
|                         placeholder="Wybierz datę" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }" |  | ||||||
|                     /> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.date" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.date }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="flex justify-end py-3"> |  | ||||||
|                 <div class="space-x-3"> |  | ||||||
|                     <InertiaLink |  | ||||||
|                         href="/holidays" |  | ||||||
|                         class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Anuluj |  | ||||||
|                     </InertiaLink> |  | ||||||
|                     <button |  | ||||||
|                         type="submit" |  | ||||||
|                         :disabled="form.processing" |  | ||||||
|                         class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Zapisz |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </form> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <form | ||||||
|  |       class="border-t border-gray-200 px-6" | ||||||
|  |       @submit.prevent="createHoliday" | ||||||
|  |     > | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="name" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Nazwa | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="name" | ||||||
|  |             v-model="form.name" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.name" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.name }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="date" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="date" | ||||||
|  |             v-model="form.date" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.date" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.date }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="flex justify-end py-3"> | ||||||
|  |         <div class="space-x-3"> | ||||||
|  |           <InertiaLink | ||||||
|  |             href="/holidays" | ||||||
|  |             class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Anuluj | ||||||
|  |           </InertiaLink> | ||||||
|  |           <button | ||||||
|  |             type="submit" | ||||||
|  |             :disabled="form.processing" | ||||||
|  |             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Zapisz | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { useForm } from '@inertiajs/inertia-vue3'; | import { useForm } from '@inertiajs/inertia-vue3' | ||||||
| import FlatPickr from 'vue-flatpickr-component'; | import FlatPickr from 'vue-flatpickr-component' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'HolidayCreate', |   name: 'HolidayCreate', | ||||||
|     components: { |   components: { | ||||||
|         FlatPickr, |     FlatPickr, | ||||||
|     }, |   }, | ||||||
|     setup() { |   setup() { | ||||||
|         const form = useForm({ |     const form = useForm({ | ||||||
|             name: null, |       name: null, | ||||||
|             date: null, |       date: null, | ||||||
|         }); |     }) | ||||||
|  |  | ||||||
|         return { form }; |     return { form } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     createHoliday() { | ||||||
|  |       this.form.post('/holidays') | ||||||
|     }, |     }, | ||||||
|     methods: { |   }, | ||||||
|         createHoliday() { | } | ||||||
|             this.form.post('/holidays'); |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,113 +1,113 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Edytuj dzień wolny" /> |   <InertiaHead title="Edytuj dzień wolny" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|             <h2 class="text-lg leading-6 font-medium text-gray-900"> |       <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Edytuj dzień wolny |         Edytuj dzień wolny | ||||||
|             </h2> |       </h2> | ||||||
|             <p class="mt-1 text-sm text-gray-500"> |       <p class="mt-1 text-sm text-gray-500"> | ||||||
|                 Użytkownik nie będzie miał możliwości wzięcia urlopu w dzień wolny. |         Użytkownik nie będzie miał możliwości wzięcia urlopu w dzień wolny. | ||||||
|             </p> |       </p> | ||||||
|         </div> |  | ||||||
|         <form |  | ||||||
|             class="border-t border-gray-200 px-6" |  | ||||||
|             @submit.prevent="editHoliday" |  | ||||||
|         > |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="name" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Nazwa |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="name" |  | ||||||
|                         v-model="form.name" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.name" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.name }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="date" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Data |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <FlatPickr |  | ||||||
|                         id="date" |  | ||||||
|                         v-model="form.date" |  | ||||||
|                         placeholder="Wybierz datę" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }" |  | ||||||
|                     /> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.date" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.date }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="flex justify-end py-3"> |  | ||||||
|                 <div class="space-x-3"> |  | ||||||
|                     <InertiaLink |  | ||||||
|                         href="/holidays" |  | ||||||
|                         class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Anuluj |  | ||||||
|                     </InertiaLink> |  | ||||||
|                     <button |  | ||||||
|                         type="submit" |  | ||||||
|                         :disabled="form.processing" |  | ||||||
|                         class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Zapisz |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </form> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <form | ||||||
|  |       class="border-t border-gray-200 px-6" | ||||||
|  |       @submit.prevent="editHoliday" | ||||||
|  |     > | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="name" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Nazwa | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="name" | ||||||
|  |             v-model="form.name" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.name" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.name }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="date" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="date" | ||||||
|  |             v-model="form.date" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.date" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.date }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="flex justify-end py-3"> | ||||||
|  |         <div class="space-x-3"> | ||||||
|  |           <InertiaLink | ||||||
|  |             href="/holidays" | ||||||
|  |             class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Anuluj | ||||||
|  |           </InertiaLink> | ||||||
|  |           <button | ||||||
|  |             type="submit" | ||||||
|  |             :disabled="form.processing" | ||||||
|  |             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Zapisz | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { useForm } from '@inertiajs/inertia-vue3'; | import { useForm } from '@inertiajs/inertia-vue3' | ||||||
| import FlatPickr from 'vue-flatpickr-component'; | import FlatPickr from 'vue-flatpickr-component' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'HolidayEdit', |   name: 'HolidayEdit', | ||||||
|     components: { |   components: { | ||||||
|         FlatPickr, |     FlatPickr, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     holiday: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |   }, | ||||||
|         holiday: { |   setup(props) { | ||||||
|             type: Object, |     const form = useForm({ | ||||||
|             default: () => null, |       name: props.holiday.name, | ||||||
|         }, |       date: props.holiday.date, | ||||||
|     }, |     }) | ||||||
|     setup(props) { |  | ||||||
|         const form = useForm({ |  | ||||||
|             name: props.holiday.name, |  | ||||||
|             date: props.holiday.date, |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         return { form }; |     return { form } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     editHoliday() { | ||||||
|  |       this.form | ||||||
|  |         .put(`/holidays/${this.holiday.id}`) | ||||||
|     }, |     }, | ||||||
|     methods: { |   }, | ||||||
|         editHoliday() { | } | ||||||
|             this.form |  | ||||||
|                 .put(`/holidays/${this.holiday.id}`); |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,167 +1,167 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Dni wolne od pracy" /> |   <InertiaHead title="Dni wolne od pracy" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="flex justify-between items-center p-4 sm:px-6"> |     <div class="flex justify-between items-center p-4 sm:px-6"> | ||||||
|             <div> |       <div> | ||||||
|                 <h2 class="text-lg leading-6 font-medium text-gray-900"> |         <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                     Dni wolne od pracy |           Dni wolne od pracy | ||||||
|                 </h2> |         </h2> | ||||||
|                 <p class="mt-1 text-sm text-gray-500"> |         <p class="mt-1 text-sm text-gray-500"> | ||||||
|                     Lista dni wolnych od pracy w danym roku |           Lista dni wolnych od pracy w danym roku | ||||||
|                 </p> |         </p> | ||||||
|             </div> |       </div> | ||||||
|             <div> |       <div> | ||||||
|                 <InertiaLink |         <InertiaLink | ||||||
|                     href="holidays/create" |           href="holidays/create" | ||||||
|                     class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |           class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|                 > |         > | ||||||
|                     Dodaj dzień wolny |           Dodaj dzień wolny | ||||||
|                 </InertiaLink> |         </InertiaLink> | ||||||
|             </div> |       </div> | ||||||
|         </div> |  | ||||||
|         <div class="border-t border-gray-200"> |  | ||||||
|             <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> |  | ||||||
|                 <table class="min-w-full divide-y divide-gray-200"> |  | ||||||
|                     <thead class="bg-gray-100"> |  | ||||||
|                         <tr> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Nazwa |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Data |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Dzień tygodnia |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             /> |  | ||||||
|                         </tr> |  | ||||||
|                     </thead> |  | ||||||
|                     <tbody class="bg-white divide-y divide-gray-100"> |  | ||||||
|                         <tr |  | ||||||
|                             v-for="holiday in holidays.data" |  | ||||||
|                             :key="holiday.id" |  | ||||||
|                             class="hover:bg-blumilk-25" |  | ||||||
|                         > |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 font-semibold capitalize"> |  | ||||||
|                                 {{ holiday.name }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 {{ holiday.displayDate }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 {{ holiday.dayOfWeek }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 text-right"> |  | ||||||
|                                 <Menu |  | ||||||
|                                     as="div" |  | ||||||
|                                     class="relative inline-block text-left" |  | ||||||
|                                 > |  | ||||||
|                                     <MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"> |  | ||||||
|                                         <DotsVerticalIcon |  | ||||||
|                                             class="h-5 w-5" |  | ||||||
|                                             aria-hidden="true" |  | ||||||
|                                         /> |  | ||||||
|                                     </MenuButton> |  | ||||||
|  |  | ||||||
|                                     <transition |  | ||||||
|                                         enter-active-class="transition ease-out duration-100" |  | ||||||
|                                         enter-from-class="transform opacity-0 scale-95" |  | ||||||
|                                         enter-to-class="transform opacity-100 scale-100" |  | ||||||
|                                         leave-active-class="transition ease-in duration-75" |  | ||||||
|                                         leave-from-class="transform opacity-100 scale-100" |  | ||||||
|                                         leave-to-class="transform opacity-0 scale-95" |  | ||||||
|                                     > |  | ||||||
|                                         <MenuItems class="origin-top-right absolute right-0 mt-2 w-56 z-10 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"> |  | ||||||
|                                             <div class="py-1"> |  | ||||||
|                                                 <MenuItem |  | ||||||
|                                                     v-slot="{ active }" |  | ||||||
|                                                     class="flex" |  | ||||||
|                                                 > |  | ||||||
|                                                     <InertiaLink |  | ||||||
|                                                         :href="`/holidays/${holiday.id}/edit`" |  | ||||||
|                                                         :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" |  | ||||||
|                                                     > |  | ||||||
|                                                         <PencilIcon |  | ||||||
|                                                             class="mr-2 h-5 w-5 text-blue-500" |  | ||||||
|                                                             aria-hidden="true" |  | ||||||
|                                                         /> Edytuj |  | ||||||
|                                                     </InertiaLink> |  | ||||||
|                                                 </MenuItem> |  | ||||||
|                                                 <MenuItem |  | ||||||
|                                                     v-slot="{ active }" |  | ||||||
|                                                     class="flex" |  | ||||||
|                                                 > |  | ||||||
|                                                     <InertiaLink |  | ||||||
|                                                         as="button" |  | ||||||
|                                                         method="delete" |  | ||||||
|                                                         :preserve-scroll="true" |  | ||||||
|                                                         :href="`/holidays/${holiday.id}`" |  | ||||||
|                                                         :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                                                     > |  | ||||||
|                                                         <TrashIcon |  | ||||||
|                                                             class="mr-2 h-5 w-5 text-red-500" |  | ||||||
|                                                             aria-hidden="true" |  | ||||||
|                                                         /> Usuń |  | ||||||
|                                                     </InertiaLink> |  | ||||||
|                                                 </MenuItem> |  | ||||||
|                                             </div> |  | ||||||
|                                         </MenuItems> |  | ||||||
|                                     </transition> |  | ||||||
|                                 </Menu> |  | ||||||
|                             </td> |  | ||||||
|                         </tr> |  | ||||||
|                         <tr |  | ||||||
|                             v-if="!holidays.data.length" |  | ||||||
|                         > |  | ||||||
|                             <td |  | ||||||
|                                 colspan="100%" |  | ||||||
|                                 class="text-center py-4 text-xl leading-5 text-gray-700" |  | ||||||
|                             > |  | ||||||
|                                 Brak danych |  | ||||||
|                             </td> |  | ||||||
|                         </tr> |  | ||||||
|                     </tbody> |  | ||||||
|                 </table> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="border-t border-gray-200"> | ||||||
|  |       <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> | ||||||
|  |         <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |           <thead class="bg-gray-100"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Nazwa | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Data | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Dzień tygodnia | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               /> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|  |             <tr | ||||||
|  |               v-for="holiday in holidays.data" | ||||||
|  |               :key="holiday.id" | ||||||
|  |               class="hover:bg-blumilk-25" | ||||||
|  |             > | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 font-semibold capitalize"> | ||||||
|  |                 {{ holiday.name }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 {{ holiday.displayDate }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 {{ holiday.dayOfWeek }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 text-right"> | ||||||
|  |                 <Menu | ||||||
|  |                   as="div" | ||||||
|  |                   class="relative inline-block text-left" | ||||||
|  |                 > | ||||||
|  |                   <MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"> | ||||||
|  |                     <DotsVerticalIcon | ||||||
|  |                       class="h-5 w-5" | ||||||
|  |                       aria-hidden="true" | ||||||
|  |                     /> | ||||||
|  |                   </MenuButton> | ||||||
|  |  | ||||||
|  |                   <transition | ||||||
|  |                     enter-active-class="transition ease-out duration-100" | ||||||
|  |                     enter-from-class="transform opacity-0 scale-95" | ||||||
|  |                     enter-to-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-active-class="transition ease-in duration-75" | ||||||
|  |                     leave-from-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-to-class="transform opacity-0 scale-95" | ||||||
|  |                   > | ||||||
|  |                     <MenuItems class="origin-top-right absolute right-0 mt-2 w-56 z-10 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"> | ||||||
|  |                       <div class="py-1"> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             :href="`/holidays/${holiday.id}/edit`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <PencilIcon | ||||||
|  |                               class="mr-2 h-5 w-5 text-blue-500" | ||||||
|  |                               aria-hidden="true" | ||||||
|  |                             /> Edytuj | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             as="button" | ||||||
|  |                             method="delete" | ||||||
|  |                             :preserve-scroll="true" | ||||||
|  |                             :href="`/holidays/${holiday.id}`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <TrashIcon | ||||||
|  |                               class="mr-2 h-5 w-5 text-red-500" | ||||||
|  |                               aria-hidden="true" | ||||||
|  |                             /> Usuń | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                       </div> | ||||||
|  |                     </MenuItems> | ||||||
|  |                   </transition> | ||||||
|  |                 </Menu> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr | ||||||
|  |               v-if="!holidays.data.length" | ||||||
|  |             > | ||||||
|  |               <td | ||||||
|  |                 colspan="100%" | ||||||
|  |                 class="text-center py-4 text-xl leading-5 text-gray-700" | ||||||
|  |               > | ||||||
|  |                 Brak danych | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid'; | import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid' | ||||||
| import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'; | import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'HolidayINdex', |   name: 'HolidayINdex', | ||||||
|     components: { |   components: { | ||||||
|         DotsVerticalIcon, |     DotsVerticalIcon, | ||||||
|         PencilIcon, |     PencilIcon, | ||||||
|         TrashIcon, |     TrashIcon, | ||||||
|         Menu, |     Menu, | ||||||
|         MenuButton, |     MenuButton, | ||||||
|         MenuItem, |     MenuItem, | ||||||
|         MenuItems, |     MenuItems, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     holidays: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |   }, | ||||||
|         holidays: { |   setup() { | ||||||
|             type: Object, |     return {} | ||||||
|             default: () => null, |   }, | ||||||
|         }, | } | ||||||
|     }, |  | ||||||
|     setup() { |  | ||||||
|         return {}; |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,86 +1,86 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Zaloguj się" /> |   <InertiaHead title="Zaloguj się" /> | ||||||
|     <transition |   <transition | ||||||
|         enter-active-class="transform ease-out duration-300 transition" |     enter-active-class="transform ease-out duration-300 transition" | ||||||
|         enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" |     enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" | ||||||
|         enter-to-class="translate-y-0 opacity-100 sm:translate-x-0" |     enter-to-class="translate-y-0 opacity-100 sm:translate-x-0" | ||||||
|         leave-active-class="transition ease-in duration-100" |     leave-active-class="transition ease-in duration-100" | ||||||
|         leave-from-class="opacity-100" |     leave-from-class="opacity-100" | ||||||
|         leave-to-class="opacity-0" |     leave-to-class="opacity-0" | ||||||
|     > |   > | ||||||
|         <div |  | ||||||
|             v-if="errors.oauth" |  | ||||||
|             class="absolute inset-x-2 top-2 sm:mx-auto sm:w-full sm:max-w-md bg-red-500 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden" |  | ||||||
|         > |  | ||||||
|             <div class="p-4"> |  | ||||||
|                 <div class="flex items-center"> |  | ||||||
|                     <div class="w-0 flex-1 flex justify-between"> |  | ||||||
|                         <ExclamationIcon class="h-5 w-5 text-white" /> |  | ||||||
|                         <p class="w-0 flex-1 text-sm font-medium text-white"> |  | ||||||
|                             {{ errors.oauth }} |  | ||||||
|                         </p> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="ml-4 flex-shrink-0 flex"> |  | ||||||
|                         <button |  | ||||||
|                             class="bg-red-500 rounded-md inline-flex text-red-100 hover:text-red-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600" |  | ||||||
|                             @click="delete errors.oauth" |  | ||||||
|                         > |  | ||||||
|                             <span class="sr-only">Close</span> |  | ||||||
|                             <XIcon |  | ||||||
|                                 class="h-5 w-5" |  | ||||||
|                                 aria-hidden="true" |  | ||||||
|                             /> |  | ||||||
|                         </button> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </transition> |  | ||||||
|     <div |     <div | ||||||
|         class="sm:mx-auto sm:w-full sm:max-w-md text-white space-y-4 flex flex-col items-center rounded-lg px-4 py-8" |       v-if="errors.oauth" | ||||||
|         dusk="login-link" |       class="absolute inset-x-2 top-2 sm:mx-auto sm:w-full sm:max-w-md bg-red-500 shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden" | ||||||
|     > |     > | ||||||
|         <img |       <div class="p-4"> | ||||||
|             class="mx-auto h-50 w-auto" |         <div class="flex items-center"> | ||||||
|             src="img/logo.png" |           <div class="w-0 flex-1 flex justify-between"> | ||||||
|             alt="Blumilk" |             <ExclamationIcon class="h-5 w-5 text-white" /> | ||||||
|         > |             <p class="w-0 flex-1 text-sm font-medium text-white"> | ||||||
|         <a |               {{ errors.oauth }} | ||||||
|             href="/login/google/start" |             </p> | ||||||
|             class="inline-flex justify-center py-2 px-6 rounded-md shadow-sm bg-blumilk-500 text-md font-medium text-white hover:bg-blumilk-700" |           </div> | ||||||
|         > |           <div class="ml-4 flex-shrink-0 flex"> | ||||||
|             Zaloguj się za pomocą Google |             <button | ||||||
|             <svg |               class="bg-red-500 rounded-md inline-flex text-red-100 hover:text-red-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600" | ||||||
|                 class="w-5 h-5 ml-2" |               @click="delete errors.oauth" | ||||||
|                 fill="currentColor" |  | ||||||
|                 viewBox="0 0 24 24" |  | ||||||
|             > |             > | ||||||
|                 <path |               <span class="sr-only">Close</span> | ||||||
|                     d="M6 12C6 15.3137 8.68629 18 12 18C14.6124 18 16.8349 16.3304 17.6586 14H12V10H21.8047V14H21.8C20.8734 18.5645 16.8379 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.445 2 18.4831 3.742 20.2815 6.39318L17.0039 8.68815C15.9296 7.06812 14.0895 6 12 6C8.68629 6 6 8.68629 6 12Z" |               <XIcon | ||||||
|                     fill="currentColor" |                 class="h-5 w-5" | ||||||
|                 /> |                 aria-hidden="true" | ||||||
|             </svg> |               /> | ||||||
|         </a> |             </button> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |   </transition> | ||||||
|  |   <div | ||||||
|  |     class="sm:mx-auto sm:w-full sm:max-w-md text-white space-y-4 flex flex-col items-center rounded-lg px-4 py-8" | ||||||
|  |     dusk="login-link" | ||||||
|  |   > | ||||||
|  |     <img | ||||||
|  |       class="mx-auto h-50 w-auto" | ||||||
|  |       src="img/logo.png" | ||||||
|  |       alt="Blumilk" | ||||||
|  |     > | ||||||
|  |     <a | ||||||
|  |       href="/login/google/start" | ||||||
|  |       class="inline-flex justify-center py-2 px-6 rounded-md shadow-sm bg-blumilk-500 text-md font-medium text-white hover:bg-blumilk-700" | ||||||
|  |     > | ||||||
|  |       Zaloguj się za pomocą Google | ||||||
|  |       <svg | ||||||
|  |         class="w-5 h-5 ml-2" | ||||||
|  |         fill="currentColor" | ||||||
|  |         viewBox="0 0 24 24" | ||||||
|  |       > | ||||||
|  |         <path | ||||||
|  |           d="M6 12C6 15.3137 8.68629 18 12 18C14.6124 18 16.8349 16.3304 17.6586 14H12V10H21.8047V14H21.8C20.8734 18.5645 16.8379 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C15.445 2 18.4831 3.742 20.2815 6.39318L17.0039 8.68815C15.9296 7.06812 14.0895 6 12 6C8.68629 6 6 8.68629 6 12Z" | ||||||
|  |           fill="currentColor" | ||||||
|  |         /> | ||||||
|  |       </svg> | ||||||
|  |     </a> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import GuestLayout from '@/Shared/Layout/GuestLayout'; | import GuestLayout from '@/Shared/Layout/GuestLayout' | ||||||
| import {XIcon} from '@heroicons/vue/solid'; | import {XIcon} from '@heroicons/vue/solid' | ||||||
| import {ExclamationIcon} from '@heroicons/vue/solid'; | import {ExclamationIcon} from '@heroicons/vue/solid' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'Login', |   name: 'LoginPage', | ||||||
|     components: { |   components: { | ||||||
|         XIcon, |     XIcon, | ||||||
|         ExclamationIcon, |     ExclamationIcon, | ||||||
|  |   }, | ||||||
|  |   layout: GuestLayout, | ||||||
|  |   props: { | ||||||
|  |     errors: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => ({oauth: null}), | ||||||
|     }, |     }, | ||||||
|     layout: GuestLayout, |   }, | ||||||
|     props: { | } | ||||||
|         errors: { |  | ||||||
|             type: Object, |  | ||||||
|             default: () => ({oauth: null}), |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,230 +1,290 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Dodawanie użytkownika" /> |   <InertiaHead title="Dodawanie użytkownika" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|             <h2 class="text-lg leading-6 font-medium text-gray-900"> |       <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Dodaj użytkownika |         Dodaj użytkownika | ||||||
|             </h2> |       </h2> | ||||||
|             <p class="mt-1 text-sm text-gray-500"> |       <p class="mt-1 text-sm text-gray-500"> | ||||||
|                 Tylko dodani użytkownicy będą mogli zalogować się do aplikacji. |         Tylko dodani użytkownicy będą mogli zalogować się do aplikacji. | ||||||
|             </p> |       </p> | ||||||
|         </div> |  | ||||||
|         <form |  | ||||||
|             class="border-t border-gray-200 px-6" |  | ||||||
|             @submit.prevent="createUser" |  | ||||||
|         > |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="firstName" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Imię |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="firstName" |  | ||||||
|                         v-model="form.firstName" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.firstName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.firstName }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.firstName" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.firstName }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="lastName" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Nazwisko |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="lastName" |  | ||||||
|                         v-model="form.lastName" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.lastName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.lastName }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.lastName" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.lastName }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="email" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Adres e-mail |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="email" |  | ||||||
|                         v-model="form.email" |  | ||||||
|                         type="email" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.email" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.email }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <Listbox |  | ||||||
|                 v-model="form.employmentForm" |  | ||||||
|                 as="div" |  | ||||||
|                 class="sm:grid sm:grid-cols-3 py-4 items-center" |  | ||||||
|             > |  | ||||||
|                 <ListboxLabel class="block text-sm font-medium text-gray-700"> |  | ||||||
|                     Forma zatrudnienia |  | ||||||
|                 </ListboxLabel> |  | ||||||
|                 <div class="mt-1 relative sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <ListboxButton |  | ||||||
|                         class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" |  | ||||||
|                     > |  | ||||||
|                         <span class="block truncate">{{ form.employmentForm.label }}</span> |  | ||||||
|                         <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> |  | ||||||
|                             <SelectorIcon class="h-5 w-5 text-gray-400" /> |  | ||||||
|                         </span> |  | ||||||
|                     </ListboxButton> |  | ||||||
|  |  | ||||||
|                     <transition |  | ||||||
|                         leave-active-class="transition ease-in duration-100" |  | ||||||
|                         leave-from-class="opacity-100" |  | ||||||
|                         leave-to-class="opacity-0" |  | ||||||
|                     > |  | ||||||
|                         <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> |  | ||||||
|                             <ListboxOption |  | ||||||
|                                 v-for="employmentForm in employmentForms" |  | ||||||
|                                 :key="employmentForm.value" |  | ||||||
|                                 v-slot="{ active, selected }" |  | ||||||
|                                 as="template" |  | ||||||
|                                 :value="employmentForm" |  | ||||||
|                             > |  | ||||||
|                                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> |  | ||||||
|                                     <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> |  | ||||||
|                                         {{ employmentForm.label }} |  | ||||||
|                                     </span> |  | ||||||
|  |  | ||||||
|                                     <span |  | ||||||
|                                         v-if="selected" |  | ||||||
|                                         :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" |  | ||||||
|                                     > |  | ||||||
|                                         <CheckIcon class="h-5 w-5" /> |  | ||||||
|                                     </span> |  | ||||||
|                                 </li> |  | ||||||
|                             </ListboxOption> |  | ||||||
|                         </ListboxOptions> |  | ||||||
|                     </transition> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.employmentForm" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.employmentForm }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </Listbox> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="employment_date" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Data zatrudnienia |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <FlatPickr |  | ||||||
|                         id="employment_date" |  | ||||||
|                         v-model="form.employmentDate" |  | ||||||
|                         placeholder="Wybierz datę" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }" |  | ||||||
|                     /> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.employmentDate" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.employmentDate }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="flex justify-end py-3"> |  | ||||||
|                 <div class="space-x-3"> |  | ||||||
|                     <InertiaLink |  | ||||||
|                         href="/users" |  | ||||||
|                         class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Anuluj |  | ||||||
|                     </InertiaLink> |  | ||||||
|                     <button |  | ||||||
|                         type="submit" |  | ||||||
|                         :disabled="form.processing" |  | ||||||
|                         class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Zapisz |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </form> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <form | ||||||
|  |       class="border-t border-gray-200 px-6" | ||||||
|  |       @submit.prevent="createUser" | ||||||
|  |     > | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="firstName" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Imię | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="firstName" | ||||||
|  |             v-model="form.firstName" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.firstName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.firstName }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.firstName" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.firstName }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="lastName" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Nazwisko | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="lastName" | ||||||
|  |             v-model="form.lastName" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.lastName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.lastName }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.lastName" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.lastName }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="email" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Adres e-mail | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="email" | ||||||
|  |             v-model="form.email" | ||||||
|  |             type="email" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.email" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.email }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <Listbox | ||||||
|  |         v-model="form.role" | ||||||
|  |         as="div" | ||||||
|  |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|  |       > | ||||||
|  |         <ListboxLabel class="block text-sm font-medium text-gray-700"> | ||||||
|  |           Rola | ||||||
|  |         </ListboxLabel> | ||||||
|  |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|  |           <ListboxButton | ||||||
|  |             class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" | ||||||
|  |           > | ||||||
|  |             <span class="block truncate">{{ form.role.label }}</span> | ||||||
|  |             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||||
|  |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |             </span> | ||||||
|  |           </ListboxButton> | ||||||
|  |           <transition | ||||||
|  |             leave-active-class="transition ease-in duration-100" | ||||||
|  |             leave-from-class="opacity-100" | ||||||
|  |             leave-to-class="opacity-0" | ||||||
|  |           > | ||||||
|  |             <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> | ||||||
|  |               <ListboxOption | ||||||
|  |                 v-for="role in roles" | ||||||
|  |                 :key="role.value" | ||||||
|  |                 v-slot="{ active, selected }" | ||||||
|  |                 as="template" | ||||||
|  |                 :value="role" | ||||||
|  |               > | ||||||
|  |                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|  |                     {{ role.label }} | ||||||
|  |                   </span> | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     v-if="selected" | ||||||
|  |                     :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                   > | ||||||
|  |                     <CheckIcon class="h-5 w-5" /> | ||||||
|  |                   </span> | ||||||
|  |                 </li> | ||||||
|  |               </ListboxOption> | ||||||
|  |             </ListboxOptions> | ||||||
|  |           </transition> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.role" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.role }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </Listbox> | ||||||
|  |       <Listbox | ||||||
|  |         v-model="form.employmentForm" | ||||||
|  |         as="div" | ||||||
|  |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|  |       > | ||||||
|  |         <ListboxLabel class="block text-sm font-medium text-gray-700"> | ||||||
|  |           Forma zatrudnienia | ||||||
|  |         </ListboxLabel> | ||||||
|  |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|  |           <ListboxButton | ||||||
|  |             class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" | ||||||
|  |           > | ||||||
|  |             <span class="block truncate">{{ form.employmentForm.label }}</span> | ||||||
|  |             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||||
|  |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |             </span> | ||||||
|  |           </ListboxButton> | ||||||
|  |  | ||||||
|  |           <transition | ||||||
|  |             leave-active-class="transition ease-in duration-100" | ||||||
|  |             leave-from-class="opacity-100" | ||||||
|  |             leave-to-class="opacity-0" | ||||||
|  |           > | ||||||
|  |             <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> | ||||||
|  |               <ListboxOption | ||||||
|  |                 v-for="employmentForm in employmentForms" | ||||||
|  |                 :key="employmentForm.value" | ||||||
|  |                 v-slot="{ active, selected }" | ||||||
|  |                 as="template" | ||||||
|  |                 :value="employmentForm" | ||||||
|  |               > | ||||||
|  |                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|  |                     {{ employmentForm.label }} | ||||||
|  |                   </span> | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     v-if="selected" | ||||||
|  |                     :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                   > | ||||||
|  |                     <CheckIcon class="h-5 w-5" /> | ||||||
|  |                   </span> | ||||||
|  |                 </li> | ||||||
|  |               </ListboxOption> | ||||||
|  |             </ListboxOptions> | ||||||
|  |           </transition> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.employmentForm" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.employmentForm }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </Listbox> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="employment_date" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data zatrudnienia | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="employment_date" | ||||||
|  |             v-model="form.employmentDate" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.employmentDate" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.employmentDate }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="flex justify-end py-3"> | ||||||
|  |         <div class="space-x-3"> | ||||||
|  |           <InertiaLink | ||||||
|  |             href="/users" | ||||||
|  |             class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Anuluj | ||||||
|  |           </InertiaLink> | ||||||
|  |           <button | ||||||
|  |             type="submit" | ||||||
|  |             :disabled="form.processing" | ||||||
|  |             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Zapisz | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { useForm } from '@inertiajs/inertia-vue3'; | import { useForm } from '@inertiajs/inertia-vue3' | ||||||
| import FlatPickr from 'vue-flatpickr-component'; | import FlatPickr from 'vue-flatpickr-component' | ||||||
| import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'; | import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue' | ||||||
| import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid'; | import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     employmentDate: 'UserCreate', |   name: 'UserCreate', | ||||||
|     components: { |   components: { | ||||||
|         FlatPickr, |     FlatPickr, | ||||||
|         Listbox, |     Listbox, | ||||||
|         ListboxButton, |     ListboxButton, | ||||||
|         ListboxLabel, |     ListboxLabel, | ||||||
|         ListboxOption, |     ListboxOption, | ||||||
|         ListboxOptions, |     ListboxOptions, | ||||||
|         CheckIcon, |     CheckIcon, | ||||||
|         SelectorIcon, |     SelectorIcon, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     employmentForms: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |     roles: { | ||||||
|         employmentForms: { |       type: Object, | ||||||
|             type: Object, |       default: () => null, | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|     }, |     }, | ||||||
|     setup(props) { |   }, | ||||||
|         const form = useForm({ |   setup(props) { | ||||||
|             firstName: null, |     const form = useForm({ | ||||||
|             lastName: null, |       firstName: null, | ||||||
|             email: null, |       lastName: null, | ||||||
|             employmentForm: props.employmentForms[0], |       email: null, | ||||||
|             employmentDate: null, |       employmentForm: props.employmentForms[0], | ||||||
|         }); |       role: props.roles[0], | ||||||
|  |       employmentDate: null, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|         return { form }; |     return { form } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     createUser() { | ||||||
|  |       this.form | ||||||
|  |         .transform(data => ({ | ||||||
|  |           ...data, | ||||||
|  |           employmentForm: data.employmentForm.value, | ||||||
|  |           role: data.role.value, | ||||||
|  |         })) | ||||||
|  |         .post('/users') | ||||||
|     }, |     }, | ||||||
|     methods: { |   }, | ||||||
|         createUser() { | } | ||||||
|             this.form |  | ||||||
|                 .transform(data => ({ |  | ||||||
|                     ...data, |  | ||||||
|                     employmentForm: data.employmentForm.value, |  | ||||||
|                 })) |  | ||||||
|                 .post('/users'); |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,234 +1,293 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Edycja użytkownika" /> |   <InertiaHead title="Edycja użytkownika" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="p-4 sm:px-6"> |     <div class="p-4 sm:px-6"> | ||||||
|             <h2 class="text-lg leading-6 font-medium text-gray-900"> |       <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                 Edytuj użytkownika |         Edytuj użytkownika | ||||||
|             </h2> |       </h2> | ||||||
|             <p class="mt-1 text-sm text-gray-500"> |       <p class="mt-1 text-sm text-gray-500"> | ||||||
|                 Edytuj dane użytkownika, takie jak e-mail czy formę zatrudnienia. |         Edytuj dane użytkownika, takie jak e-mail czy formę zatrudnienia. | ||||||
|             </p> |       </p> | ||||||
|         </div> |  | ||||||
|         <form |  | ||||||
|             class="border-t border-gray-200 px-6" |  | ||||||
|             @submit.prevent="editUser" |  | ||||||
|         > |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="firstName" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Imię |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="firstName" |  | ||||||
|                         v-model="form.firstName" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.firstName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.firstName }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.firstName" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.firstName }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="lastName" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Nazwisko |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="lastName" |  | ||||||
|                         v-model="form.lastName" |  | ||||||
|                         type="text" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.lastName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.lastName }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.lastName" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.lastName }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="email" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Adres e-mail |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <input |  | ||||||
|                         id="email" |  | ||||||
|                         v-model="form.email" |  | ||||||
|                         type="email" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }" |  | ||||||
|                     > |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.email" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.email }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <Listbox |  | ||||||
|                 v-model="form.employmentForm" |  | ||||||
|                 as="div" |  | ||||||
|                 class="sm:grid sm:grid-cols-3 py-4 items-center" |  | ||||||
|             > |  | ||||||
|                 <ListboxLabel class="block text-sm font-medium text-gray-700"> |  | ||||||
|                     Forma zatrudnienia |  | ||||||
|                 </ListboxLabel> |  | ||||||
|                 <div class="mt-1 relative sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <ListboxButton |  | ||||||
|                         class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" |  | ||||||
|                     > |  | ||||||
|                         <span class="block truncate">{{ form.employmentForm.label }}</span> |  | ||||||
|                         <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> |  | ||||||
|                             <SelectorIcon class="h-5 w-5 text-gray-400" /> |  | ||||||
|                         </span> |  | ||||||
|                     </ListboxButton> |  | ||||||
|  |  | ||||||
|                     <transition |  | ||||||
|                         leave-active-class="transition ease-in duration-100" |  | ||||||
|                         leave-from-class="opacity-100" |  | ||||||
|                         leave-to-class="opacity-0" |  | ||||||
|                     > |  | ||||||
|                         <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> |  | ||||||
|                             <ListboxOption |  | ||||||
|                                 v-for="employmentForm in employmentForms" |  | ||||||
|                                 :key="employmentForm.value" |  | ||||||
|                                 v-slot="{ active, selected }" |  | ||||||
|                                 as="template" |  | ||||||
|                                 :value="employmentForm" |  | ||||||
|                             > |  | ||||||
|                                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> |  | ||||||
|                                     <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> |  | ||||||
|                                         {{ employmentForm.label }} |  | ||||||
|                                     </span> |  | ||||||
|  |  | ||||||
|                                     <span |  | ||||||
|                                         v-if="selected" |  | ||||||
|                                         :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" |  | ||||||
|                                     > |  | ||||||
|                                         <CheckIcon class="h-5 w-5" /> |  | ||||||
|                                     </span> |  | ||||||
|                                 </li> |  | ||||||
|                             </ListboxOption> |  | ||||||
|                         </ListboxOptions> |  | ||||||
|                     </transition> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.employmentForm" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.employmentForm }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </Listbox> |  | ||||||
|             <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |  | ||||||
|                 <label |  | ||||||
|                     for="employment_date" |  | ||||||
|                     class="block text-sm font-medium text-gray-700 sm:mt-px" |  | ||||||
|                 > |  | ||||||
|                     Data zatrudnienia |  | ||||||
|                 </label> |  | ||||||
|                 <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                     <FlatPickr |  | ||||||
|                         id="employment_date" |  | ||||||
|                         v-model="form.employmentDate" |  | ||||||
|                         placeholder="Wybierz datę" |  | ||||||
|                         class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |  | ||||||
|                         :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }" |  | ||||||
|                     /> |  | ||||||
|                     <p |  | ||||||
|                         v-if="form.errors.employmentDate" |  | ||||||
|                         class="mt-2 text-sm text-red-600" |  | ||||||
|                     > |  | ||||||
|                         {{ form.errors.employmentDate }} |  | ||||||
|                     </p> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="flex justify-end py-3"> |  | ||||||
|                 <div class="space-x-3"> |  | ||||||
|                     <InertiaLink |  | ||||||
|                         href="/users" |  | ||||||
|                         class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Anuluj |  | ||||||
|                     </InertiaLink> |  | ||||||
|                     <button |  | ||||||
|                         type="submit" |  | ||||||
|                         :disabled="form.processing" |  | ||||||
|                         class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                     > |  | ||||||
|                         Zapisz |  | ||||||
|                     </button> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </form> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <form | ||||||
|  |       class="border-t border-gray-200 px-6" | ||||||
|  |       @submit.prevent="editUser" | ||||||
|  |     > | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="firstName" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Imię | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="firstName" | ||||||
|  |             v-model="form.firstName" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.firstName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.firstName }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.firstName" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.firstName }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="lastName" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Nazwisko | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="lastName" | ||||||
|  |             v-model="form.lastName" | ||||||
|  |             type="text" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.lastName, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.lastName }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.lastName" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.lastName }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="email" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Adres e-mail | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <input | ||||||
|  |             id="email" | ||||||
|  |             v-model="form.email" | ||||||
|  |             type="email" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }" | ||||||
|  |           > | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.email" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.email }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <Listbox | ||||||
|  |         v-model="form.role" | ||||||
|  |         as="div" | ||||||
|  |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|  |       > | ||||||
|  |         <ListboxLabel class="block text-sm font-medium text-gray-700"> | ||||||
|  |           Rola | ||||||
|  |         </ListboxLabel> | ||||||
|  |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|  |           <ListboxButton | ||||||
|  |             class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" | ||||||
|  |           > | ||||||
|  |             <span class="block truncate">{{ form.role.label }}</span> | ||||||
|  |             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||||
|  |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |             </span> | ||||||
|  |           </ListboxButton> | ||||||
|  |           <transition | ||||||
|  |             leave-active-class="transition ease-in duration-100" | ||||||
|  |             leave-from-class="opacity-100" | ||||||
|  |             leave-to-class="opacity-0" | ||||||
|  |           > | ||||||
|  |             <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> | ||||||
|  |               <ListboxOption | ||||||
|  |                 v-for="role in roles" | ||||||
|  |                 :key="role.value" | ||||||
|  |                 v-slot="{ active, selected }" | ||||||
|  |                 as="template" | ||||||
|  |                 :value="role" | ||||||
|  |               > | ||||||
|  |                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|  |                     {{ role.label }} | ||||||
|  |                   </span> | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     v-if="selected" | ||||||
|  |                     :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                   > | ||||||
|  |                     <CheckIcon class="h-5 w-5" /> | ||||||
|  |                   </span> | ||||||
|  |                 </li> | ||||||
|  |               </ListboxOption> | ||||||
|  |             </ListboxOptions> | ||||||
|  |           </transition> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.role" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.role }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </Listbox> | ||||||
|  |       <Listbox | ||||||
|  |         v-model="form.employmentForm" | ||||||
|  |         as="div" | ||||||
|  |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|  |       > | ||||||
|  |         <ListboxLabel class="block text-sm font-medium text-gray-700"> | ||||||
|  |           Forma zatrudnienia | ||||||
|  |         </ListboxLabel> | ||||||
|  |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|  |           <ListboxButton | ||||||
|  |             class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }" | ||||||
|  |           > | ||||||
|  |             <span class="block truncate">{{ form.employmentForm.label }}</span> | ||||||
|  |             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||||
|  |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |             </span> | ||||||
|  |           </ListboxButton> | ||||||
|  |           <transition | ||||||
|  |             leave-active-class="transition ease-in duration-100" | ||||||
|  |             leave-from-class="opacity-100" | ||||||
|  |             leave-to-class="opacity-0" | ||||||
|  |           > | ||||||
|  |             <ListboxOptions class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"> | ||||||
|  |               <ListboxOption | ||||||
|  |                 v-for="employmentForm in employmentForms" | ||||||
|  |                 :key="employmentForm.value" | ||||||
|  |                 v-slot="{ active, selected }" | ||||||
|  |                 as="template" | ||||||
|  |                 :value="employmentForm" | ||||||
|  |               > | ||||||
|  |                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|  |                     {{ employmentForm.label }} | ||||||
|  |                   </span> | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     v-if="selected" | ||||||
|  |                     :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                   > | ||||||
|  |                     <CheckIcon class="h-5 w-5" /> | ||||||
|  |                   </span> | ||||||
|  |                 </li> | ||||||
|  |               </ListboxOption> | ||||||
|  |             </ListboxOptions> | ||||||
|  |           </transition> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.employmentForm" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.employmentForm }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </Listbox> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="employment_date" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Data zatrudnienia | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="employment_date" | ||||||
|  |             v-model="form.employmentDate" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.employmentDate" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.employmentDate }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="flex justify-end py-3"> | ||||||
|  |         <div class="space-x-3"> | ||||||
|  |           <InertiaLink | ||||||
|  |             href="/users" | ||||||
|  |             class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Anuluj | ||||||
|  |           </InertiaLink> | ||||||
|  |           <button | ||||||
|  |             type="submit" | ||||||
|  |             :disabled="form.processing" | ||||||
|  |             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Zapisz | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import {useForm} from '@inertiajs/inertia-vue3'; | import {useForm} from '@inertiajs/inertia-vue3' | ||||||
| import FlatPickr from 'vue-flatpickr-component'; | import FlatPickr from 'vue-flatpickr-component' | ||||||
| import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue'; | import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue' | ||||||
| import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid'; | import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     employmentDate: 'UserEdit', |   name: 'UserEdit', | ||||||
|     components: { |   components: { | ||||||
|         FlatPickr, |     FlatPickr, | ||||||
|         Listbox, |     Listbox, | ||||||
|         ListboxButton, |     ListboxButton, | ||||||
|         ListboxLabel, |     ListboxLabel, | ||||||
|         ListboxOption, |     ListboxOption, | ||||||
|         ListboxOptions, |     ListboxOptions, | ||||||
|         CheckIcon, |     CheckIcon, | ||||||
|         SelectorIcon, |     SelectorIcon, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     employmentForms: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |     roles: { | ||||||
|         employmentForms: { |       type: Object, | ||||||
|             type: Object, |       default: () => null, | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|         user: { |  | ||||||
|             type: Object, |  | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|     }, |     }, | ||||||
|     setup(props) { |     user: { | ||||||
|         const form = useForm({ |       type: Object, | ||||||
|             firstName: props.user.firstName, |       default: () => null, | ||||||
|             lastName: props.user.lastName, |     }, | ||||||
|             email: props.user.email, |   }, | ||||||
|             employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), |   setup(props) { | ||||||
|             employmentDate: props.user.employmentDate, |     const form = useForm({ | ||||||
|         }); |       firstName: props.user.firstName, | ||||||
|  |       lastName: props.user.lastName, | ||||||
|  |       email: props.user.email, | ||||||
|  |       role: props.roles.find(role => role.value === props.user.role), | ||||||
|  |       employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), | ||||||
|  |       employmentDate: props.user.employmentDate, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|         return { form }; |     return { form } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     editUser() { | ||||||
|  |       this.form | ||||||
|  |         .transform(data => ({ | ||||||
|  |           ...data, | ||||||
|  |           employmentForm: data.employmentForm.value, | ||||||
|  |           role: data.role.value, | ||||||
|  |         })) | ||||||
|  |         .put(`/users/${this.user.id}`) | ||||||
|     }, |     }, | ||||||
|     methods: { |   }, | ||||||
|         editUser() { | } | ||||||
|             this.form |  | ||||||
|                 .transform(data => ({ |  | ||||||
|                     ...data, |  | ||||||
|                     employmentForm: data.employmentForm.value, |  | ||||||
|                 })) |  | ||||||
|                 .put(`/users/${this.user.id}`); |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,300 +1,300 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Użytkownicy" /> |   <InertiaHead title="Użytkownicy" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="flex justify-between items-center p-4 sm:px-6"> |     <div class="flex justify-between items-center p-4 sm:px-6"> | ||||||
|             <div> |       <div> | ||||||
|                 <h2 class="text-lg leading-6 font-medium text-gray-900"> |         <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                     Użytkownicy w organizacji |           Użytkownicy w organizacji | ||||||
|                 </h2> |         </h2> | ||||||
|                 <p class="mt-1 text-sm text-gray-500"> |         <p class="mt-1 text-sm text-gray-500"> | ||||||
|                     Lista użytkowników w organizacji. |           Lista użytkowników w organizacji. | ||||||
|                 </p> |         </p> | ||||||
|             </div> |       </div> | ||||||
|             <div> |       <div> | ||||||
|                 <InertiaLink |         <InertiaLink | ||||||
|                     href="users/create" |           href="users/create" | ||||||
|                     class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |           class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|                 > |         > | ||||||
|                     Dodaj użytkownika |           Dodaj użytkownika | ||||||
|                 </InertiaLink> |         </InertiaLink> | ||||||
|             </div> |       </div> | ||||||
|         </div> |  | ||||||
|         <div class="border-t border-gray-200"> |  | ||||||
|             <div class="px-4 py-3"> |  | ||||||
|                 <div class="relative max-w-md"> |  | ||||||
|                     <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> |  | ||||||
|                         <SearchIcon class="h-5 w-5 text-gray-400" /> |  | ||||||
|                     </div> |  | ||||||
|                     <input |  | ||||||
|                         v-model.trim="search" |  | ||||||
|                         type="search" |  | ||||||
|                         class="block w-full bg-white border border-gray-300 rounded-md py-2 pl-10 pr-3 text-sm placeholder-gray-500 focus:outline-none focus:text-gray-900 focus:placeholder-gray-400 focus:ring-1 focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm mt-1" |  | ||||||
|                         placeholder="Szukaj" |  | ||||||
|                     > |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> |  | ||||||
|                 <table class="min-w-full divide-y divide-gray-200"> |  | ||||||
|                     <thead class="bg-gray-50"> |  | ||||||
|                         <tr> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Imię i nazwisko |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Rola |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Forma zatrudnienia |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             > |  | ||||||
|                                 Data rozpoczęcia |  | ||||||
|                             </th> |  | ||||||
|                             <th |  | ||||||
|                                 scope="col" |  | ||||||
|                                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                             /> |  | ||||||
|                         </tr> |  | ||||||
|                     </thead> |  | ||||||
|                     <tbody class="bg-white divide-y divide-gray-100"> |  | ||||||
|                         <tr |  | ||||||
|                             v-for="user in users.data" |  | ||||||
|                             :key="user.id" |  | ||||||
|                             :class="{ 'bg-red-50': user.deleted, 'hover:bg-blumilk-25': !user.deleted }" |  | ||||||
|                         > |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 <div class="flex"> |  | ||||||
|                                     <span |  | ||||||
|                                         class="inline-flex items-center justify-center h-10 w-10 rounded-full" |  | ||||||
|                                     > |  | ||||||
|                                         <img |  | ||||||
|                                             class="h-10 w-10 rounded-full" |  | ||||||
|                                             :src="user.avatar" |  | ||||||
|                                             alt="" |  | ||||||
|                                         > |  | ||||||
|                                     </span> |  | ||||||
|                                     <div class="ml-3"> |  | ||||||
|                                         <p class="text-sm font-medium break-all text-gray-900"> |  | ||||||
|                                             {{ user.name }} |  | ||||||
|                                         </p> |  | ||||||
|                                         <p class="text-sm break-all text-gray-500"> |  | ||||||
|                                             {{ user.email }} |  | ||||||
|                                         </p> |  | ||||||
|                                     </div> |  | ||||||
|                                 </div> |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 {{ user.role }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 {{ user.employmentForm }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                 {{ user.employmentDate }} |  | ||||||
|                             </td> |  | ||||||
|                             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 text-right"> |  | ||||||
|                                 <Menu |  | ||||||
|                                     as="div" |  | ||||||
|                                     class="relative inline-block text-left" |  | ||||||
|                                 > |  | ||||||
|                                     <MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"> |  | ||||||
|                                         <DotsVerticalIcon |  | ||||||
|                                             class="h-5 w-5" |  | ||||||
|                                             aria-hidden="true" |  | ||||||
|                                         /> |  | ||||||
|                                     </MenuButton> |  | ||||||
|  |  | ||||||
|                                     <transition |  | ||||||
|                                         enter-active-class="transition ease-out duration-100" |  | ||||||
|                                         enter-from-class="transform opacity-0 scale-95" |  | ||||||
|                                         enter-to-class="transform opacity-100 scale-100" |  | ||||||
|                                         leave-active-class="transition ease-in duration-75" |  | ||||||
|                                         leave-from-class="transform opacity-100 scale-100" |  | ||||||
|                                         leave-to-class="transform opacity-0 scale-95" |  | ||||||
|                                     > |  | ||||||
|                                         <MenuItems class="origin-top-right absolute right-0 mt-2 w-56 z-10 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"> |  | ||||||
|                                             <div |  | ||||||
|                                                 v-if="!user.deleted" |  | ||||||
|                                                 class="py-1" |  | ||||||
|                                             > |  | ||||||
|                                                 <MenuItem |  | ||||||
|                                                     v-slot="{ active }" |  | ||||||
|                                                     class="flex" |  | ||||||
|                                                 > |  | ||||||
|                                                     <InertiaLink |  | ||||||
|                                                         :href="`/users/${user.id}/edit`" |  | ||||||
|                                                         :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" |  | ||||||
|                                                     > |  | ||||||
|                                                         <PencilIcon |  | ||||||
|                                                             class="mr-2 h-5 w-5 text-blue-500" |  | ||||||
|                                                             aria-hidden="true" |  | ||||||
|                                                         /> Edytuj |  | ||||||
|                                                     </InertiaLink> |  | ||||||
|                                                 </MenuItem> |  | ||||||
|                                                 <MenuItem |  | ||||||
|                                                     v-slot="{ active }" |  | ||||||
|                                                     class="flex" |  | ||||||
|                                                 > |  | ||||||
|                                                     <InertiaLink |  | ||||||
|                                                         as="button" |  | ||||||
|                                                         method="delete" |  | ||||||
|                                                         :preserve-scroll="true" |  | ||||||
|                                                         :href="`/users/${user.id}`" |  | ||||||
|                                                         :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                                                     > |  | ||||||
|                                                         <TrashIcon |  | ||||||
|                                                             class="mr-2 h-5 w-5 text-red-500" |  | ||||||
|                                                             aria-hidden="true" |  | ||||||
|                                                         /> Usuń |  | ||||||
|                                                     </InertiaLink> |  | ||||||
|                                                 </MenuItem> |  | ||||||
|                                             </div> |  | ||||||
|                                             <div |  | ||||||
|                                                 v-else |  | ||||||
|                                                 class="py-1" |  | ||||||
|                                             > |  | ||||||
|                                                 <MenuItem |  | ||||||
|                                                     v-slot="{ active }" |  | ||||||
|                                                     class="flex" |  | ||||||
|                                                 > |  | ||||||
|                                                     <InertiaLink |  | ||||||
|                                                         as="button" |  | ||||||
|                                                         method="post" |  | ||||||
|                                                         :preserve-scroll="true" |  | ||||||
|                                                         :href="`/users/${user.id}/restore`" |  | ||||||
|                                                         :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                                                     > |  | ||||||
|                                                         <RefreshIcon |  | ||||||
|                                                             class="mr-2 h-5 w-5 text-green-500" |  | ||||||
|                                                             aria-hidden="true" |  | ||||||
|                                                         /> Przywróć |  | ||||||
|                                                     </InertiaLink> |  | ||||||
|                                                 </MenuItem> |  | ||||||
|                                             </div> |  | ||||||
|                                         </MenuItems> |  | ||||||
|                                     </transition> |  | ||||||
|                                 </Menu> |  | ||||||
|                             </td> |  | ||||||
|                         </tr> |  | ||||||
|                         <tr |  | ||||||
|                             v-if="! users.data.length" |  | ||||||
|                         > |  | ||||||
|                             <td |  | ||||||
|                                 colspan="100%" |  | ||||||
|                                 class="text-center py-4 text-xl leading-5 text-gray-700" |  | ||||||
|                             > |  | ||||||
|                                 Brak danych |  | ||||||
|                             </td> |  | ||||||
|                         </tr> |  | ||||||
|                     </tbody> |  | ||||||
|                 </table> |  | ||||||
|                 <div |  | ||||||
|                     v-if="users.data.length && users.meta.last_page !== 1" |  | ||||||
|                     class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg" |  | ||||||
|                 > |  | ||||||
|                     <div class="flex-1 flex justify-between sm:hidden"> |  | ||||||
|                         <InertiaLink |  | ||||||
|                             :is="users.links.prev ? 'InertiaLink': 'span'" |  | ||||||
|                             :href="users.links.prev" |  | ||||||
|                             class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" |  | ||||||
|                         > |  | ||||||
|                             Poprzednia |  | ||||||
|                         </InertiaLink> |  | ||||||
|                         <Component |  | ||||||
|                             :is="users.links.next ? 'InertiaLink': 'span'" |  | ||||||
|                             :href="users.links.next" |  | ||||||
|                             class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" |  | ||||||
|                         > |  | ||||||
|                             Następna |  | ||||||
|                         </Component> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> |  | ||||||
|                         <div class="text-sm text-gray-700"> |  | ||||||
|                             Wyświetlanie |  | ||||||
|                             <span class="font-medium">{{ users.meta.from }}</span> |  | ||||||
|                             od |  | ||||||
|                             <span class="font-medium">{{ users.meta.to }}</span> |  | ||||||
|                             do |  | ||||||
|                             <span class="font-medium">{{ users.meta.total }}</span> |  | ||||||
|                             wyników |  | ||||||
|                         </div> |  | ||||||
|                         <nav class="relative z-0 inline-flex space-x-1"> |  | ||||||
|                             <template |  | ||||||
|                                 v-for="(link, index) in users.meta.links" |  | ||||||
|                                 :key="index" |  | ||||||
|                             > |  | ||||||
|                                 <Component |  | ||||||
|                                     :is="link.url ? 'InertiaLink' : 'span'" |  | ||||||
|                                     :href="link.url" |  | ||||||
|                                     :preserve-scroll="true" |  | ||||||
|                                     class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium" |  | ||||||
|                                     :class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}" |  | ||||||
|                                     v-text="link.label" |  | ||||||
|                                 /> |  | ||||||
|                             </template> |  | ||||||
|                         </nav> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="border-t border-gray-200"> | ||||||
|  |       <div class="px-4 py-3"> | ||||||
|  |         <div class="relative max-w-md"> | ||||||
|  |           <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | ||||||
|  |             <SearchIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |           </div> | ||||||
|  |           <input | ||||||
|  |             v-model.trim="search" | ||||||
|  |             type="search" | ||||||
|  |             class="block w-full bg-white border border-gray-300 rounded-md py-2 pl-10 pr-3 text-sm placeholder-gray-500 focus:outline-none focus:text-gray-900 focus:placeholder-gray-400 focus:ring-1 focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm mt-1" | ||||||
|  |             placeholder="Szukaj" | ||||||
|  |           > | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> | ||||||
|  |         <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Imię i nazwisko | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Rola | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Forma zatrudnienia | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               > | ||||||
|  |                 Data rozpoczęcia | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |               /> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|  |             <tr | ||||||
|  |               v-for="user in users.data" | ||||||
|  |               :key="user.id" | ||||||
|  |               :class="{ 'bg-red-50': user.deleted, 'hover:bg-blumilk-25': !user.deleted }" | ||||||
|  |             > | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 <div class="flex"> | ||||||
|  |                   <span | ||||||
|  |                     class="inline-flex items-center justify-center h-10 w-10 rounded-full" | ||||||
|  |                   > | ||||||
|  |                     <img | ||||||
|  |                       class="h-10 w-10 rounded-full" | ||||||
|  |                       :src="user.avatar" | ||||||
|  |                       alt="" | ||||||
|  |                     > | ||||||
|  |                   </span> | ||||||
|  |                   <div class="ml-3"> | ||||||
|  |                     <p class="text-sm font-medium break-all text-gray-900"> | ||||||
|  |                       {{ user.name }} | ||||||
|  |                     </p> | ||||||
|  |                     <p class="text-sm break-all text-gray-500"> | ||||||
|  |                       {{ user.email }} | ||||||
|  |                     </p> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 {{ user.role }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 {{ user.employmentForm }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                 {{ user.employmentDate }} | ||||||
|  |               </td> | ||||||
|  |               <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 text-right"> | ||||||
|  |                 <Menu | ||||||
|  |                   as="div" | ||||||
|  |                   class="relative inline-block text-left" | ||||||
|  |                 > | ||||||
|  |                   <MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"> | ||||||
|  |                     <DotsVerticalIcon | ||||||
|  |                       class="h-5 w-5" | ||||||
|  |                       aria-hidden="true" | ||||||
|  |                     /> | ||||||
|  |                   </MenuButton> | ||||||
|  |  | ||||||
|  |                   <transition | ||||||
|  |                     enter-active-class="transition ease-out duration-100" | ||||||
|  |                     enter-from-class="transform opacity-0 scale-95" | ||||||
|  |                     enter-to-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-active-class="transition ease-in duration-75" | ||||||
|  |                     leave-from-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-to-class="transform opacity-0 scale-95" | ||||||
|  |                   > | ||||||
|  |                     <MenuItems class="origin-top-right absolute right-0 mt-2 w-56 z-10 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"> | ||||||
|  |                       <div | ||||||
|  |                         v-if="!user.deleted" | ||||||
|  |                         class="py-1" | ||||||
|  |                       > | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             :href="`/users/${user.id}/edit`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'font-medium block px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <PencilIcon | ||||||
|  |                               class="mr-2 h-5 w-5 text-blue-500" | ||||||
|  |                               aria-hidden="true" | ||||||
|  |                             /> Edytuj | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             as="button" | ||||||
|  |                             method="delete" | ||||||
|  |                             :preserve-scroll="true" | ||||||
|  |                             :href="`/users/${user.id}`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <TrashIcon | ||||||
|  |                               class="mr-2 h-5 w-5 text-red-500" | ||||||
|  |                               aria-hidden="true" | ||||||
|  |                             /> Usuń | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                       </div> | ||||||
|  |                       <div | ||||||
|  |                         v-else | ||||||
|  |                         class="py-1" | ||||||
|  |                       > | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             as="button" | ||||||
|  |                             method="post" | ||||||
|  |                             :preserve-scroll="true" | ||||||
|  |                             :href="`/users/${user.id}/restore`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <RefreshIcon | ||||||
|  |                               class="mr-2 h-5 w-5 text-green-500" | ||||||
|  |                               aria-hidden="true" | ||||||
|  |                             /> Przywróć | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                       </div> | ||||||
|  |                     </MenuItems> | ||||||
|  |                   </transition> | ||||||
|  |                 </Menu> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr | ||||||
|  |               v-if="! users.data.length" | ||||||
|  |             > | ||||||
|  |               <td | ||||||
|  |                 colspan="100%" | ||||||
|  |                 class="text-center py-4 text-xl leading-5 text-gray-700" | ||||||
|  |               > | ||||||
|  |                 Brak danych | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |         <div | ||||||
|  |           v-if="users.data.length && users.meta.last_page !== 1" | ||||||
|  |           class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg" | ||||||
|  |         > | ||||||
|  |           <div class="flex-1 flex justify-between sm:hidden"> | ||||||
|  |             <InertiaLink | ||||||
|  |               :is="users.links.prev ? 'InertiaLink': 'span'" | ||||||
|  |               :href="users.links.prev" | ||||||
|  |               class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" | ||||||
|  |             > | ||||||
|  |               Poprzednia | ||||||
|  |             </InertiaLink> | ||||||
|  |             <Component | ||||||
|  |               :is="users.links.next ? 'InertiaLink': 'span'" | ||||||
|  |               :href="users.links.next" | ||||||
|  |               class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" | ||||||
|  |             > | ||||||
|  |               Następna | ||||||
|  |             </Component> | ||||||
|  |           </div> | ||||||
|  |           <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> | ||||||
|  |             <div class="text-sm text-gray-700"> | ||||||
|  |               Wyświetlanie | ||||||
|  |               <span class="font-medium">{{ users.meta.from }}</span> | ||||||
|  |               od | ||||||
|  |               <span class="font-medium">{{ users.meta.to }}</span> | ||||||
|  |               do | ||||||
|  |               <span class="font-medium">{{ users.meta.total }}</span> | ||||||
|  |               wyników | ||||||
|  |             </div> | ||||||
|  |             <nav class="relative z-0 inline-flex space-x-1"> | ||||||
|  |               <template | ||||||
|  |                 v-for="(link, index) in users.meta.links" | ||||||
|  |                 :key="index" | ||||||
|  |               > | ||||||
|  |                 <Component | ||||||
|  |                   :is="link.url ? 'InertiaLink' : 'span'" | ||||||
|  |                   :href="link.url" | ||||||
|  |                   :preserve-scroll="true" | ||||||
|  |                   class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium" | ||||||
|  |                   :class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}" | ||||||
|  |                   v-text="link.label" | ||||||
|  |                 /> | ||||||
|  |               </template> | ||||||
|  |             </nav> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import { ref, watch } from 'vue'; | import { ref, watch } from 'vue' | ||||||
| import { Inertia } from '@inertiajs/inertia'; | import { Inertia } from '@inertiajs/inertia' | ||||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash' | ||||||
| import { SearchIcon } from '@heroicons/vue/outline'; | import { SearchIcon } from '@heroicons/vue/outline' | ||||||
| import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid'; | import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid' | ||||||
| import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'; | import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'UserIndex', |   name: 'UserIndex', | ||||||
|     components: { |   components: { | ||||||
|         SearchIcon, |     SearchIcon, | ||||||
|         DotsVerticalIcon, |     DotsVerticalIcon, | ||||||
|         PencilIcon, |     PencilIcon, | ||||||
|         TrashIcon, |     TrashIcon, | ||||||
|         RefreshIcon, |     RefreshIcon, | ||||||
|         Menu, |     Menu, | ||||||
|         MenuButton, |     MenuButton, | ||||||
|         MenuItem, |     MenuItem, | ||||||
|         MenuItems, |     MenuItems, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     users: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |     filters: { | ||||||
|         users: { |       type: Object, | ||||||
|             type: Object, |       default: () => null, | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|         filters: { |  | ||||||
|             type: Object, |  | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|     }, |     }, | ||||||
|     setup(props) { |   }, | ||||||
|         let search = ref(props.filters.search); |   setup(props) { | ||||||
|  |     let search = ref(props.filters.search) | ||||||
|  |  | ||||||
|         watch(search, debounce(value => { |     watch(search, debounce(value => { | ||||||
|             Inertia.get('/users', value ? { search: value} : {}, { |       Inertia.get('/users', value ? { search: value} : {}, { | ||||||
|                 preserveState: true, |         preserveState: true, | ||||||
|                 replace: true, |         replace: true, | ||||||
|             }); |       }) | ||||||
|         }, 300)); |     }, 300)) | ||||||
|  |  | ||||||
|         return { |     return { | ||||||
|             search, |       search, | ||||||
|         }; |     } | ||||||
|     }, |   }, | ||||||
| }; | } | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,175 +1,175 @@ | |||||||
| <template> | <template> | ||||||
|     <InertiaHead title="Użytkownicy" /> |   <InertiaHead title="Użytkownicy" /> | ||||||
|     <div class="bg-white sm:rounded-lg shadow-md"> |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|         <div class="flex justify-between items-center p-4 sm:px-6"> |     <div class="flex justify-between items-center p-4 sm:px-6"> | ||||||
|             <div> |       <div> | ||||||
|                 <h2 class="text-lg leading-6 font-medium text-gray-900"> |         <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|                     Dostępne dni urlopu dla użytkowników |           Dostępne dni urlopu dla użytkowników | ||||||
|                 </h2> |         </h2> | ||||||
|                 <p class="mt-1 text-sm text-gray-500"> |         <p class="mt-1 text-sm text-gray-500"> | ||||||
|                     Zarządzaj dostepnymi dniami urlopów dla użytkowników. |           Zarządzaj dostepnymi dniami urlopów dla użytkowników. | ||||||
|                 </p> |         </p> | ||||||
|             </div> |       </div> | ||||||
|         </div> |  | ||||||
|         <div class="border-t border-gray-200"> |  | ||||||
|             <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> |  | ||||||
|                 <form @submit.prevent="submitVacationDays"> |  | ||||||
|                     <table class="min-w-full divide-y divide-gray-200"> |  | ||||||
|                         <thead class="bg-gray-50"> |  | ||||||
|                             <tr> |  | ||||||
|                                 <th |  | ||||||
|                                     scope="col" |  | ||||||
|                                     class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                                 > |  | ||||||
|                                     Imię i nazwisko |  | ||||||
|                                 </th> |  | ||||||
|                                 <th |  | ||||||
|                                     scope="col" |  | ||||||
|                                     class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                                 > |  | ||||||
|                                     Forma zatrudnienia |  | ||||||
|                                 </th> |  | ||||||
|                                 <th |  | ||||||
|                                     scope="col" |  | ||||||
|                                     class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                                 > |  | ||||||
|                                     Posiada urlop? |  | ||||||
|                                 </th> |  | ||||||
|                                 <th |  | ||||||
|                                     scope="col" |  | ||||||
|                                     class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |  | ||||||
|                                 > |  | ||||||
|                                     Dostępne dni w roku |  | ||||||
|                                 </th> |  | ||||||
|                             </tr> |  | ||||||
|                         </thead> |  | ||||||
|                         <tbody class="bg-white divide-y divide-gray-100"> |  | ||||||
|                             <tr |  | ||||||
|                                 v-for="(item, index) in form.items" |  | ||||||
|                                 :key="item.id" |  | ||||||
|                                 class="hover:bg-blumilk-25" |  | ||||||
|                             > |  | ||||||
|                                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                     <div class="flex"> |  | ||||||
|                                         <span |  | ||||||
|                                             class="inline-flex items-center justify-center h-10 w-10 rounded-full" |  | ||||||
|                                         > |  | ||||||
|                                             <img |  | ||||||
|                                                 class="h-10 w-10 rounded-full" |  | ||||||
|                                                 :src="item.user.avatar" |  | ||||||
|                                                 alt="" |  | ||||||
|                                             > |  | ||||||
|                                         </span> |  | ||||||
|                                         <div class="ml-3"> |  | ||||||
|                                             <p class="text-sm font-medium break-all text-gray-900"> |  | ||||||
|                                                 {{ item.user.name }} |  | ||||||
|                                             </p> |  | ||||||
|                                             <p class="text-sm break-all text-gray-500"> |  | ||||||
|                                                 {{ item.user.email }} |  | ||||||
|                                             </p> |  | ||||||
|                                         </div> |  | ||||||
|                                     </div> |  | ||||||
|                                 </td> |  | ||||||
|                                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                     {{ item.user.employmentForm }} |  | ||||||
|                                 </td> |  | ||||||
|                                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                     <Switch |  | ||||||
|                                         v-model="item.hasVacation" |  | ||||||
|                                         :class="[item.hasVacation ? 'bg-blumilk-500' : 'bg-gray-200', 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500']" |  | ||||||
|                                     > |  | ||||||
|                                         <span |  | ||||||
|                                             :class="[item.hasVacation ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200']" |  | ||||||
|                                         /> |  | ||||||
|                                     </Switch> |  | ||||||
|                                 </td> |  | ||||||
|                                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |  | ||||||
|                                     <div class="mt-1 sm:mt-0 sm:col-span-2"> |  | ||||||
|                                         <input |  | ||||||
|                                             v-model="item.days" |  | ||||||
|                                             type="number" |  | ||||||
|                                             min="0" |  | ||||||
|                                             class="block w-full shadow-sm rounded-md sm:text-sm disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none disabled:cursor-not-allowed" |  | ||||||
|                                             :disabled="!item.hasVacation" |  | ||||||
|                                             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`items.${index}.days`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`items.${index}.days`] }" |  | ||||||
|                                         > |  | ||||||
|                                         <p |  | ||||||
|                                             v-if="form.errors[`items.${index}.days`]" |  | ||||||
|                                             class="mt-2 text-sm text-red-600" |  | ||||||
|                                         > |  | ||||||
|                                             {{ form.errors[`items.${index}.days`] }} |  | ||||||
|                                         </p> |  | ||||||
|                                     </div> |  | ||||||
|                                 </td> |  | ||||||
|                             </tr> |  | ||||||
|                             <tr |  | ||||||
|                                 v-if="!form.items.length" |  | ||||||
|                             > |  | ||||||
|                                 <td |  | ||||||
|                                     colspan="100%" |  | ||||||
|                                     class="text-center py-4 text-xl leading-5 text-gray-700" |  | ||||||
|                                 > |  | ||||||
|                                     Brak danych |  | ||||||
|                                 </td> |  | ||||||
|                             </tr> |  | ||||||
|                         </tbody> |  | ||||||
|                     </table> |  | ||||||
|                     <div class="flex justify-end py-3 px-4"> |  | ||||||
|                         <button |  | ||||||
|                             type="submit" |  | ||||||
|                             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" |  | ||||||
|                         > |  | ||||||
|                             Zapisz |  | ||||||
|                         </button> |  | ||||||
|                     </div> |  | ||||||
|                 </form> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="border-t border-gray-200"> | ||||||
|  |       <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> | ||||||
|  |         <form @submit.prevent="submitVacationDays"> | ||||||
|  |           <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |             <thead class="bg-gray-50"> | ||||||
|  |               <tr> | ||||||
|  |                 <th | ||||||
|  |                   scope="col" | ||||||
|  |                   class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |                 > | ||||||
|  |                   Imię i nazwisko | ||||||
|  |                 </th> | ||||||
|  |                 <th | ||||||
|  |                   scope="col" | ||||||
|  |                   class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |                 > | ||||||
|  |                   Forma zatrudnienia | ||||||
|  |                 </th> | ||||||
|  |                 <th | ||||||
|  |                   scope="col" | ||||||
|  |                   class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |                 > | ||||||
|  |                   Posiada urlop? | ||||||
|  |                 </th> | ||||||
|  |                 <th | ||||||
|  |                   scope="col" | ||||||
|  |                   class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |                 > | ||||||
|  |                   Dostępne dni w roku | ||||||
|  |                 </th> | ||||||
|  |               </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|  |               <tr | ||||||
|  |                 v-for="(item, index) in form.items" | ||||||
|  |                 :key="item.id" | ||||||
|  |                 class="hover:bg-blumilk-25" | ||||||
|  |               > | ||||||
|  |                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                   <div class="flex"> | ||||||
|  |                     <span | ||||||
|  |                       class="inline-flex items-center justify-center h-10 w-10 rounded-full" | ||||||
|  |                     > | ||||||
|  |                       <img | ||||||
|  |                         class="h-10 w-10 rounded-full" | ||||||
|  |                         :src="item.user.avatar" | ||||||
|  |                         alt="" | ||||||
|  |                       > | ||||||
|  |                     </span> | ||||||
|  |                     <div class="ml-3"> | ||||||
|  |                       <p class="text-sm font-medium break-all text-gray-900"> | ||||||
|  |                         {{ item.user.name }} | ||||||
|  |                       </p> | ||||||
|  |                       <p class="text-sm break-all text-gray-500"> | ||||||
|  |                         {{ item.user.email }} | ||||||
|  |                       </p> | ||||||
|  |                     </div> | ||||||
|  |                   </div> | ||||||
|  |                 </td> | ||||||
|  |                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                   {{ item.user.employmentForm }} | ||||||
|  |                 </td> | ||||||
|  |                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                   <Switch | ||||||
|  |                     v-model="item.hasVacation" | ||||||
|  |                     :class="[item.hasVacation ? 'bg-blumilk-500' : 'bg-gray-200', 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500']" | ||||||
|  |                   > | ||||||
|  |                     <span | ||||||
|  |                       :class="[item.hasVacation ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200']" | ||||||
|  |                     /> | ||||||
|  |                   </Switch> | ||||||
|  |                 </td> | ||||||
|  |                 <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |                   <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |                     <input | ||||||
|  |                       v-model="item.days" | ||||||
|  |                       type="number" | ||||||
|  |                       min="0" | ||||||
|  |                       class="block w-full shadow-sm rounded-md sm:text-sm disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none disabled:cursor-not-allowed" | ||||||
|  |                       :disabled="!item.hasVacation" | ||||||
|  |                       :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`items.${index}.days`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`items.${index}.days`] }" | ||||||
|  |                     > | ||||||
|  |                     <p | ||||||
|  |                       v-if="form.errors[`items.${index}.days`]" | ||||||
|  |                       class="mt-2 text-sm text-red-600" | ||||||
|  |                     > | ||||||
|  |                       {{ form.errors[`items.${index}.days`] }} | ||||||
|  |                     </p> | ||||||
|  |                   </div> | ||||||
|  |                 </td> | ||||||
|  |               </tr> | ||||||
|  |               <tr | ||||||
|  |                 v-if="!form.items.length" | ||||||
|  |               > | ||||||
|  |                 <td | ||||||
|  |                   colspan="100%" | ||||||
|  |                   class="text-center py-4 text-xl leading-5 text-gray-700" | ||||||
|  |                 > | ||||||
|  |                   Brak danych | ||||||
|  |                 </td> | ||||||
|  |               </tr> | ||||||
|  |             </tbody> | ||||||
|  |           </table> | ||||||
|  |           <div class="flex justify-end py-3 px-4"> | ||||||
|  |             <button | ||||||
|  |               type="submit" | ||||||
|  |               class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |             > | ||||||
|  |               Zapisz | ||||||
|  |             </button> | ||||||
|  |           </div> | ||||||
|  |         </form> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| import {Switch} from '@headlessui/vue'; | import {Switch} from '@headlessui/vue' | ||||||
| import {useForm} from '@inertiajs/inertia-vue3'; | import {useForm} from '@inertiajs/inertia-vue3' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     name: 'VacationLimits', |   name: 'VacationLimits', | ||||||
|     components: { |   components: { | ||||||
|         Switch, |     Switch, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     limits: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|     }, |     }, | ||||||
|     props: { |     years: { | ||||||
|         limits: { |       type: Object, | ||||||
|             type: Object, |       default: () => null, | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|         years: { |  | ||||||
|             type: Object, |  | ||||||
|             default: () => null, |  | ||||||
|         }, |  | ||||||
|     }, |     }, | ||||||
|     setup(props) { |   }, | ||||||
|         const form = useForm({ |   setup(props) { | ||||||
|             items: props.limits.data, |     const form = useForm({ | ||||||
|         }); |       items: props.limits.data, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|         return { |     return { | ||||||
|             form, |       form, | ||||||
|         }; |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     submitVacationDays() { | ||||||
|  |       this.form | ||||||
|  |         .transform(data => ({ | ||||||
|  |           items: data.items.map(item => ({ | ||||||
|  |             id: item.id, | ||||||
|  |             days: item.hasVacation ? item.days : null, | ||||||
|  |           })), | ||||||
|  |         })) | ||||||
|  |         .put('/vacation-limits', { | ||||||
|  |           preserveState: (page) => Object.keys(page.props.errors).length, | ||||||
|  |           preserveScroll: true, | ||||||
|  |         }) | ||||||
|     }, |     }, | ||||||
|     methods: { |   }, | ||||||
|         submitVacationDays() { | } | ||||||
|             this.form |  | ||||||
|                 .transform(data => ({ |  | ||||||
|                     items: data.items.map(item => ({ |  | ||||||
|                         id: item.id, |  | ||||||
|                         days: item.hasVacation ? item.days : null, |  | ||||||
|                     })), |  | ||||||
|                 })) |  | ||||||
|                 .put('/vacation-limits', { |  | ||||||
|                     preserveState: (page) => Object.keys(page.props.errors).length, |  | ||||||
|                     preserveScroll: true, |  | ||||||
|                 }); |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| </script> | </script> | ||||||
|   | |||||||
							
								
								
									
										240
									
								
								resources/js/Pages/VacationRequest/Create.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								resources/js/Pages/VacationRequest/Create.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | |||||||
|  | <template> | ||||||
|  |   <InertiaHead title="Złóż wniosek urlopowy" /> | ||||||
|  |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|  |     <div class="p-4 sm:px-6"> | ||||||
|  |       <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |         Złóż wniosek urlopowy | ||||||
|  |       </h2> | ||||||
|  |     </div> | ||||||
|  |     <form | ||||||
|  |       class="border-t border-gray-200 px-6" | ||||||
|  |       @submit.prevent="createForm" | ||||||
|  |     > | ||||||
|  |       <Listbox | ||||||
|  |         v-model="form.vacationType" | ||||||
|  |         as="div" | ||||||
|  |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|  |       > | ||||||
|  |         <ListboxLabel class="block text-sm font-medium text-gray-700"> | ||||||
|  |           Rodzaj wniosku | ||||||
|  |         </ListboxLabel> | ||||||
|  |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|  |           <ListboxButton | ||||||
|  |             class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.vacationType, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.vacationType }" | ||||||
|  |           > | ||||||
|  |             <span class="block truncate">{{ form.vacationType.label }}</span> | ||||||
|  |             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> | ||||||
|  |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|  |             </span> | ||||||
|  |           </ListboxButton> | ||||||
|  |  | ||||||
|  |           <transition | ||||||
|  |             leave-active-class="transition ease-in duration-100" | ||||||
|  |             leave-from-class="opacity-100" | ||||||
|  |             leave-to-class="opacity-0" | ||||||
|  |           > | ||||||
|  |             <ListboxOptions | ||||||
|  |               class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm" | ||||||
|  |             > | ||||||
|  |               <ListboxOption | ||||||
|  |                 v-for="vacationType in vacationTypes" | ||||||
|  |                 :key="vacationType.value" | ||||||
|  |                 v-slot="{ active, selected }" | ||||||
|  |                 as="template" | ||||||
|  |                 :value="vacationType" | ||||||
|  |               > | ||||||
|  |                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|  |                     {{ vacationType.label }} | ||||||
|  |                   </span> | ||||||
|  |  | ||||||
|  |                   <span | ||||||
|  |                     v-if="selected" | ||||||
|  |                     :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                   > | ||||||
|  |                     <CheckIcon class="h-5 w-5" /> | ||||||
|  |                   </span> | ||||||
|  |                 </li> | ||||||
|  |               </ListboxOption> | ||||||
|  |             </ListboxOptions> | ||||||
|  |           </transition> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.vacationType" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.vacationType }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </Listbox> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="date_from" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Planowany urlop od | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="date_from" | ||||||
|  |             v-model="form.dateFrom" | ||||||
|  |             :config="fromInputConfig" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.dateFrom, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateFrom }" | ||||||
|  |             @on-change="onFromChange" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.dateFrom" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.dateFrom }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="date_from" | ||||||
|  |           class="block text-sm font-medium text-gray-700 sm:mt-px" | ||||||
|  |         > | ||||||
|  |           Planowany urlop do | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <FlatPickr | ||||||
|  |             id="date_to" | ||||||
|  |             v-model="form.dateTo" | ||||||
|  |             :config="toInputConfig" | ||||||
|  |             placeholder="Wybierz datę" | ||||||
|  |             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" | ||||||
|  |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.dateTo, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateTo }" | ||||||
|  |             @on-change="onToChange" | ||||||
|  |           /> | ||||||
|  |           <p | ||||||
|  |             v-if="form.errors.dateTo" | ||||||
|  |             class="mt-2 text-sm text-red-600" | ||||||
|  |           > | ||||||
|  |             {{ form.errors.dateTo }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <span class="block text-sm font-medium text-gray-700 sm:mt-px">Liczba dni urlopu</span> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2 w-full max-w-lg bg-gray-50 border border-gray-300 rounded-md px-4 py-2 inline-flex items-center text-gray-500 sm:text-sm"> | ||||||
|  |           1 | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
|  |         <label | ||||||
|  |           for="comment" | ||||||
|  |           class="block text-sm font-medium text-gray-700" | ||||||
|  |         > | ||||||
|  |           Komentarz | ||||||
|  |         </label> | ||||||
|  |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|  |           <textarea | ||||||
|  |             id="comment" | ||||||
|  |             v-model="form.comment" | ||||||
|  |             rows="4" | ||||||
|  |             class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md" | ||||||
|  |           /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="flex justify-end py-3"> | ||||||
|  |         <div class="space-x-3"> | ||||||
|  |           <InertiaLink | ||||||
|  |             href="/vacation-requests" | ||||||
|  |             class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Anuluj | ||||||
|  |           </InertiaLink> | ||||||
|  |           <button | ||||||
|  |             type="submit" | ||||||
|  |             :disabled="form.processing" | ||||||
|  |             class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |           > | ||||||
|  |             Zapisz | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </form> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import {useForm} from '@inertiajs/inertia-vue3' | ||||||
|  | import FlatPickr from 'vue-flatpickr-component' | ||||||
|  | import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue' | ||||||
|  | import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid' | ||||||
|  | import {reactive} from 'vue' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'VacationRequestCreate', | ||||||
|  |   components: { | ||||||
|  |     FlatPickr, | ||||||
|  |     Listbox, | ||||||
|  |     ListboxButton, | ||||||
|  |     ListboxLabel, | ||||||
|  |     ListboxOption, | ||||||
|  |     ListboxOptions, | ||||||
|  |     CheckIcon, | ||||||
|  |     SelectorIcon, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     vacationTypes: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |     holidays: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   setup(props) { | ||||||
|  |     const form = useForm({ | ||||||
|  |       dateFrom: null, | ||||||
|  |       dateTo: null, | ||||||
|  |       vacationType: props.vacationTypes[0], | ||||||
|  |       comment: null, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     const disableDates = [ | ||||||
|  |       date => (date.getDay() === 0 || date.getDay() === 6), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     const fromInputConfig = reactive({ | ||||||
|  |       maxDate: null, | ||||||
|  |       disable: disableDates, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     const toInputConfig = reactive({ | ||||||
|  |       minDate: null, | ||||||
|  |       disable: disableDates, | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       form, | ||||||
|  |       fromInputConfig, | ||||||
|  |       toInputConfig, | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     createForm() { | ||||||
|  |       this.form | ||||||
|  |         .transform(data => ({ | ||||||
|  |           from: data.dateFrom, | ||||||
|  |           to: data.dateTo, | ||||||
|  |           type: data.vacationType.value, | ||||||
|  |           comment: data.comment, | ||||||
|  |         })) | ||||||
|  |         .post('/vacation-requests') | ||||||
|  |     }, | ||||||
|  |     onFromChange(selectedDates, dateStr) { | ||||||
|  |       this.toInputConfig.minDate = dateStr | ||||||
|  |     }, | ||||||
|  |     onToChange(selectedDates, dateStr) { | ||||||
|  |       this.fromInputConfig.maxDate = dateStr | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										217
									
								
								resources/js/Pages/VacationRequest/Index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								resources/js/Pages/VacationRequest/Index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | |||||||
|  | <template> | ||||||
|  |   <InertiaHead title="Twoje wnioski urlopowe" /> | ||||||
|  |   <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|  |     <div class="flex justify-between items-center p-4 sm:px-6"> | ||||||
|  |       <div> | ||||||
|  |         <h2 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |           Twoje wnioski urlopowe | ||||||
|  |         </h2> | ||||||
|  |       </div> | ||||||
|  |       <div> | ||||||
|  |         <InertiaLink | ||||||
|  |           href="vacation-requests/create" | ||||||
|  |           class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |         > | ||||||
|  |           Dodaj wniosek | ||||||
|  |         </InertiaLink> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible"> | ||||||
|  |       <nav class="relative shadow flex divide-x divide-gray-200 border-t border-gray-200"> | ||||||
|  |         <InertiaLink | ||||||
|  |           v-for="(status, index) in statuses" | ||||||
|  |           :key="index" | ||||||
|  |           :data="{ status: status.value }" | ||||||
|  |           :class="[status.value === filters.status ? 'text-gray-900' : '', 'text-gray-500 hover:text-gray-700 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-sm font-medium text-center hover:bg-gray-50 focus:z-10']" | ||||||
|  |         > | ||||||
|  |           <span>{{ status.name }}</span> | ||||||
|  |           <span :class="[status.value === filters.status ? 'bg-blumilk-500' : 'bg-transparent', 'absolute inset-x-0 bottom-0 h-0.5']" /> | ||||||
|  |         </InertiaLink> | ||||||
|  |       </nav> | ||||||
|  |       <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |         <thead class="bg-gray-50"> | ||||||
|  |           <tr> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Numer | ||||||
|  |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Rodzaj urlopu | ||||||
|  |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Status | ||||||
|  |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Od | ||||||
|  |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Do | ||||||
|  |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Dni urlopu | ||||||
|  |             </th> | ||||||
|  |             <th scope="col" /> | ||||||
|  |           </tr> | ||||||
|  |         </thead> | ||||||
|  |         <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|  |           <tr | ||||||
|  |             v-for="request in requests.data" | ||||||
|  |             :key="request.id" | ||||||
|  |             class="hover:bg-blumilk-25" | ||||||
|  |           > | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               <InertiaLink | ||||||
|  |                 :href="`/vacation-requests/${request.id}`" | ||||||
|  |                 class="font-semibold text-blumilk-600 hover:text-blumilk-500 hover:underline" | ||||||
|  |               > | ||||||
|  |                 {{ request.name }} | ||||||
|  |               </InertiaLink> | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               {{ request.type }} | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               {{ request.state }} | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               {{ request.from }} | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               {{ request.to }} | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               X | ||||||
|  |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|  |               <InertiaLink :href="`/vacation-requests/${request.id}`"> | ||||||
|  |                 <ChevronRightIcon class="block w-6 h-6 fill-gray-400" /> | ||||||
|  |               </InertiaLink> | ||||||
|  |             </td> | ||||||
|  |           </tr> | ||||||
|  |           <tr | ||||||
|  |             v-if="! requests.data.length" | ||||||
|  |           > | ||||||
|  |             <td | ||||||
|  |               colspan="100%" | ||||||
|  |               class="text-center py-4 text-xl leading-5 text-gray-700" | ||||||
|  |             > | ||||||
|  |               Brak danych | ||||||
|  |             </td> | ||||||
|  |           </tr> | ||||||
|  |         </tbody> | ||||||
|  |       </table> | ||||||
|  |       <div | ||||||
|  |         v-if="requests.data.length && requests.meta.last_page !== 1" | ||||||
|  |         class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg" | ||||||
|  |       > | ||||||
|  |         <div class="flex-1 flex justify-between sm:hidden"> | ||||||
|  |           <InertiaLink | ||||||
|  |             :is="requests.links.prev ? 'InertiaLink': 'span'" | ||||||
|  |             :href="requests.links.prev" | ||||||
|  |             class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" | ||||||
|  |           > | ||||||
|  |             Poprzednia | ||||||
|  |           </InertiaLink> | ||||||
|  |           <Component | ||||||
|  |             :is="requests.links.next ? 'InertiaLink': 'span'" | ||||||
|  |             :href="requests.links.next" | ||||||
|  |             class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" | ||||||
|  |           > | ||||||
|  |             Następna | ||||||
|  |           </Component> | ||||||
|  |         </div> | ||||||
|  |         <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> | ||||||
|  |           <div class="text-sm text-gray-700"> | ||||||
|  |             Wyświetlanie | ||||||
|  |             <span class="font-medium">{{ requests.meta.from }}</span> | ||||||
|  |             od | ||||||
|  |             <span class="font-medium">{{ requests.meta.to }}</span> | ||||||
|  |             do | ||||||
|  |             <span class="font-medium">{{ requests.meta.total }}</span> | ||||||
|  |             wyników | ||||||
|  |           </div> | ||||||
|  |           <nav class="relative z-0 inline-flex space-x-1"> | ||||||
|  |             <template | ||||||
|  |               v-for="(link, index) in requests.meta.links" | ||||||
|  |               :key="index" | ||||||
|  |             > | ||||||
|  |               <Component | ||||||
|  |                 :is="link.url ? 'InertiaLink' : 'span'" | ||||||
|  |                 :href="link.url" | ||||||
|  |                 :preserve-scroll="true" | ||||||
|  |                 class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium" | ||||||
|  |                 :class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}" | ||||||
|  |                 v-text="link.label" | ||||||
|  |               /> | ||||||
|  |             </template> | ||||||
|  |           </nav> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import {ChevronRightIcon, DotsVerticalIcon, PencilIcon, TrashIcon} from '@heroicons/vue/solid' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'VacationRequestIndex', | ||||||
|  |   components: { | ||||||
|  |     DotsVerticalIcon, | ||||||
|  |     PencilIcon, | ||||||
|  |     TrashIcon, | ||||||
|  |     ChevronRightIcon, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     requests: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |     filters: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   setup() { | ||||||
|  |     const statuses = [ | ||||||
|  |       { | ||||||
|  |         name: 'Wszystkie', | ||||||
|  |         value: 'all', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: 'W trakcie', | ||||||
|  |         value: 'pending', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: 'Zatwierdzone', | ||||||
|  |         value: 'success', | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: 'Odrzucone/anulowane', | ||||||
|  |         value: 'failed', | ||||||
|  |       }, | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       statuses, | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
							
								
								
									
										216
									
								
								resources/js/Pages/VacationRequest/Show.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								resources/js/Pages/VacationRequest/Show.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | |||||||
|  | <template> | ||||||
|  |   <InertiaHead :title="`Wniosek ${request.name}`" /> | ||||||
|  |   <div class="grid grid-cols-1 gap-6 xl:grid-flow-col-dense xl:grid-cols-3"> | ||||||
|  |     <div class="space-y-6 xl:col-start-1 xl:col-span-2"> | ||||||
|  |       <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|  |         <div class="px-4 py-5 sm:px-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Informacje na temat wniosku | ||||||
|  |           </h3> | ||||||
|  |         </div> | ||||||
|  |         <div class="border-t border-gray-200 px-4 py-5 sm:p-0"> | ||||||
|  |           <dl class="sm:divide-y sm:divide-gray-200"> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Nr wniosku | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 {{ request.name }} | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Rodzaj urlopu | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 {{ request.type }} | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Urlop od | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 {{ request.from }} | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Urlop do | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 {{ request.to }} | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Dni | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 x | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|  |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|  |                 Komentarz | ||||||
|  |               </dt> | ||||||
|  |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|  |                 {{ request.comment }} | ||||||
|  |               </dd> | ||||||
|  |             </div> | ||||||
|  |           </dl> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="bg-white shadow sm:rounded-lg"> | ||||||
|  |         <div class="px-4 py-5 sm:p-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Zaakceptuj wniosek jako osoba techniczna | ||||||
|  |           </h3> | ||||||
|  |           <div class="mt-2 max-w-xl text-sm text-gray-500"> | ||||||
|  |             <p> | ||||||
|  |               W zależności od typu wniosku, zostanie on zatwierdzony lub osoba administracyjna będzie musiała go zaakceptować. | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-5"> | ||||||
|  |             <InertiaLink | ||||||
|  |               :href="`/vacation-requests/${request.id}/accept-as-technical`" | ||||||
|  |               method="post" | ||||||
|  |               as="button" | ||||||
|  |               class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |             > | ||||||
|  |               Zaakceptuj wniosek | ||||||
|  |             </InertiaLink> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="bg-white shadow sm:rounded-lg"> | ||||||
|  |         <div class="px-4 py-5 sm:p-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Zaakceptuj wniosek jako osoba administracyjna | ||||||
|  |           </h3> | ||||||
|  |           <div class="mt-2 max-w-xl text-sm text-gray-500"> | ||||||
|  |             <p> | ||||||
|  |               Po akceptacji przez osobę administracyjną, wniosek zostanie zatwierdzony. | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-5"> | ||||||
|  |             <InertiaLink | ||||||
|  |               :href="`/vacation-requests/${request.id}/accept-as-administrative`" | ||||||
|  |               method="post" | ||||||
|  |               as="button" | ||||||
|  |               class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500" | ||||||
|  |             > | ||||||
|  |               Zaakceptuj wniosek | ||||||
|  |             </InertiaLink> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="bg-white shadow sm:rounded-lg"> | ||||||
|  |         <div class="px-4 py-5 sm:p-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Odrzuć wniosek | ||||||
|  |           </h3> | ||||||
|  |           <div class="mt-2 max-w-xl text-sm text-gray-500"> | ||||||
|  |             <p> | ||||||
|  |               Odrzuconego wniosku nie można przywracać - należy zrobić nowy. | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-5"> | ||||||
|  |             <InertiaLink | ||||||
|  |               :href="`/vacation-requests/${request.id}/reject`" | ||||||
|  |               method="post" | ||||||
|  |               as="button" | ||||||
|  |               class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm" | ||||||
|  |             > | ||||||
|  |               Odrzuć wniosek | ||||||
|  |             </InertiaLink> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |       <div class="bg-white shadow sm:rounded-lg border border-red-500"> | ||||||
|  |         <div class="px-4 py-5 sm:p-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Anuluj wniosek | ||||||
|  |           </h3> | ||||||
|  |           <div class="mt-2 max-w-xl text-sm text-gray-500"> | ||||||
|  |             <p> | ||||||
|  |               Wniosek można anulować w każdej chwili - nawet jeśli był już zatwierdzony. | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|  |           <div class="mt-5"> | ||||||
|  |             <InertiaLink | ||||||
|  |               :href="`/vacation-requests/${request.id}/cancel`" | ||||||
|  |               method="post" | ||||||
|  |               as="button" | ||||||
|  |               class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm" | ||||||
|  |             > | ||||||
|  |               Anuluj wniosek | ||||||
|  |             </InertiaLink> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="xl:col-start-3 xl:col-span-1 space-y-6"> | ||||||
|  |       <div class="bg-white sm:rounded-lg shadow-md"> | ||||||
|  |         <div class="px-4 py-5 sm:px-6"> | ||||||
|  |           <h3 class="text-lg leading-6 font-medium text-gray-900"> | ||||||
|  |             Historia wniosku | ||||||
|  |           </h3> | ||||||
|  |         </div> | ||||||
|  |         <div class="border-t border-gray-200 px-4 py-4"> | ||||||
|  |           <ul> | ||||||
|  |             <li | ||||||
|  |               v-for="(activity, index) in activities.data" | ||||||
|  |               :key="activity.id" | ||||||
|  |             > | ||||||
|  |               <div :class="{'relative pb-8': index !== activities.data.length - 1}"> | ||||||
|  |                 <span | ||||||
|  |                   v-if="(index !== activities.data.length - 1)" | ||||||
|  |                   class="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200" | ||||||
|  |                 /> | ||||||
|  |                 <div class="relative flex space-x-3"> | ||||||
|  |                   <div> | ||||||
|  |                     <span class="bg-blumilk-500 h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white"> | ||||||
|  |                       <ThumbUpIcon class="w-5 h-5 text-white" /> | ||||||
|  |                     </span> | ||||||
|  |                   </div> | ||||||
|  |                   <div class="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4"> | ||||||
|  |                     <div> | ||||||
|  |                       <p class="text-sm text-gray-500"> | ||||||
|  |                         {{ activity.to }} | ||||||
|  |                       </p> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="text-right text-sm whitespace-nowrap text-gray-500"> | ||||||
|  |                       <time>{{ activity.date }}</time> | ||||||
|  |                     </div> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </li> | ||||||
|  |           </ul> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | import { ThumbUpIcon } from '@heroicons/vue/outline' | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   name: 'VacationRequestShow', | ||||||
|  |   components: { | ||||||
|  |     ThumbUpIcon, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     request: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |     activities: { | ||||||
|  |       type: Object, | ||||||
|  |       default: () => null, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | } | ||||||
|  | </script> | ||||||
| @@ -1,12 +1,12 @@ | |||||||
|  |  | ||||||
| <template> | <template> | ||||||
|     <div class="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8 bg-blumilk-25"> |   <div class="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8 bg-blumilk-25"> | ||||||
|         <slot /> |     <slot /> | ||||||
|     </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
| export default { | export default { | ||||||
|     name: 'GuestLayout', |   name: 'GuestLayout', | ||||||
| }; | } | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -1,38 +1,38 @@ | |||||||
| import {createApp, h} from 'vue'; | import {createApp, h} from 'vue' | ||||||
| import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3'; | import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3' | ||||||
| import {InertiaProgress} from '@inertiajs/progress'; | import {InertiaProgress} from '@inertiajs/progress' | ||||||
| import AppLayout from '@/Shared/Layout/AppLayout'; | import AppLayout from '@/Shared/Layout/AppLayout' | ||||||
| import Flatpickr from 'flatpickr'; | import Flatpickr from 'flatpickr' | ||||||
| import { Polish } from 'flatpickr/dist/l10n/pl.js'; | import { Polish } from 'flatpickr/dist/l10n/pl.js' | ||||||
|  |  | ||||||
| createInertiaApp({ | createInertiaApp({ | ||||||
|     resolve: name => { |   resolve: name => { | ||||||
|         const page = require(`./Pages/${name}`).default; |     const page = require(`./Pages/${name}`).default | ||||||
|  |  | ||||||
|         page.layout = page.layout || AppLayout; |     page.layout = page.layout || AppLayout | ||||||
|  |  | ||||||
|         return page; |     return page | ||||||
|     }, |   }, | ||||||
|     setup({el, App, props, plugin}) { |   setup({el, App, props, plugin}) { | ||||||
|         createApp({render: () => h(App, props)}) |     createApp({render: () => h(App, props)}) | ||||||
|             .use(plugin) |       .use(plugin) | ||||||
|             .component('InertiaLink', Link) |       .component('InertiaLink', Link) | ||||||
|             .component('InertiaHead', Head) |       .component('InertiaHead', Head) | ||||||
|             .mount(el); |       .mount(el) | ||||||
|     }, |   }, | ||||||
|     title: title => `${title} - Toby`, |   title: title => `${title} - Toby`, | ||||||
| }); | }) | ||||||
|  |  | ||||||
| InertiaProgress.init({ | InertiaProgress.init({ | ||||||
|     delay: 0, |   delay: 0, | ||||||
|     color: 'red', |   color: 'red', | ||||||
| }); | }) | ||||||
|  |  | ||||||
| Flatpickr.localize(Polish); | Flatpickr.localize(Polish) | ||||||
| Flatpickr.setDefaults({ | Flatpickr.setDefaults({ | ||||||
|     dateFormat: 'Y-m-d', |   dateFormat: 'Y-m-d', | ||||||
|     enableTime: false, |   enableTime: false, | ||||||
|     altFormat: 'j F Y', |   altFormat: 'j F Y', | ||||||
|     altInput: true, |   altInput: true, | ||||||
| }); | }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,5 +3,27 @@ | |||||||
|   "employment_contract": "Umowa o pracę", |   "employment_contract": "Umowa o pracę", | ||||||
|   "commission_contract": "Umowa zlecenie", |   "commission_contract": "Umowa zlecenie", | ||||||
|   "b2b_contract": "Kontrakt B2B", |   "b2b_contract": "Kontrakt B2B", | ||||||
|   "board_member_contract": "Członek zarządu" |   "board_member_contract": "Członek zarządu", | ||||||
|  |   "vacation": "Urlop wypoczynkowy", | ||||||
|  |   "vacation_on_request": "Urlop na żądanie", | ||||||
|  |   "special_vacation": "Urlop okolicznościowy", | ||||||
|  |   "childcare_vacation": "Opieka nad dzieckiem art 188 kp", | ||||||
|  |   "training_vacation": "Urlop szkoleniowy", | ||||||
|  |   "unpaid_vacation": "Urlop bezpłatny", | ||||||
|  |   "volunteering_vacation": "Wolontariat", | ||||||
|  |   "look_for_work_vacation": "Urlop na poszukiwanie pracy", | ||||||
|  |   "time_in_lieu": "Odbiór za święto", | ||||||
|  |   "sick_vacation": "Zwolnienie lekarskie", | ||||||
|  |   "employee": "Pracownik", | ||||||
|  |   "administrator": "Administrator", | ||||||
|  |   "technical_approver": "Techniczny klepacz", | ||||||
|  |   "administrative_approver": "Administracyjny klepacz", | ||||||
|  |   "created": "Utworzony", | ||||||
|  |   "canceled": "Anulowany", | ||||||
|  |   "rejected": "Odrzucony", | ||||||
|  |   "approved": "Zatwierdzony", | ||||||
|  |   "waiting_for_technical": "Czeka na akceptację od technicznego", | ||||||
|  |   "waiting_for_administrative": "Czeka na akceptację od administracyjnego", | ||||||
|  |   "accepted_by_technical": "Zaakceptowany przez technicznego", | ||||||
|  |   "accepted_by_administrative": "Zaakceptowany przez administracyjnego" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,16 +3,17 @@ | |||||||
| declare(strict_types=1); | declare(strict_types=1); | ||||||
|  |  | ||||||
| use Illuminate\Support\Facades\Route; | use Illuminate\Support\Facades\Route; | ||||||
| use Illuminate\Support\Facades\View; |  | ||||||
| use Toby\Infrastructure\Http\Controllers\GoogleController; | use Toby\Infrastructure\Http\Controllers\GoogleController; | ||||||
| use Toby\Infrastructure\Http\Controllers\HolidayController; | use Toby\Infrastructure\Http\Controllers\HolidayController; | ||||||
| use Toby\Infrastructure\Http\Controllers\LogoutController; | use Toby\Infrastructure\Http\Controllers\LogoutController; | ||||||
| use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController; | use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController; | ||||||
| use Toby\Infrastructure\Http\Controllers\UserController; | use Toby\Infrastructure\Http\Controllers\UserController; | ||||||
| use Toby\Infrastructure\Http\Controllers\VacationLimitController; | use Toby\Infrastructure\Http\Controllers\VacationLimitController; | ||||||
|  | use Toby\Infrastructure\Http\Controllers\VacationRequestController; | ||||||
|  |  | ||||||
| Route::middleware("auth")->group(function (): void { | Route::middleware("auth")->group(function (): void { | ||||||
|     Route::get("/", fn() => inertia("Dashboard"))->name("dashboard"); |     Route::get("/", fn() => inertia("Dashboard")) | ||||||
|  |         ->name("dashboard"); | ||||||
|     Route::post("/logout", LogoutController::class); |     Route::post("/logout", LogoutController::class); | ||||||
|  |  | ||||||
|     Route::resource("users", UserController::class); |     Route::resource("users", UserController::class); | ||||||
| @@ -20,16 +21,37 @@ Route::middleware("auth")->group(function (): void { | |||||||
|  |  | ||||||
|     Route::resource("holidays", HolidayController::class); |     Route::resource("holidays", HolidayController::class); | ||||||
|  |  | ||||||
|  |     Route::get("/vacation-limits", [VacationLimitController::class, "edit"]) | ||||||
|  |         ->name("vacation.limits"); | ||||||
|     Route::get("/calendar", [HolidayController::class,"showCalendar"]); |     Route::get("/calendar", [HolidayController::class,"showCalendar"]); | ||||||
|  |  | ||||||
|     Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits"); |     Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits"); | ||||||
|     Route::put("/vacation-limits", [VacationLimitController::class, "update"]); |     Route::put("/vacation-limits", [VacationLimitController::class, "update"]); | ||||||
|  |  | ||||||
|     Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)->name("year-periods.select"); |     Route::get("/vacation-requests", [VacationRequestController::class, "index"]) | ||||||
|  |         ->name("vacation.requests.index"); | ||||||
|  |     Route::get("/vacation-requests/create", [VacationRequestController::class, "create"]) | ||||||
|  |         ->name("vacation.requests.create"); | ||||||
|  |     Route::post("/vacation-requests", [VacationRequestController::class, "store"]) | ||||||
|  |         ->name("vacation.requests.store"); | ||||||
|  |     Route::get("/vacation-requests/{vacationRequest}", [VacationRequestController::class, "show"]) | ||||||
|  |         ->name("vacation.requests.show"); | ||||||
|  |     Route::post("/vacation-requests/{vacationRequest}/reject", [VacationRequestController::class, "reject"]) | ||||||
|  |         ->name("vacation.requests.reject"); | ||||||
|  |     Route::post("/vacation-requests/{vacationRequest}/cancel", [VacationRequestController::class, "cancel"]) | ||||||
|  |         ->name("vacation.requests.cancel"); | ||||||
|  |     Route::post("/vacation-requests/{vacationRequest}/accept-as-technical", [VacationRequestController::class, "acceptAsTechnical"]) | ||||||
|  |         ->name("vacation.requests.accept-as-technical"); | ||||||
|  |     Route::post("/vacation-requests/{vacationRequest}/accept-as-administrative", [VacationRequestController::class, "acceptAsAdministrative"]) | ||||||
|  |         ->name("vacation.requests.accept-as-administrative"); | ||||||
|  |  | ||||||
|  |     Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class) | ||||||
|  |         ->name("year-periods.select"); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| Route::middleware("guest")->group(function (): void { | Route::middleware("guest")->group(function (): void { | ||||||
|     Route::get("login", fn() => inertia("Login"))->name("login"); |     Route::get("login", fn() => inertia("Login")) | ||||||
|  |         ->name("login"); | ||||||
|     Route::get("login/google/start", [GoogleController::class, "redirect"]) |     Route::get("login/google/start", [GoogleController::class, "redirect"]) | ||||||
|         ->name("login.google.start"); |         ->name("login.google.start"); | ||||||
|     Route::get("login/google/end", [GoogleController::class, "callback"]) |     Route::get("login/google/end", [GoogleController::class, "callback"]) | ||||||
|   | |||||||
| @@ -8,7 +8,8 @@ use Illuminate\Foundation\Testing\DatabaseMigrations; | |||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Inertia\Testing\AssertableInertia as Assert; | use Inertia\Testing\AssertableInertia as Assert; | ||||||
| use Tests\FeatureTestCase; | use Tests\FeatureTestCase; | ||||||
| use Toby\Domain\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
| class UserTest extends FeatureTestCase | class UserTest extends FeatureTestCase | ||||||
| @@ -87,6 +88,7 @@ class UserTest extends FeatureTestCase | |||||||
|             ->post("/users", [ |             ->post("/users", [ | ||||||
|                 "firstName" => "John", |                 "firstName" => "John", | ||||||
|                 "lastName" => "Doe", |                 "lastName" => "Doe", | ||||||
|  |                 "role" => Role::EMPLOYEE->value, | ||||||
|                 "email" => "john.doe@example.com", |                 "email" => "john.doe@example.com", | ||||||
|                 "employmentForm" => EmploymentForm::B2B_CONTRACT->value, |                 "employmentForm" => EmploymentForm::B2B_CONTRACT->value, | ||||||
|                 "employmentDate" => Carbon::now()->toDateString(), |                 "employmentDate" => Carbon::now()->toDateString(), | ||||||
| @@ -122,6 +124,7 @@ class UserTest extends FeatureTestCase | |||||||
|                 "firstName" => "John", |                 "firstName" => "John", | ||||||
|                 "lastName" => "Doe", |                 "lastName" => "Doe", | ||||||
|                 "email" => "john.doe@example.com", |                 "email" => "john.doe@example.com", | ||||||
|  |                 "role" => Role::EMPLOYEE->value, | ||||||
|                 "employmentForm" => EmploymentForm::B2B_CONTRACT->value, |                 "employmentForm" => EmploymentForm::B2B_CONTRACT->value, | ||||||
|                 "employmentDate" => Carbon::now()->toDateString(), |                 "employmentDate" => Carbon::now()->toDateString(), | ||||||
|             ]) |             ]) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user