* #36 - wip * #36 - wip * #36 - wip * #36 - added some translations * #36 - fix * #36 - fix Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
		
							
								
								
									
										42
									
								
								app/Domain/VacationDaysCalculator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/Domain/VacationDaysCalculator.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Carbon\CarbonInterface; | ||||||
|  | use Carbon\CarbonPeriod; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  |  | ||||||
|  | class VacationDaysCalculator | ||||||
|  | { | ||||||
|  |     public function calculateDays(YearPeriod $yearPeriod, CarbonInterface $from, CarbonInterface $to): Collection | ||||||
|  |     { | ||||||
|  |         $period = CarbonPeriod::create($from, $to); | ||||||
|  |         $holidays = $yearPeriod->holidays()->pluck("date"); | ||||||
|  |  | ||||||
|  |         $validDays = collect(); | ||||||
|  |  | ||||||
|  |         foreach ($period as $day) { | ||||||
|  |             if ($this->passes($day, $holidays)) { | ||||||
|  |                 $validDays->add($day); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $validDays; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function passes(CarbonInterface $day, Collection $holidays): bool | ||||||
|  |     { | ||||||
|  |         if ($day->isWeekend()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($holidays->contains($day)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -4,13 +4,23 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Closure; | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class DoesNotExceedLimitRule implements VacationRequestRule | class DoesNotExceedLimitRule implements VacationRequestRule | ||||||
| { | { | ||||||
|     public function check(VacationRequest $vacationRequest, Closure $next) |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|     { |     { | ||||||
|         return $next($vacationRequest); |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("You have exceeded your vacation limit."); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,13 +4,25 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Closure; | use Toby\Domain\VacationDaysCalculator; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class MinimumOneVacationDayRule implements VacationRequestRule | class MinimumOneVacationDayRule implements VacationRequestRule | ||||||
| { | { | ||||||
|     public function check(VacationRequest $vacationRequest, Closure $next) |     public function __construct( | ||||||
|  |         protected VacationDaysCalculator $vacationDaysCalculator, | ||||||
|  |     ) { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|     { |     { | ||||||
|         return $next($vacationRequest); |         return $this->vacationDaysCalculator | ||||||
|  |             ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) | ||||||
|  |             ->isNotEmpty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("Vacation needs minimum one day."); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class NoApprovedVacationRequestsInRange implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|  |     { | ||||||
|  |         return !$vacationRequest | ||||||
|  |             ->user | ||||||
|  |             ->vacationRequests() | ||||||
|  |             ->overlapsWith($vacationRequest) | ||||||
|  |             ->states(VacationRequestState::successStates()) | ||||||
|  |             ->exists(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("You have approved vacation request in this range."); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\VacationRequestState; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class NoPendingVacationRequestInRange implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|  |     { | ||||||
|  |         return !$vacationRequest | ||||||
|  |             ->user | ||||||
|  |             ->vacationRequests() | ||||||
|  |             ->overlapsWith($vacationRequest) | ||||||
|  |             ->states(VacationRequestState::pendingStates()) | ||||||
|  |             ->exists(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("You have pending vacation request in this range."); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationRangeIsInTheSameYearRule implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|  |     { | ||||||
|  |         return $vacationRequest->from->isSameYear($vacationRequest->to); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("The vacation request cannot be created at the turn of the year."); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,10 +4,10 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Closure; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| interface VacationRequestRule | interface VacationRequestRule | ||||||
| { | { | ||||||
|     public function check(VacationRequest $vacationRequest, Closure $next); |     public function check(VacationRequest $vacationRequest): bool; | ||||||
|  |     public function errorMessage(): string; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,32 +4,49 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation; | namespace Toby\Domain\Validation; | ||||||
|  |  | ||||||
| use Illuminate\Pipeline\Pipeline; | use Illuminate\Contracts\Container\Container; | ||||||
| use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange; | use Illuminate\Validation\ValidationException; | ||||||
| use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule; | use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule; | ||||||
| use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule; | use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule; | ||||||
| use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange; | use Toby\Domain\Validation\Rules\NoApprovedVacationRequestsInRange; | ||||||
|  | use Toby\Domain\Validation\Rules\NoPendingVacationRequestInRange; | ||||||
|  | use Toby\Domain\Validation\Rules\VacationRangeIsInTheSameYearRule; | ||||||
|  | use Toby\Domain\Validation\Rules\VacationRequestRule; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class VacationRequestValidator | class VacationRequestValidator | ||||||
| { | { | ||||||
|     protected array $rules = [ |     protected array $rules = [ | ||||||
|  |         VacationRangeIsInTheSameYearRule::class, | ||||||
|         MinimumOneVacationDayRule::class, |         MinimumOneVacationDayRule::class, | ||||||
|         DoesNotExceedLimitRule::class, |         DoesNotExceedLimitRule::class, | ||||||
|         PendingVacationRequestInSameRange::class, |         NoPendingVacationRequestInRange::class, | ||||||
|         ApprovedVacationDaysInSameRange::class, |         NoApprovedVacationRequestsInRange::class, | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected Pipeline $pipeline, |         protected Container $container, | ||||||
|     ) { |     ) { | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws ValidationException | ||||||
|  |      */ | ||||||
|     public function validate(VacationRequest $vacationRequest): void |     public function validate(VacationRequest $vacationRequest): void | ||||||
|     { |     { | ||||||
|         $this->pipeline |         foreach ($this->rules as $rule) { | ||||||
|             ->send($vacationRequest) |             $rule = $this->makeRule($rule); | ||||||
|             ->through($this->rules) |  | ||||||
|             ->via("check"); |             if (!$rule->check($vacationRequest)) { | ||||||
|  |                 throw ValidationException::withMessages([ | ||||||
|  |                     "vacationRequest" => $rule->errorMessage(), | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function makeRule(string $class): VacationRequestRule | ||||||
|  |     { | ||||||
|  |         return $this->container->make($class); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ use Toby\Domain\Enums\VacationType; | |||||||
|  * @property VacationRequestState $state |  * @property VacationRequestState $state | ||||||
|  * @property Carbon $from |  * @property Carbon $from | ||||||
|  * @property Carbon $to |  * @property Carbon $to | ||||||
|  |  * @property int $estimated_days | ||||||
|  * @property string $comment |  * @property string $comment | ||||||
|  * @property User $user |  * @property User $user | ||||||
|  * @property YearPeriod $yearPeriod |  * @property YearPeriod $yearPeriod | ||||||
| @@ -68,6 +69,12 @@ class VacationRequest extends Model | |||||||
|         return $query->whereIn("state", $states); |         return $query->whereIn("state", $states); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder | ||||||
|  |     { | ||||||
|  |         return $query->where("from", "<=", $vacationRequest->to) | ||||||
|  |             ->where("to", ">=", $vacationRequest->from); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): VacationRequestFactory |     protected static function newFactory(): VacationRequestFactory | ||||||
|     { |     { | ||||||
|         return VacationRequestFactory::new(); |         return VacationRequestFactory::new(); | ||||||
|   | |||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Controllers\Api; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\JsonResponse; | ||||||
|  | use Toby\Domain\VacationDaysCalculator; | ||||||
|  | use Toby\Infrastructure\Http\Controllers\Controller; | ||||||
|  | use Toby\Infrastructure\Http\Requests\Api\CalculateVacationDaysRequest; | ||||||
|  |  | ||||||
|  | class CalculateVacationDaysController extends Controller | ||||||
|  | { | ||||||
|  |     public function __invoke(CalculateVacationDaysRequest $request, VacationDaysCalculator $calculator): JsonResponse | ||||||
|  |     { | ||||||
|  |         $days = $calculator->calculateDays($request->yearPeriod(), $request->from(), $request->to()); | ||||||
|  |  | ||||||
|  |         return new JsonResponse($days->all()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,6 +11,7 @@ use Illuminate\Http\Response as LaravelResponse; | |||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Toby\Domain\Enums\VacationRequestState; | use Toby\Domain\Enums\VacationRequestState; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Domain\VacationDaysCalculator; | ||||||
| use Toby\Domain\VacationRequestStateManager; | use Toby\Domain\VacationRequestStateManager; | ||||||
| use Toby\Domain\Validation\VacationRequestValidator; | use Toby\Domain\Validation\VacationRequestValidator; | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; | use Toby\Eloquent\Helpers\YearPeriodRetriever; | ||||||
| @@ -64,9 +65,15 @@ class VacationRequestController extends Controller | |||||||
|         VacationRequestRequest $request, |         VacationRequestRequest $request, | ||||||
|         VacationRequestValidator $vacationRequestValidator, |         VacationRequestValidator $vacationRequestValidator, | ||||||
|         VacationRequestStateManager $stateManager, |         VacationRequestStateManager $stateManager, | ||||||
|  |         VacationDaysCalculator $vacationDaysCalculator, | ||||||
|     ): RedirectResponse { |     ): RedirectResponse { | ||||||
|         /** @var VacationRequest $vacationRequest */ |         /** @var VacationRequest $vacationRequest */ | ||||||
|         $vacationRequest = $request->user()->vacationRequests()->make($request->data()); |         $vacationRequest = $request->user()->vacationRequests()->make($request->data()); | ||||||
|  |         $vacationRequest->estimated_days = $vacationDaysCalculator->calculateDays( | ||||||
|  |             $vacationRequest->yearPeriod, | ||||||
|  |             $vacationRequest->from, | ||||||
|  |             $vacationRequest->to, | ||||||
|  |         )->count(); | ||||||
|  |  | ||||||
|         $vacationRequestValidator->validate($vacationRequest); |         $vacationRequestValidator->validate($vacationRequest); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Requests\Api; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Http\FormRequest; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  | use Toby\Infrastructure\Http\Rules\YearPeriodExists; | ||||||
|  |  | ||||||
|  | class CalculateVacationDaysRequest extends FormRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "from" => ["required", "date_format:Y-m-d", new YearPeriodExists()], | ||||||
|  |             "to" => ["required", "date_format:Y-m-d", new YearPeriodExists()], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function from(): Carbon | ||||||
|  |     { | ||||||
|  |         return Carbon::create($this->request->get("from")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function to(): Carbon | ||||||
|  |     { | ||||||
|  |         return Carbon::create($this->request->get("to")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function yearPeriod(): YearPeriod | ||||||
|  |     { | ||||||
|  |         return YearPeriod::findByYear(Carbon::create($this->request->get("from"))->year); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,6 +20,7 @@ class VacationRequestResource extends JsonResource | |||||||
|             "state" => $this->state->label(), |             "state" => $this->state->label(), | ||||||
|             "from" => $this->from->toDisplayString(), |             "from" => $this->from->toDisplayString(), | ||||||
|             "to" => $this->to->toDisplayString(), |             "to" => $this->to->toDisplayString(), | ||||||
|  |             "estimatedDays" => $this->estimated_days, | ||||||
|             "comment" => $this->comment, |             "comment" => $this->comment, | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -10,5 +10,5 @@ return [ | |||||||
|     "allowed_headers" => ["*"], |     "allowed_headers" => ["*"], | ||||||
|     "exposed_headers" => [], |     "exposed_headers" => [], | ||||||
|     "max_age" => 0, |     "max_age" => 0, | ||||||
|     "supports_credentials" => false, |     "supports_credentials" => true, | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ declare(strict_types=1); | |||||||
| namespace Database\Factories; | namespace Database\Factories; | ||||||
|  |  | ||||||
| use Carbon\CarbonImmutable; | use Carbon\CarbonImmutable; | ||||||
|  | use Carbon\CarbonPeriod; | ||||||
| use Illuminate\Database\Eloquent\Factories\Factory; | use Illuminate\Database\Eloquent\Factories\Factory; | ||||||
| use Toby\Domain\Enums\VacationRequestState; | use Toby\Domain\Enums\VacationRequestState; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
| @@ -15,6 +16,7 @@ use Toby\Eloquent\Models\YearPeriod; | |||||||
| class VacationRequestFactory extends Factory | class VacationRequestFactory extends Factory | ||||||
| { | { | ||||||
|     protected $model = VacationRequest::class; |     protected $model = VacationRequest::class; | ||||||
|  |     private static int $number = 1; | ||||||
|  |  | ||||||
|     public function definition(): array |     public function definition(): array | ||||||
|     { |     { | ||||||
| @@ -29,6 +31,7 @@ class VacationRequestFactory extends Factory | |||||||
|             "state" => $this->faker->randomElement(VacationRequestState::cases()), |             "state" => $this->faker->randomElement(VacationRequestState::cases()), | ||||||
|             "from" => $from, |             "from" => $from, | ||||||
|             "to" => $from->addDays($days), |             "to" => $from->addDays($days), | ||||||
|  |             "estimated_days" => fn(array $attributes) => $this->estimateDays($attributes), | ||||||
|             "comment" => $this->faker->boolean ? $this->faker->paragraph() : null, |             "comment" => $this->faker->boolean ? $this->faker->paragraph() : null, | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| @@ -36,12 +39,15 @@ class VacationRequestFactory extends Factory | |||||||
|     protected function generateName(array $attributes): string |     protected function generateName(array $attributes): string | ||||||
|     { |     { | ||||||
|         $year = YearPeriod::find($attributes["year_period_id"])->year; |         $year = YearPeriod::find($attributes["year_period_id"])->year; | ||||||
|         $user = User::find($attributes["user_id"]); |         $number = static::$number++; | ||||||
|  |  | ||||||
|         $number = $user->vacationRequests() |  | ||||||
|             ->whereYear("from", $year) |  | ||||||
|             ->count() + 1; |  | ||||||
|  |  | ||||||
|         return "{$number}/{$year}"; |         return "{$number}/{$year}"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     protected function estimateDays(array $attributes): int | ||||||
|  |     { | ||||||
|  |         $period = CarbonPeriod::create($attributes["from"], $attributes["to"]); | ||||||
|  |  | ||||||
|  |         return $period->count(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ return new class() extends Migration { | |||||||
|             $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); |             $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); | ||||||
|             $table->string("type"); |             $table->string("type"); | ||||||
|             $table->string("state")->nullable(); |             $table->string("state")->nullable(); | ||||||
|  |             $table->integer("estimated_days"); | ||||||
|             $table->date("from"); |             $table->date("from"); | ||||||
|             $table->date("to"); |             $table->date("to"); | ||||||
|             $table->text("comment")->nullable(); |             $table->text("comment")->nullable(); | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										37
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| { | { | ||||||
|     "name": "application", |     "name": "toby", | ||||||
|     "lockfileVersion": 2, |     "lockfileVersion": 2, | ||||||
|     "requires": true, |     "requires": true, | ||||||
|     "packages": { |     "packages": { | ||||||
| @@ -15,6 +15,7 @@ | |||||||
|                 "@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", | ||||||
|  |                 "axios": "^0.25.0", | ||||||
|                 "echarts": "^5.2.2", |                 "echarts": "^5.2.2", | ||||||
|                 "flatpickr": "^4.6.9", |                 "flatpickr": "^4.6.9", | ||||||
|                 "laravel-mix": "^6.0.6", |                 "laravel-mix": "^6.0.6", | ||||||
| @@ -1741,6 +1742,14 @@ | |||||||
|                 "vue": "^3.0.0" |                 "vue": "^3.0.0" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         "node_modules/@inertiajs/inertia/node_modules/axios": { | ||||||
|  |             "version": "0.21.4", | ||||||
|  |             "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", | ||||||
|  |             "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", | ||||||
|  |             "dependencies": { | ||||||
|  |                 "follow-redirects": "^1.14.0" | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|         "node_modules/@inertiajs/progress": { |         "node_modules/@inertiajs/progress": { | ||||||
|             "version": "0.2.7", |             "version": "0.2.7", | ||||||
|             "resolved": "https://registry.npmjs.org/@inertiajs/progress/-/progress-0.2.7.tgz", |             "resolved": "https://registry.npmjs.org/@inertiajs/progress/-/progress-0.2.7.tgz", | ||||||
| @@ -2642,11 +2651,11 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/axios": { |         "node_modules/axios": { | ||||||
|             "version": "0.21.4", |             "version": "0.25.0", | ||||||
|             "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", |             "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", | ||||||
|             "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", |             "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", | ||||||
|             "dependencies": { |             "dependencies": { | ||||||
|                 "follow-redirects": "^1.14.0" |                 "follow-redirects": "^1.14.7" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "node_modules/babel-loader": { |         "node_modules/babel-loader": { | ||||||
| @@ -10664,6 +10673,16 @@ | |||||||
|                 "axios": "^0.21.1", |                 "axios": "^0.21.1", | ||||||
|                 "deepmerge": "^4.0.0", |                 "deepmerge": "^4.0.0", | ||||||
|                 "qs": "^6.9.0" |                 "qs": "^6.9.0" | ||||||
|  |             }, | ||||||
|  |             "dependencies": { | ||||||
|  |                 "axios": { | ||||||
|  |                     "version": "0.21.4", | ||||||
|  |                     "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", | ||||||
|  |                     "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", | ||||||
|  |                     "requires": { | ||||||
|  |                         "follow-redirects": "^1.14.0" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "@inertiajs/inertia-vue3": { |         "@inertiajs/inertia-vue3": { | ||||||
| @@ -11468,11 +11487,11 @@ | |||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "axios": { |         "axios": { | ||||||
|             "version": "0.21.4", |             "version": "0.25.0", | ||||||
|             "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", |             "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", | ||||||
|             "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", |             "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", | ||||||
|             "requires": { |             "requires": { | ||||||
|                 "follow-redirects": "^1.14.0" |                 "follow-redirects": "^1.14.7" | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         "babel-loader": { |         "babel-loader": { | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ | |||||||
|         "@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", | ||||||
|  |         "axios": "^0.25.0", | ||||||
|         "echarts": "^5.2.2", |         "echarts": "^5.2.2", | ||||||
|         "flatpickr": "^4.6.9", |         "flatpickr": "^4.6.9", | ||||||
|         "laravel-mix": "^6.0.6", |         "laravel-mix": "^6.0.6", | ||||||
|   | |||||||
| @@ -10,8 +10,26 @@ | |||||||
|       class="border-t border-gray-200 px-6" |       class="border-t border-gray-200 px-6" | ||||||
|       @submit.prevent="createForm" |       @submit.prevent="createForm" | ||||||
|     > |     > | ||||||
|  |       <div | ||||||
|  |         v-if="form.errors.vacationRequest" | ||||||
|  |         class="rounded-md bg-red-50 p-4 mt-2" | ||||||
|  |       > | ||||||
|  |         <div class="flex"> | ||||||
|  |           <div class="flex-shrink-0"> | ||||||
|  |             <XCircleIcon class="h-5 w-5 text-red-400" /> | ||||||
|  |           </div> | ||||||
|  |           <div class="ml-3"> | ||||||
|  |             <h3 class="text-sm font-medium text-red-800"> | ||||||
|  |               Wniosek nie mógł zostać utworzony | ||||||
|  |             </h3> | ||||||
|  |             <div class="mt-2 text-sm text-red-700"> | ||||||
|  |               <span>{{ form.errors.vacationRequest }}</span> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|       <Listbox |       <Listbox | ||||||
|         v-model="form.vacationType" |         v-model="form.type" | ||||||
|         as="div" |         as="div" | ||||||
|         class="sm:grid sm:grid-cols-3 py-4 items-center" |         class="sm:grid sm:grid-cols-3 py-4 items-center" | ||||||
|       > |       > | ||||||
| @@ -21,9 +39,9 @@ | |||||||
|         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> |         <div class="mt-1 relative sm:mt-0 sm:col-span-2"> | ||||||
|           <ListboxButton |           <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="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 }" |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.type, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.type }" | ||||||
|           > |           > | ||||||
|             <span class="block truncate">{{ form.vacationType.label }}</span> |             <span class="block truncate">{{ form.type.label }}</span> | ||||||
|             <span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none"> |             <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" /> |               <SelectorIcon class="h-5 w-5 text-gray-400" /> | ||||||
|             </span> |             </span> | ||||||
| @@ -38,15 +56,15 @@ | |||||||
|               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" |               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 |               <ListboxOption | ||||||
|                 v-for="vacationType in vacationTypes" |                 v-for="type in vacationTypes" | ||||||
|                 :key="vacationType.value" |                 :key="type.value" | ||||||
|                 v-slot="{ active, selected }" |                 v-slot="{ active, selected }" | ||||||
|                 as="template" |                 as="template" | ||||||
|                 :value="vacationType" |                 :value="type" | ||||||
|               > |               > | ||||||
|                 <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> |                 <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']"> |                   <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']"> | ||||||
|                     {{ vacationType.label }} |                     {{ type.label }} | ||||||
|                   </span> |                   </span> | ||||||
|  |  | ||||||
|                   <span |                   <span | ||||||
| @@ -60,10 +78,10 @@ | |||||||
|             </ListboxOptions> |             </ListboxOptions> | ||||||
|           </transition> |           </transition> | ||||||
|           <p |           <p | ||||||
|             v-if="form.errors.vacationType" |             v-if="form.errors.type" | ||||||
|             class="mt-2 text-sm text-red-600" |             class="mt-2 text-sm text-red-600" | ||||||
|           > |           > | ||||||
|             {{ form.errors.vacationType }} |             {{ form.errors.type }} | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </Listbox> |       </Listbox> | ||||||
| @@ -77,18 +95,18 @@ | |||||||
|         <div class="mt-1 sm:mt-0 sm:col-span-2"> |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|           <FlatPickr |           <FlatPickr | ||||||
|             id="date_from" |             id="date_from" | ||||||
|             v-model="form.dateFrom" |             v-model="form.from" | ||||||
|             :config="fromInputConfig" |             :config="fromInputConfig" | ||||||
|             placeholder="Wybierz datę" |             placeholder="Wybierz datę" | ||||||
|             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |             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 }" |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.from, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.from }" | ||||||
|             @on-change="onFromChange" |             @on-change="onFromChange" | ||||||
|           /> |           /> | ||||||
|           <p |           <p | ||||||
|             v-if="form.errors.dateFrom" |             v-if="form.errors.from" | ||||||
|             class="mt-2 text-sm text-red-600" |             class="mt-2 text-sm text-red-600" | ||||||
|           > |           > | ||||||
|             {{ form.errors.dateFrom }} |             {{ form.errors.from }} | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -102,25 +120,25 @@ | |||||||
|         <div class="mt-1 sm:mt-0 sm:col-span-2"> |         <div class="mt-1 sm:mt-0 sm:col-span-2"> | ||||||
|           <FlatPickr |           <FlatPickr | ||||||
|             id="date_to" |             id="date_to" | ||||||
|             v-model="form.dateTo" |             v-model="form.to" | ||||||
|             :config="toInputConfig" |             :config="toInputConfig" | ||||||
|             placeholder="Wybierz datę" |             placeholder="Wybierz datę" | ||||||
|             class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm" |             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 }" |             :class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.to, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.to }" | ||||||
|             @on-change="onToChange" |             @on-change="onToChange" | ||||||
|           /> |           /> | ||||||
|           <p |           <p | ||||||
|             v-if="form.errors.dateTo" |             v-if="form.errors.to" | ||||||
|             class="mt-2 text-sm text-red-600" |             class="mt-2 text-sm text-red-600" | ||||||
|           > |           > | ||||||
|             {{ form.errors.dateTo }} |             {{ form.errors.to }} | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |       <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> |         <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"> |         <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 |           {{ estimatedDays.length }} | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> |       <div class="sm:grid sm:grid-cols-3 py-4 items-center"> | ||||||
| @@ -135,7 +153,7 @@ | |||||||
|             id="comment" |             id="comment" | ||||||
|             v-model="form.comment" |             v-model="form.comment" | ||||||
|             rows="4" |             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" |             class="shadow-sm focus:ring-blumilk-500 focus:border-blumilk-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md" | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
| @@ -164,8 +182,9 @@ | |||||||
| 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, XCircleIcon} from '@heroicons/vue/solid' | ||||||
| import {reactive} from 'vue' | import {reactive, ref} from 'vue' | ||||||
|  | import axios from 'axios' | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   name: 'VacationRequestCreate', |   name: 'VacationRequestCreate', | ||||||
| @@ -178,6 +197,7 @@ export default { | |||||||
|     ListboxOptions, |     ListboxOptions, | ||||||
|     CheckIcon, |     CheckIcon, | ||||||
|     SelectorIcon, |     SelectorIcon, | ||||||
|  |     XCircleIcon, | ||||||
|   }, |   }, | ||||||
|   props: { |   props: { | ||||||
|     vacationTypes: { |     vacationTypes: { | ||||||
| @@ -191,12 +211,14 @@ export default { | |||||||
|   }, |   }, | ||||||
|   setup(props) { |   setup(props) { | ||||||
|     const form = useForm({ |     const form = useForm({ | ||||||
|       dateFrom: null, |       from: null, | ||||||
|       dateTo: null, |       to: null, | ||||||
|       vacationType: props.vacationTypes[0], |       type: props.vacationTypes[0], | ||||||
|       comment: null, |       comment: null, | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     const estimatedDays = ref([]) | ||||||
|  |  | ||||||
|     const disableDates = [ |     const disableDates = [ | ||||||
|       date => (date.getDay() === 0 || date.getDay() === 6), |       date => (date.getDay() === 0 || date.getDay() === 6), | ||||||
|     ] |     ] | ||||||
| @@ -213,6 +235,7 @@ export default { | |||||||
|  |  | ||||||
|     return { |     return { | ||||||
|       form, |       form, | ||||||
|  |       estimatedDays, | ||||||
|       fromInputConfig, |       fromInputConfig, | ||||||
|       toInputConfig, |       toInputConfig, | ||||||
|     } |     } | ||||||
| @@ -221,18 +244,26 @@ export default { | |||||||
|     createForm() { |     createForm() { | ||||||
|       this.form |       this.form | ||||||
|         .transform(data => ({ |         .transform(data => ({ | ||||||
|           from: data.dateFrom, |           ...data, | ||||||
|           to: data.dateTo, |           type: data.type.value, | ||||||
|           type: data.vacationType.value, |  | ||||||
|           comment: data.comment, |  | ||||||
|         })) |         })) | ||||||
|         .post('/vacation-requests') |         .post('/vacation-requests') | ||||||
|     }, |     }, | ||||||
|     onFromChange(selectedDates, dateStr) { |     onFromChange(selectedDates, dateStr) { | ||||||
|       this.toInputConfig.minDate = dateStr |       this.toInputConfig.minDate = dateStr | ||||||
|  |  | ||||||
|  |       this.refreshEstimatedDays(this.form.from, this.form.to) | ||||||
|     }, |     }, | ||||||
|     onToChange(selectedDates, dateStr) { |     onToChange(selectedDates, dateStr) { | ||||||
|       this.fromInputConfig.maxDate = dateStr |       this.fromInputConfig.maxDate = dateStr | ||||||
|  |  | ||||||
|  |       this.refreshEstimatedDays(this.form.from, this.form.to) | ||||||
|  |     }, | ||||||
|  |     refreshEstimatedDays(from, to) { | ||||||
|  |       if (from && to) { | ||||||
|  |         axios.post('/api/calculate-vacation-days', {from, to}) | ||||||
|  |           .then(res => this.estimatedDays = res.data) | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -33,40 +33,40 @@ | |||||||
|           <tr> |           <tr> | ||||||
|             <th |             <th | ||||||
|               scope="col" |               scope="col" | ||||||
|               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |               class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|             > |             > | ||||||
|               Numer |               Numer | ||||||
|             </th> |             </th> | ||||||
|             <th |             <th | ||||||
|               scope="col" |               scope="col" | ||||||
|               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |               class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|             > |             > | ||||||
|               Rodzaj urlopu |               Rodzaj urlopu | ||||||
|             </th> |             </th> | ||||||
|             <th |             <th | ||||||
|               scope="col" |               scope="col" | ||||||
|               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |               class="px-4 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 |               Od | ||||||
|             </th> |             </th> | ||||||
|             <th |             <th | ||||||
|               scope="col" |               scope="col" | ||||||
|               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |               class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|             > |             > | ||||||
|               Do |               Do | ||||||
|             </th> |             </th> | ||||||
|             <th |             <th | ||||||
|               scope="col" |               scope="col" | ||||||
|               class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" |               class="px-4 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|             > |             > | ||||||
|               Dni urlopu |               Dni urlopu | ||||||
|             </th> |             </th> | ||||||
|  |             <th | ||||||
|  |               scope="col" | ||||||
|  |               class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" | ||||||
|  |             > | ||||||
|  |               Status | ||||||
|  |             </th> | ||||||
|             <th scope="col" /> |             <th scope="col" /> | ||||||
|           </tr> |           </tr> | ||||||
|         </thead> |         </thead> | ||||||
| @@ -87,17 +87,17 @@ | |||||||
|             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|               {{ request.type }} |               {{ request.type }} | ||||||
|             </td> |             </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"> |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|               {{ request.from }} |               {{ request.from }} | ||||||
|             </td> |             </td> | ||||||
|             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|               {{ request.to }} |               {{ request.to }} | ||||||
|             </td> |             </td> | ||||||
|  |             <td class="px-4 py-4 whitespace-nowrap text-right text-sm text-gray-500"> | ||||||
|  |               {{ request.estimatedDays }} | ||||||
|  |             </td> | ||||||
|             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|               X |               {{ request.state }} | ||||||
|             </td> |             </td> | ||||||
|             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> |             <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> | ||||||
|               <InertiaLink :href="`/vacation-requests/${request.id}`"> |               <InertiaLink :href="`/vacation-requests/${request.id}`"> | ||||||
|   | |||||||
| @@ -44,10 +44,10 @@ | |||||||
|             </div> |             </div> | ||||||
|             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> |             <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"> |               <dt class="text-sm font-medium text-gray-500"> | ||||||
|                 Dni |                 Dni urlopu | ||||||
|               </dt> |               </dt> | ||||||
|               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> |               <dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"> | ||||||
|                 x |                 {{ request.estimatedDays }} | ||||||
|               </dd> |               </dd> | ||||||
|             </div> |             </div> | ||||||
|             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> |             <div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"> | ||||||
|   | |||||||
| @@ -16,8 +16,8 @@ | |||||||
|   "sick_vacation": "Zwolnienie lekarskie", |   "sick_vacation": "Zwolnienie lekarskie", | ||||||
|   "employee": "Pracownik", |   "employee": "Pracownik", | ||||||
|   "administrator": "Administrator", |   "administrator": "Administrator", | ||||||
|   "technical_approver": "Techniczny klepacz", |   "technical_approver": "Techniczny akceptujący", | ||||||
|   "administrative_approver": "Administracyjny klepacz", |   "administrative_approver": "Administracyjny akceptujący", | ||||||
|   "created": "Utworzony", |   "created": "Utworzony", | ||||||
|   "canceled": "Anulowany", |   "canceled": "Anulowany", | ||||||
|   "rejected": "Odrzucony", |   "rejected": "Odrzucony", | ||||||
| @@ -25,5 +25,10 @@ | |||||||
|   "waiting_for_technical": "Czeka na akceptację od technicznego", |   "waiting_for_technical": "Czeka na akceptację od technicznego", | ||||||
|   "waiting_for_administrative": "Czeka na akceptację od administracyjnego", |   "waiting_for_administrative": "Czeka na akceptację od administracyjnego", | ||||||
|   "accepted_by_technical": "Zaakceptowany przez technicznego", |   "accepted_by_technical": "Zaakceptowany przez technicznego", | ||||||
|   "accepted_by_administrative": "Zaakceptowany przez administracyjnego" |   "accepted_by_administrative": "Zaakceptowany przez administracyjnego", | ||||||
|  |   "You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.", | ||||||
|  |   "You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.", | ||||||
|  |   "You have exceeded your vacation limit.": "Przekroczyłeś/aś limit urlopu.", | ||||||
|  |   "Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.", | ||||||
|  |   "The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku." | ||||||
| } | } | ||||||
|   | |||||||
| @@ -114,4 +114,15 @@ return [ | |||||||
|     "uploaded" => "Nie udało się wgrać pliku :attribute.", |     "uploaded" => "Nie udało się wgrać pliku :attribute.", | ||||||
|     "url" => "Format pola :attribute jest nieprawidłowy.", |     "url" => "Format pola :attribute jest nieprawidłowy.", | ||||||
|     "uuid" => "Pole :attribute musi być poprawnym identyfikatorem UUID.", |     "uuid" => "Pole :attribute musi być poprawnym identyfikatorem UUID.", | ||||||
|  |     "attributes" => [ | ||||||
|  |         "to" => "do", | ||||||
|  |         "from" => "od", | ||||||
|  |         "firstName" => "imię", | ||||||
|  |         "lastName" => "nazwisko", | ||||||
|  |         "email" => "e-mail", | ||||||
|  |         "position" => "stanowisko", | ||||||
|  |         "employmentDate" => "data zatrudnienia", | ||||||
|  |         "date" => "data", | ||||||
|  |         "name" => "nazwa", | ||||||
|  |     ], | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -74,7 +74,7 @@ | |||||||
|         <h2>Wniosek o urlop</h2> |         <h2>Wniosek o urlop</h2> | ||||||
|         <p class="content"> |         <p class="content"> | ||||||
|             Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }} |             Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }} | ||||||
|             do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. x dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}. |             do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. {{ $vacationRequest->estimated_days }} dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}. | ||||||
|         </p> |         </p> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,10 @@ | |||||||
| <?php | <?php | ||||||
|  |  | ||||||
| declare(strict_types=1); | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Facades\Route; | ||||||
|  | use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController; | ||||||
|  |  | ||||||
|  | Route::middleware("auth:sanctum")->group(function (): void { | ||||||
|  |     Route::post("calculate-vacation-days", CalculateVacationDaysController::class); | ||||||
|  | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user