#36 - wip
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,22 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use Toby\Domain\VacationTypeConfigRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
class DoesNotExceedLimitRule extends VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function check(VacationRequest $vacationRequest, Closure $next)
 | 
			
		||||
    public function __construct(protected VacationTypeConfigRetriever $configRetriever)
 | 
			
		||||
    {
 | 
			
		||||
        return $next($vacationRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function passes(VacationRequest $vacationRequest): bool
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function errorMessage(): string
 | 
			
		||||
    {
 | 
			
		||||
        return __("You have exceeded your vacation limit.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,24 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use Toby\Domain\VacationDaysCalculator;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class MinimumOneVacationDayRule implements VacationRequestRule
 | 
			
		||||
class MinimumOneVacationDayRule extends VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function check(VacationRequest $vacationRequest, Closure $next)
 | 
			
		||||
    public function __construct(protected VacationDaysCalculator $vacationDaysCalculator)
 | 
			
		||||
    {
 | 
			
		||||
        return $next($vacationRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function passes(VacationRequest $vacationRequest): bool
 | 
			
		||||
    {
 | 
			
		||||
        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 extends VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function passes(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 extends VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function passes(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 extends VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function passes(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,18 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Closure;
 | 
			
		||||
use Illuminate\Validation\ValidationException;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
interface VacationRequestRule
 | 
			
		||||
abstract class VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function check(VacationRequest $vacationRequest, Closure $next);
 | 
			
		||||
    public function check(VacationRequest $vacationRequest): void
 | 
			
		||||
    {
 | 
			
		||||
        if (! $this->passes($vacationRequest)) {
 | 
			
		||||
            throw ValidationException::withMessages(["vacationRequest" => $this->errorMessage()]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public abstract function passes(VacationRequest $vacationRequest): bool;
 | 
			
		||||
    public abstract function errorMessage(): string;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,19 +5,21 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Domain\Validation;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Pipeline\Pipeline;
 | 
			
		||||
use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange;
 | 
			
		||||
use Toby\Domain\Validation\Rules\NoApprovedVacationRequestsInRange;
 | 
			
		||||
use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule;
 | 
			
		||||
use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule;
 | 
			
		||||
use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange;
 | 
			
		||||
use Toby\Domain\Validation\Rules\NoPendingVacationRequestInRange;
 | 
			
		||||
use Toby\Domain\Validation\Rules\VacationRangeIsInTheSameYearRule;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestValidator
 | 
			
		||||
{
 | 
			
		||||
    protected array $rules = [
 | 
			
		||||
        VacationRangeIsInTheSameYearRule::class,
 | 
			
		||||
        MinimumOneVacationDayRule::class,
 | 
			
		||||
        DoesNotExceedLimitRule::class,
 | 
			
		||||
        PendingVacationRequestInSameRange::class,
 | 
			
		||||
        ApprovedVacationDaysInSameRange::class,
 | 
			
		||||
        NoPendingVacationRequestInRange::class,
 | 
			
		||||
        NoApprovedVacationRequestsInRange::class,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
@@ -27,9 +29,8 @@ class VacationRequestValidator
 | 
			
		||||
 | 
			
		||||
    public function validate(VacationRequest $vacationRequest): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->pipeline
 | 
			
		||||
            ->send($vacationRequest)
 | 
			
		||||
            ->through($this->rules)
 | 
			
		||||
            ->via("check");
 | 
			
		||||
        foreach ($this->rules as $rule) {
 | 
			
		||||
            app($rule)->check($vacationRequest);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Eloquent\Models;
 | 
			
		||||
 | 
			
		||||
use Carbon\CarbonInterface;
 | 
			
		||||
use Database\Factories\VacationRequestFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
@@ -68,6 +69,12 @@ class VacationRequest extends Model
 | 
			
		||||
        return $query->whereIn("state", $states);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scopeOverlapsWith(Builder $query, VacationRequest $vacationRequest): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $query->where("from", '<=', $vacationRequest->to)
 | 
			
		||||
            ->where("to", '>=', $vacationRequest->from);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static function newFactory(): VacationRequestFactory
 | 
			
		||||
    {
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Barryvdh\DomPDF\Facade\Pdf;
 | 
			
		||||
use Carbon\Carbon;
 | 
			
		||||
use Illuminate\Http\RedirectResponse;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response as LaravelResponse;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,5 +10,5 @@ return [
 | 
			
		||||
    "allowed_headers" => ["*"],
 | 
			
		||||
    "exposed_headers" => [],
 | 
			
		||||
    "max_age" => 0,
 | 
			
		||||
    "supports_credentials" => false,
 | 
			
		||||
    "supports_credentials" => true,
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,8 @@ class VacationRequestFactory extends Factory
 | 
			
		||||
            ->whereYear("from", $year)
 | 
			
		||||
            ->count() + 1;
 | 
			
		||||
 | 
			
		||||
        dump($user->vacationRequests()->count());
 | 
			
		||||
 | 
			
		||||
        return "{$number}/{$year}";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										37
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "application",
 | 
			
		||||
    "name": "toby",
 | 
			
		||||
    "lockfileVersion": 2,
 | 
			
		||||
    "requires": true,
 | 
			
		||||
    "packages": {
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
                "@tailwindcss/typography": "^0.5.0",
 | 
			
		||||
                "@vue/compiler-sfc": "^3.2.26",
 | 
			
		||||
                "autoprefixer": "^10.4.2",
 | 
			
		||||
                "axios": "^0.25.0",
 | 
			
		||||
                "echarts": "^5.2.2",
 | 
			
		||||
                "flatpickr": "^4.6.9",
 | 
			
		||||
                "laravel-mix": "^6.0.6",
 | 
			
		||||
@@ -1741,6 +1742,14 @@
 | 
			
		||||
                "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": {
 | 
			
		||||
            "version": "0.2.7",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/@inertiajs/progress/-/progress-0.2.7.tgz",
 | 
			
		||||
@@ -2642,11 +2651,11 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/axios": {
 | 
			
		||||
            "version": "0.21.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
 | 
			
		||||
            "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
 | 
			
		||||
            "version": "0.25.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
 | 
			
		||||
            "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
 | 
			
		||||
            "dependencies": {
 | 
			
		||||
                "follow-redirects": "^1.14.0"
 | 
			
		||||
                "follow-redirects": "^1.14.7"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "node_modules/babel-loader": {
 | 
			
		||||
@@ -10664,6 +10673,16 @@
 | 
			
		||||
                "axios": "^0.21.1",
 | 
			
		||||
                "deepmerge": "^4.0.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": {
 | 
			
		||||
@@ -11468,11 +11487,11 @@
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "axios": {
 | 
			
		||||
            "version": "0.21.4",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
 | 
			
		||||
            "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
 | 
			
		||||
            "version": "0.25.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
 | 
			
		||||
            "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
 | 
			
		||||
            "requires": {
 | 
			
		||||
                "follow-redirects": "^1.14.0"
 | 
			
		||||
                "follow-redirects": "^1.14.7"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "babel-loader": {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
        "@tailwindcss/typography": "^0.5.0",
 | 
			
		||||
        "@vue/compiler-sfc": "^3.2.26",
 | 
			
		||||
        "autoprefixer": "^10.4.2",
 | 
			
		||||
        "axios": "^0.25.0",
 | 
			
		||||
        "echarts": "^5.2.2",
 | 
			
		||||
        "flatpickr": "^4.6.9",
 | 
			
		||||
        "laravel-mix": "^6.0.6",
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,26 @@
 | 
			
		||||
      class="border-t border-gray-200 px-6"
 | 
			
		||||
      @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
 | 
			
		||||
        v-model="form.vacationType"
 | 
			
		||||
        v-model="form.type"
 | 
			
		||||
        as="div"
 | 
			
		||||
        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">
 | 
			
		||||
          <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 }"
 | 
			
		||||
            :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">
 | 
			
		||||
              <SelectorIcon class="h-5 w-5 text-gray-400" />
 | 
			
		||||
            </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"
 | 
			
		||||
            >
 | 
			
		||||
              <ListboxOption
 | 
			
		||||
                v-for="vacationType in vacationTypes"
 | 
			
		||||
                :key="vacationType.value"
 | 
			
		||||
                v-for="type in vacationTypes"
 | 
			
		||||
                :key="type.value"
 | 
			
		||||
                v-slot="{ active, selected }"
 | 
			
		||||
                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']">
 | 
			
		||||
                  <span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
 | 
			
		||||
                    {{ vacationType.label }}
 | 
			
		||||
                    {{ type.label }}
 | 
			
		||||
                  </span>
 | 
			
		||||
 | 
			
		||||
                  <span
 | 
			
		||||
@@ -60,10 +78,10 @@
 | 
			
		||||
            </ListboxOptions>
 | 
			
		||||
          </transition>
 | 
			
		||||
          <p
 | 
			
		||||
            v-if="form.errors.vacationType"
 | 
			
		||||
            v-if="form.errors.type"
 | 
			
		||||
            class="mt-2 text-sm text-red-600"
 | 
			
		||||
          >
 | 
			
		||||
            {{ form.errors.vacationType }}
 | 
			
		||||
            {{ form.errors.type }}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Listbox>
 | 
			
		||||
@@ -77,18 +95,18 @@
 | 
			
		||||
        <div class="mt-1 sm:mt-0 sm:col-span-2">
 | 
			
		||||
          <FlatPickr
 | 
			
		||||
            id="date_from"
 | 
			
		||||
            v-model="form.dateFrom"
 | 
			
		||||
            v-model="form.from"
 | 
			
		||||
            :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 }"
 | 
			
		||||
            :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"
 | 
			
		||||
          />
 | 
			
		||||
          <p
 | 
			
		||||
            v-if="form.errors.dateFrom"
 | 
			
		||||
            v-if="form.errors.from"
 | 
			
		||||
            class="mt-2 text-sm text-red-600"
 | 
			
		||||
          >
 | 
			
		||||
            {{ form.errors.dateFrom }}
 | 
			
		||||
            {{ form.errors.from }}
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -102,25 +120,25 @@
 | 
			
		||||
        <div class="mt-1 sm:mt-0 sm:col-span-2">
 | 
			
		||||
          <FlatPickr
 | 
			
		||||
            id="date_to"
 | 
			
		||||
            v-model="form.dateTo"
 | 
			
		||||
            v-model="form.to"
 | 
			
		||||
            :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 }"
 | 
			
		||||
            :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"
 | 
			
		||||
          />
 | 
			
		||||
          <p
 | 
			
		||||
            v-if="form.errors.dateTo"
 | 
			
		||||
            v-if="form.errors.to"
 | 
			
		||||
            class="mt-2 text-sm text-red-600"
 | 
			
		||||
          >
 | 
			
		||||
            {{ form.errors.dateTo }}
 | 
			
		||||
            {{ form.errors.to }}
 | 
			
		||||
          </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
 | 
			
		||||
          {{ estimatedDays.length }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="sm:grid sm:grid-cols-3 py-4 items-center">
 | 
			
		||||
@@ -164,8 +182,9 @@
 | 
			
		||||
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'
 | 
			
		||||
import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid'
 | 
			
		||||
import {reactive, ref} from 'vue'
 | 
			
		||||
import axios from 'axios'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'VacationRequestCreate',
 | 
			
		||||
@@ -178,6 +197,7 @@ export default {
 | 
			
		||||
    ListboxOptions,
 | 
			
		||||
    CheckIcon,
 | 
			
		||||
    SelectorIcon,
 | 
			
		||||
    XCircleIcon,
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    vacationTypes: {
 | 
			
		||||
@@ -191,12 +211,14 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  setup(props) {
 | 
			
		||||
    const form = useForm({
 | 
			
		||||
      dateFrom: null,
 | 
			
		||||
      dateTo: null,
 | 
			
		||||
      vacationType: props.vacationTypes[0],
 | 
			
		||||
      from: null,
 | 
			
		||||
      to: null,
 | 
			
		||||
      type: props.vacationTypes[0],
 | 
			
		||||
      comment: null,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const estimatedDays = ref([])
 | 
			
		||||
 | 
			
		||||
    const disableDates = [
 | 
			
		||||
      date => (date.getDay() === 0 || date.getDay() === 6),
 | 
			
		||||
    ]
 | 
			
		||||
@@ -213,6 +235,7 @@ export default {
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      form,
 | 
			
		||||
      estimatedDays,
 | 
			
		||||
      fromInputConfig,
 | 
			
		||||
      toInputConfig,
 | 
			
		||||
    }
 | 
			
		||||
@@ -221,18 +244,26 @@ export default {
 | 
			
		||||
    createForm() {
 | 
			
		||||
      this.form
 | 
			
		||||
        .transform(data => ({
 | 
			
		||||
          from: data.dateFrom,
 | 
			
		||||
          to: data.dateTo,
 | 
			
		||||
          type: data.vacationType.value,
 | 
			
		||||
          comment: data.comment,
 | 
			
		||||
          ...data,
 | 
			
		||||
          type: data.type.value,
 | 
			
		||||
        }))
 | 
			
		||||
        .post('/vacation-requests')
 | 
			
		||||
    },
 | 
			
		||||
    onFromChange(selectedDates, dateStr) {
 | 
			
		||||
      this.toInputConfig.minDate = dateStr
 | 
			
		||||
 | 
			
		||||
      this.refreshEstimatedDays(this.form.from, this.form.to)
 | 
			
		||||
    },
 | 
			
		||||
    onToChange(selectedDates, 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)
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
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