* change layout * change layout * #22 - wip * wip * wip * #22 - wip * #22 - wip * #22 - wip * #22 - wip * #22 - fix * #22 - wip * #22 - added some tests * #22 - wip * #22 - wip * #22 - fix * #22 - wip * #22 - wip * #22 - wip * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #22 - fix * #22 - cr fixes * #22 - cr fix Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
parent
b161981d5a
commit
6c352b629c
80
app/Domain/CalendarGenerator.php
Normal file
80
app/Domain/CalendarGenerator.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain;
|
||||||
|
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
|
use Carbon\CarbonInterface;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Eloquent\Models\Vacation;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
|
class CalendarGenerator
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected YearPeriodRetriever $yearPeriodRetriever,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(YearPeriod $yearPeriod, string $month): array
|
||||||
|
{
|
||||||
|
$date = CarbonImmutable::create($yearPeriod->year, $this->monthNameToNumber($month));
|
||||||
|
$period = CarbonPeriod::create($date->startOfMonth(), $date->endOfMonth());
|
||||||
|
$holidays = $yearPeriod->holidays()->pluck("date");
|
||||||
|
|
||||||
|
return $this->generateCalendar($period, $holidays);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function monthNameToNumber($name): int
|
||||||
|
{
|
||||||
|
return match ($name) {
|
||||||
|
default => CarbonInterface::JANUARY,
|
||||||
|
"february" => CarbonInterface::FEBRUARY,
|
||||||
|
"march" => CarbonInterface::MARCH,
|
||||||
|
"april" => CarbonInterface::APRIL,
|
||||||
|
"may" => CarbonInterface::MAY,
|
||||||
|
"june" => CarbonInterface::JUNE,
|
||||||
|
"july" => CarbonInterface::JULY,
|
||||||
|
"august" => CarbonInterface::AUGUST,
|
||||||
|
"september" => CarbonInterface::SEPTEMBER,
|
||||||
|
"october" => CarbonInterface::OCTOBER,
|
||||||
|
"november" => CarbonInterface::NOVEMBER,
|
||||||
|
"december" => CarbonInterface::DECEMBER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateCalendar(CarbonPeriod $period, Collection $holidays): array
|
||||||
|
{
|
||||||
|
$calendar = [];
|
||||||
|
$vacations = $this->getVacationsForPeriod($period);
|
||||||
|
|
||||||
|
foreach ($period as $day) {
|
||||||
|
$vacationsForDay = $vacations[$day->toDateString()] ?? new Collection();
|
||||||
|
|
||||||
|
$calendar[] = [
|
||||||
|
"date" => $day->toDateString(),
|
||||||
|
"dayOfMonth" => $day->translatedFormat("j"),
|
||||||
|
"dayOfWeek" => $day->translatedFormat("D"),
|
||||||
|
"isToday" => $day->isToday(),
|
||||||
|
"isWeekend" => $day->isWeekend(),
|
||||||
|
"isHoliday" => $holidays->contains($day),
|
||||||
|
"vacations" => $vacationsForDay->pluck("user_id"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getVacationsForPeriod(CarbonPeriod $period): Collection
|
||||||
|
{
|
||||||
|
return Vacation::query()
|
||||||
|
->whereBetween("date", [$period->start, $period->end])
|
||||||
|
->whereRelation("vacationRequest", "state", VacationRequestState::Approved->value)
|
||||||
|
->get()
|
||||||
|
->groupBy(fn(Vacation $vacation) => $vacation->date->toDateString());
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,10 @@ namespace Toby\Domain\Enums;
|
|||||||
|
|
||||||
enum EmploymentForm: string
|
enum EmploymentForm: string
|
||||||
{
|
{
|
||||||
case EMPLOYMENT_CONTRACT = "employment_contract";
|
case EmploymentContract = "employment_contract";
|
||||||
case COMMISSION_CONTRACT = "commission_contract";
|
case ComissionContract = "commission_contract";
|
||||||
case B2B_CONTRACT = "b2b_contract";
|
case B2bContract = "b2b_contract";
|
||||||
case BOARD_MEMBER_CONTRACT = "board_member_contract";
|
case BoardMemberContract = "board_member_contract";
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
|
@ -6,10 +6,10 @@ namespace Toby\Domain\Enums;
|
|||||||
|
|
||||||
enum Role: string
|
enum Role: string
|
||||||
{
|
{
|
||||||
case EMPLOYEE = "employee";
|
case Employee = "employee";
|
||||||
case ADMINISTRATOR = "administrator";
|
case Administrator = "administrator";
|
||||||
case TECHNICAL_APPROVER = "technical_approver";
|
case TechnicalApprover = "technical_approver";
|
||||||
case ADMINISTRATIVE_APPROVER = "administrative_approver";
|
case AdministrativeApprover = "administrative_approver";
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
|
@ -6,14 +6,14 @@ namespace Toby\Domain\Enums;
|
|||||||
|
|
||||||
enum VacationRequestState: string
|
enum VacationRequestState: string
|
||||||
{
|
{
|
||||||
case CREATED = "created";
|
case Created = "created";
|
||||||
case CANCELED = "canceled";
|
case Canceled = "canceled";
|
||||||
case REJECTED = "rejected";
|
case Rejected = "rejected";
|
||||||
case APPROVED = "approved";
|
case Approved = "approved";
|
||||||
case WAITING_FOR_TECHNICAL = "waiting_for_technical";
|
case WaitingForTechnical = "waiting_for_technical";
|
||||||
case WAITING_FOR_ADMINISTRATIVE = "waiting_for_administrative";
|
case WaitingForAdministrative = "waiting_for_administrative";
|
||||||
case ACCEPTED_BY_TECHNICAL = "accepted_by_technical";
|
case AcceptedByTechnical = "accepted_by_technical";
|
||||||
case ACCEPTED_BY_ADMINSTRATIVE = "accepted_by_administrative";
|
case AcceptedByAdministrative = "accepted_by_administrative";
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
@ -23,24 +23,24 @@ enum VacationRequestState: string
|
|||||||
public static function pendingStates(): array
|
public static function pendingStates(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::CREATED,
|
self::Created,
|
||||||
self::WAITING_FOR_TECHNICAL,
|
self::WaitingForTechnical,
|
||||||
self::WAITING_FOR_ADMINISTRATIVE,
|
self::WaitingForAdministrative,
|
||||||
self::ACCEPTED_BY_TECHNICAL,
|
self::AcceptedByTechnical,
|
||||||
self::ACCEPTED_BY_ADMINSTRATIVE,
|
self::AcceptedByAdministrative,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function successStates(): array
|
public static function successStates(): array
|
||||||
{
|
{
|
||||||
return [self::APPROVED];
|
return [self::Approved];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function failedStates(): array
|
public static function failedStates(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
self::REJECTED,
|
self::Rejected,
|
||||||
self::CANCELED,
|
self::Canceled,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@ namespace Toby\Domain\Enums;
|
|||||||
|
|
||||||
enum VacationType: string
|
enum VacationType: string
|
||||||
{
|
{
|
||||||
case VACATION = "vacation";
|
case Vacation = "vacation";
|
||||||
case VACATION_ON_REQUEST = "vacation_on_request";
|
case OnRequest = "vacation_on_request";
|
||||||
case SPECIAL_VACATION = "special_vacation";
|
case Special = "special_vacation";
|
||||||
case CHILDCARE_VACATION = "childcare_vacation";
|
case Childcare = "childcare_vacation";
|
||||||
case TRAINING_VACATION = "training_vacation";
|
case Training = "training_vacation";
|
||||||
case UNPAID_VACATION = "unpaid_vacation";
|
case Unpaid = "unpaid_vacation";
|
||||||
case VOLUNTEERING_VACATION = "volunteering_vacation";
|
case Volunteering = "volunteering_vacation";
|
||||||
case TIME_IN_LIEU = "time_in_lieu";
|
case TimeInLieu = "time_in_lieu";
|
||||||
case SICK_VACATION = "sick_vacation";
|
case Sick = "sick_vacation";
|
||||||
|
|
||||||
public function label(): string
|
public function label(): string
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ class VacationDaysCalculator
|
|||||||
$period = CarbonPeriod::create($from, $to);
|
$period = CarbonPeriod::create($from, $to);
|
||||||
$holidays = $yearPeriod->holidays()->pluck("date");
|
$holidays = $yearPeriod->holidays()->pluck("date");
|
||||||
|
|
||||||
$validDays = collect();
|
$validDays = new Collection();
|
||||||
|
|
||||||
foreach ($period as $day) {
|
foreach ($period as $day) {
|
||||||
if ($this->passes($day, $holidays)) {
|
if ($this->passes($day, $holidays)) {
|
||||||
|
@ -23,50 +23,50 @@ class VacationRequestStateManager
|
|||||||
|
|
||||||
public function markAsCreated(VacationRequest $vacationRequest): void
|
public function markAsCreated(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::CREATED);
|
$this->changeState($vacationRequest, VacationRequestState::Created);
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
|
$this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function approve(VacationRequest $vacationRequest): void
|
public function approve(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::APPROVED);
|
$this->changeState($vacationRequest, VacationRequestState::Approved);
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
|
$this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function reject(VacationRequest $vacationRequest): void
|
public function reject(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::REJECTED);
|
$this->changeState($vacationRequest, VacationRequestState::Rejected);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cancel(VacationRequest $vacationRequest): void
|
public function cancel(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::CANCELED);
|
$this->changeState($vacationRequest, VacationRequestState::Canceled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function acceptAsTechnical(VacationRequest $vacationRequest): void
|
public function acceptAsTechnical(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_TECHNICAL);
|
$this->changeState($vacationRequest, VacationRequestState::AcceptedByTechnical);
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
|
$this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function acceptAsAdministrative(VacationRequest $vacationRequest): void
|
public function acceptAsAdministrative(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_ADMINSTRATIVE);
|
$this->changeState($vacationRequest, VacationRequestState::AcceptedByAdministrative);
|
||||||
|
|
||||||
$this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
|
$this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function waitForTechnical(VacationRequest $vacationRequest): void
|
public function waitForTechnical(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_TECHNICAL);
|
$this->changeState($vacationRequest, VacationRequestState::WaitingForTechnical);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function waitForAdministrative(VacationRequest $vacationRequest): void
|
public function waitForAdministrative(VacationRequest $vacationRequest): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_ADMINISTRATIVE);
|
$this->changeState($vacationRequest, VacationRequestState::WaitingForAdministrative);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void
|
protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void
|
||||||
|
@ -4,23 +4,64 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Toby\Domain\Validation\Rules;
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\VacationDaysCalculator;
|
||||||
use Toby\Domain\VacationTypeConfigRetriever;
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
use Toby\Eloquent\Models\VacationRequest;
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
class DoesNotExceedLimitRule implements VacationRequestRule
|
class DoesNotExceedLimitRule implements VacationRequestRule
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected VacationTypeConfigRetriever $configRetriever,
|
protected VacationTypeConfigRetriever $configRetriever,
|
||||||
|
protected VacationDaysCalculator $vacationDaysCalculator,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function check(VacationRequest $vacationRequest): bool
|
public function check(VacationRequest $vacationRequest): bool
|
||||||
{
|
{
|
||||||
|
if (!$this->configRetriever->hasLimit($vacationRequest->type)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod);
|
||||||
|
$vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod);
|
||||||
|
$estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count();
|
||||||
|
|
||||||
|
return $limit >= ($vacationDays + $estimatedDays);
|
||||||
|
}
|
||||||
|
|
||||||
public function errorMessage(): string
|
public function errorMessage(): string
|
||||||
{
|
{
|
||||||
return __("You have exceeded your vacation limit.");
|
return __("Vacation limit has been exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getUserVacationLimit(User $user, YearPeriod $yearPeriod): int
|
||||||
|
{
|
||||||
|
return $user->vacationLimits()->where("year_period_id", $yearPeriod->id)->first()->days ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getVacationDaysWithLimit(User $user, YearPeriod $yearPeriod): int
|
||||||
|
{
|
||||||
|
return $user->vacations()
|
||||||
|
->where("year_period_id", $yearPeriod->id)
|
||||||
|
->whereRelation(
|
||||||
|
"vacationRequest",
|
||||||
|
fn(Builder $query) => $query
|
||||||
|
->whereIn("type", $this->getLimitableVacationTypes())
|
||||||
|
->noStates(VacationRequestState::failedStates()),
|
||||||
|
)
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLimitableVacationTypes(): Collection
|
||||||
|
{
|
||||||
|
$types = new Collection(VacationType::cases());
|
||||||
|
|
||||||
|
return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ use Toby\Domain\Enums\Role;
|
|||||||
* @property Carbon $employment_date
|
* @property Carbon $employment_date
|
||||||
* @property Collection $vacationLimits
|
* @property Collection $vacationLimits
|
||||||
* @property Collection $vacationRequests
|
* @property Collection $vacationRequests
|
||||||
|
* @property Collection $vacations
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
@ -57,6 +58,11 @@ class User extends Authenticatable
|
|||||||
return $this->hasMany(VacationRequest::class);
|
return $this->hasMany(VacationRequest::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function vacations(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Vacation::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeSearch(Builder $query, ?string $text): Builder
|
public function scopeSearch(Builder $query, ?string $text): Builder
|
||||||
{
|
{
|
||||||
if ($text === null) {
|
if ($text === null) {
|
||||||
|
43
app/Eloquent/Models/Vacation.php
Normal file
43
app/Eloquent/Models/Vacation.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Eloquent\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property Carbon $date
|
||||||
|
* @property User $user
|
||||||
|
* @property VacationRequest $vacationRequest
|
||||||
|
* @property YearPeriod $yearPeriod
|
||||||
|
*/
|
||||||
|
class Vacation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
protected $guarded = [];
|
||||||
|
protected $casts = [
|
||||||
|
"date" => "date",
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function vacationRequest(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(VacationRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function yearPeriod(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(YearPeriod::class);
|
||||||
|
}
|
||||||
|
}
|
@ -21,11 +21,11 @@ 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
|
||||||
* @property Collection $activities
|
* @property Collection $activities
|
||||||
|
* @property Collection $vacations
|
||||||
* @property Carbon $created_at
|
* @property Carbon $created_at
|
||||||
* @property Carbon $updated_at
|
* @property Carbon $updated_at
|
||||||
*/
|
*/
|
||||||
@ -57,6 +57,11 @@ class VacationRequest extends Model
|
|||||||
return $this->hasMany(VacationRequestActivity::class);
|
return $this->hasMany(VacationRequestActivity::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function vacations(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Vacation::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function changeStateTo(VacationRequestState $state): void
|
public function changeStateTo(VacationRequestState $state): void
|
||||||
{
|
{
|
||||||
$this->state = $state;
|
$this->state = $state;
|
||||||
@ -69,6 +74,11 @@ class VacationRequest extends Model
|
|||||||
return $query->whereIn("state", $states);
|
return $query->whereIn("state", $states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function scopeNoStates(Builder $query, array $states): Builder
|
||||||
|
{
|
||||||
|
return $query->whereNotIn("state", $states);
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder
|
public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder
|
||||||
{
|
{
|
||||||
return $query->where("from", "<=", $vacationRequest->to)
|
return $query->where("from", "<=", $vacationRequest->to)
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Toby\Domain\CalendarGenerator;
|
||||||
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Infrastructure\Http\Resources\UserResource;
|
||||||
|
|
||||||
|
class VacationCalendarController extends Controller
|
||||||
|
{
|
||||||
|
public function index(
|
||||||
|
Request $request,
|
||||||
|
YearPeriodRetriever $yearPeriodRetriever,
|
||||||
|
CalendarGenerator $calendarGenerator,
|
||||||
|
): Response {
|
||||||
|
$month = Str::lower($request->query("month", Carbon::now()->englishMonth));
|
||||||
|
$yearPeriod = $yearPeriodRetriever->selected();
|
||||||
|
$users = User::query()
|
||||||
|
->orderBy("last_name")
|
||||||
|
->orderBy("first_name")
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$calendar = $calendarGenerator->generate($yearPeriod, $month);
|
||||||
|
|
||||||
|
return inertia("Calendar", [
|
||||||
|
"calendar" => $calendar,
|
||||||
|
"currentMonth" => $month,
|
||||||
|
"users" => UserResource::collection($users),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -24,16 +24,21 @@ class VacationRequestController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
|
public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
|
||||||
{
|
{
|
||||||
|
$status = $request->get("status", "all");
|
||||||
|
|
||||||
$vacationRequests = $request->user()
|
$vacationRequests = $request->user()
|
||||||
->vacationRequests()
|
->vacationRequests()
|
||||||
|
->with("vacations")
|
||||||
->where("year_period_id", $yearPeriodRetriever->selected()->id)
|
->where("year_period_id", $yearPeriodRetriever->selected()->id)
|
||||||
->latest()
|
->latest()
|
||||||
->states(VacationRequestState::filterByStatus($request->query("status", "all")))
|
->states(VacationRequestState::filterByStatus($status))
|
||||||
->paginate();
|
->paginate();
|
||||||
|
|
||||||
return inertia("VacationRequest/Index", [
|
return inertia("VacationRequest/Index", [
|
||||||
"requests" => VacationRequestResource::collection($vacationRequests),
|
"requests" => VacationRequestResource::collection($vacationRequests),
|
||||||
"filters" => $request->only("status"),
|
"filters" => [
|
||||||
|
"status" => $status,
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,15 +74,24 @@ class VacationRequestController extends Controller
|
|||||||
): 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);
|
||||||
|
|
||||||
$vacationRequest->save();
|
$vacationRequest->save();
|
||||||
|
|
||||||
|
$days = $vacationDaysCalculator->calculateDays(
|
||||||
|
$vacationRequest->yearPeriod,
|
||||||
|
$vacationRequest->from,
|
||||||
|
$vacationRequest->to,
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($days as $day) {
|
||||||
|
$vacationRequest->vacations()->create([
|
||||||
|
"date" => $day,
|
||||||
|
"user_id" => $vacationRequest->user->id,
|
||||||
|
"year_period_id" => $vacationRequest->yearPeriod->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$stateManager->markAsCreated($vacationRequest);
|
$stateManager->markAsCreated($vacationRequest);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
|
@ -13,9 +13,11 @@ class VacationRequestActivityResource extends JsonResource
|
|||||||
public function toArray($request): array
|
public function toArray($request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"date" => $this->created_at->toDisplayString(),
|
"date" => $this->created_at->format("d.m.Y"),
|
||||||
"who" => $this->user ? $this->user->fullName : __("System"),
|
"time" => $this->created_at->format("H:i"),
|
||||||
"to" => $this->to->label(),
|
"user" => $this->user ? $this->user->fullName : __("System"),
|
||||||
|
"state" => $this->to,
|
||||||
|
"text" => $this->to->label(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,11 @@ class VacationRequestResource extends JsonResource
|
|||||||
"name" => $this->name,
|
"name" => $this->name,
|
||||||
"user" => new UserResource($this->user),
|
"user" => new UserResource($this->user),
|
||||||
"type" => $this->type->label(),
|
"type" => $this->type->label(),
|
||||||
"state" => $this->state->label(),
|
"state" => $this->state,
|
||||||
"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,
|
||||||
|
"days" => VacationResource::collection($this->vacations),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
app/Infrastructure/Http/Resources/VacationResource.php
Normal file
21
app/Infrastructure/Http/Resources/VacationResource.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class VacationResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"id" => $this->id,
|
||||||
|
"displayDate" => $this->date->toDisplayString(),
|
||||||
|
"date" => $this->date->toDateString(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -6,55 +6,55 @@ use Toby\Domain\Enums\VacationType;
|
|||||||
use Toby\Domain\VacationTypeConfigRetriever;
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
VacationType::VACATION->value => [
|
VacationType::Vacation->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
||||||
],
|
],
|
||||||
VacationType::VACATION_ON_REQUEST->value => [
|
VacationType::OnRequest->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
||||||
],
|
],
|
||||||
VacationType::TIME_IN_LIEU->value => [
|
VacationType::TimeInLieu->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::SICK_VACATION->value => [
|
VacationType::Sick->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::UNPAID_VACATION->value => [
|
VacationType::Unpaid->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::SPECIAL_VACATION->value => [
|
VacationType::Special->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::CHILDCARE_VACATION->value => [
|
VacationType::Childcare->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::TRAINING_VACATION->value => [
|
VacationType::Training->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
],
|
],
|
||||||
VacationType::VOLUNTEERING_VACATION->value => [
|
VacationType::Volunteering->value => [
|
||||||
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
@ -23,7 +23,7 @@ class UserFactory extends Factory
|
|||||||
"email" => $this->faker->unique()->safeEmail(),
|
"email" => $this->faker->unique()->safeEmail(),
|
||||||
"employment_form" => $this->faker->randomElement(EmploymentForm::cases()),
|
"employment_form" => $this->faker->randomElement(EmploymentForm::cases()),
|
||||||
"position" => $this->faker->jobTitle(),
|
"position" => $this->faker->jobTitle(),
|
||||||
"role" => Role::EMPLOYEE,
|
"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),
|
||||||
];
|
];
|
||||||
|
15
database/factories/VacationFactory.php
Normal file
15
database/factories/VacationFactory.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
class VacationFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ 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;
|
||||||
@ -31,7 +30,6 @@ 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,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -43,11 +41,4 @@ class VacationRequestFactory extends Factory
|
|||||||
|
|
||||||
return "{$number}/{$year}";
|
return "{$number}/{$year}";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function estimateDays(array $attributes): int
|
|
||||||
{
|
|
||||||
$period = CarbonPeriod::create($attributes["from"], $attributes["to"]);
|
|
||||||
|
|
||||||
return $period->count();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +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("role")->default(Role::Employee->value);
|
||||||
$table->string("position");
|
$table->string("position");
|
||||||
$table->string("employment_form");
|
$table->string("employment_form");
|
||||||
$table->date("employment_date");
|
$table->date("employment_date");
|
||||||
|
@ -18,7 +18,6 @@ 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();
|
||||||
|
@ -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;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
|
return new class() extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create("vacations", function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignIdFor(VacationRequest::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->date("date");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists("vacations");
|
||||||
|
}
|
||||||
|
};
|
@ -8,6 +8,7 @@ use Illuminate\Database\Seeder;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Toby\Domain\PolishHolidaysRetriever;
|
use Toby\Domain\PolishHolidaysRetriever;
|
||||||
|
use Toby\Domain\VacationDaysCalculator;
|
||||||
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;
|
||||||
@ -79,6 +80,21 @@ class DatabaseSeeder extends Seeder
|
|||||||
->sequence(fn() => [
|
->sequence(fn() => [
|
||||||
"year_period_id" => $yearPeriods->random()->id,
|
"year_period_id" => $yearPeriods->random()->id,
|
||||||
])
|
])
|
||||||
|
->afterCreating(function (VacationRequest $vacationRequest): void {
|
||||||
|
$days = app(VacationDaysCalculator::class)->calculateDays(
|
||||||
|
$vacationRequest->yearPeriod,
|
||||||
|
$vacationRequest->from,
|
||||||
|
$vacationRequest->to,
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($days as $day) {
|
||||||
|
$vacationRequest->vacations()->create([
|
||||||
|
"date" => $day,
|
||||||
|
"user_id" => $vacationRequest->user->id,
|
||||||
|
"year_period_id" => $vacationRequest->yearPeriod->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
})
|
||||||
->create();
|
->create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
60
resources/js/Composables/monthInfo.js
Normal file
60
resources/js/Composables/monthInfo.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const months = [
|
||||||
|
{
|
||||||
|
'name': 'Styczeń',
|
||||||
|
'value': 'january',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Luty',
|
||||||
|
'value': 'february',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Marzec',
|
||||||
|
'value': 'march',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Kwiecień',
|
||||||
|
'value': 'april',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Maj',
|
||||||
|
'value': 'may',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Czerwiec',
|
||||||
|
'value': 'june',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Lipiec',
|
||||||
|
'value': 'july',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Sierpień',
|
||||||
|
'value': 'august',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Wrzesień',
|
||||||
|
'value': 'september',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Październik',
|
||||||
|
'value': 'october',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Listopad',
|
||||||
|
'value': 'november',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': 'Grudzień',
|
||||||
|
'value': 'december',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useMonthInfo() {
|
||||||
|
const getMonths = () => months
|
||||||
|
const findMonth = value => months.find(month => month.value === value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
getMonths,
|
||||||
|
findMonth,
|
||||||
|
}
|
||||||
|
}
|
128
resources/js/Composables/statusInfo.js
Normal file
128
resources/js/Composables/statusInfo.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
CheckIcon as OutlineCheckIcon,
|
||||||
|
ClockIcon as OutlineClockIcon,
|
||||||
|
DocumentTextIcon as OutlineDocumentTextIcon,
|
||||||
|
ThumbDownIcon as OutlineThumbDownIcon,
|
||||||
|
ThumbUpIcon as OutlineThumbUpIcon,
|
||||||
|
XIcon as OutlineXIcon,
|
||||||
|
} from '@heroicons/vue/outline'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CheckIcon as SolidCheckIcon,
|
||||||
|
ClockIcon as SolidClockIcon,
|
||||||
|
DocumentTextIcon as SolidDocumentTextIcon,
|
||||||
|
ThumbDownIcon as SolidThumbDownIcon,
|
||||||
|
ThumbUpIcon as SolidThumbUpIcon,
|
||||||
|
XIcon as SolidXIcon,
|
||||||
|
} from '@heroicons/vue/solid'
|
||||||
|
|
||||||
|
const statuses = [
|
||||||
|
{
|
||||||
|
text: 'Utworzony',
|
||||||
|
value: 'created',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineDocumentTextIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-gray-400',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidDocumentTextIcon,
|
||||||
|
color: 'text-gray-400',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Czeka na akceptację od przełożonego technicznego',
|
||||||
|
value: 'waiting_for_technical',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineClockIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-amber-400',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidClockIcon,
|
||||||
|
color: 'text-amber-400',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Czeka na akceptację od przełożonego administracyjnego',
|
||||||
|
value: 'waiting_for_administrative',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineClockIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-amber-400',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidClockIcon,
|
||||||
|
color: 'text-amber-400',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Odrzucony',
|
||||||
|
value: 'rejected',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineThumbDownIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-rose-600',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidThumbDownIcon,
|
||||||
|
color: 'text-rose-600',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Zaakceptowany przez przełożonego technicznego',
|
||||||
|
value: 'accepted_by_technical',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineThumbUpIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-green-500',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidThumbUpIcon,
|
||||||
|
color: 'text-green-500',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Zaakceptowany przez przełożonego administracyjnego',
|
||||||
|
value: 'accepted_by_administrative',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineThumbUpIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-green-500',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidThumbUpIcon,
|
||||||
|
color: 'text-green-500',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Zatwierdzony',
|
||||||
|
value: 'approved',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineCheckIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-blumilk-500',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidCheckIcon,
|
||||||
|
color: 'text-blumilk-500',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Anulowany',
|
||||||
|
value: 'canceled',
|
||||||
|
outline: {
|
||||||
|
icon: OutlineXIcon,
|
||||||
|
foreground: 'text-white',
|
||||||
|
background: 'bg-gray-900',
|
||||||
|
},
|
||||||
|
solid: {
|
||||||
|
icon: SolidXIcon,
|
||||||
|
color: 'text-gray-900',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useStatusInfo(status) {
|
||||||
|
return statuses.find(statusInfo => statusInfo.value === status)
|
||||||
|
}
|
167
resources/js/Pages/Calendar.vue
Normal file
167
resources/js/Pages/Calendar.vue
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<InertiaHead title="Kalendarz urlopów" />
|
||||||
|
<div class="bg-white shadow-md">
|
||||||
|
<div class="p-4 sm:px-6">
|
||||||
|
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Kalendarz urlopów
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-center table-fixed text-sm border border-gray-300">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-64 py-2 border border-gray-300">
|
||||||
|
<Menu
|
||||||
|
as="div"
|
||||||
|
class="relative inline-block text-left"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<MenuButton
|
||||||
|
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white 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"
|
||||||
|
>
|
||||||
|
{{ selectedMonth.name }}
|
||||||
|
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" />
|
||||||
|
</MenuButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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-32 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
|
>
|
||||||
|
<div class="py-1">
|
||||||
|
<MenuItem
|
||||||
|
v-for="(month, index) in months"
|
||||||
|
:key="index"
|
||||||
|
v-slot="{ active }"
|
||||||
|
>
|
||||||
|
<InertiaLink
|
||||||
|
href="/vacation-calendar"
|
||||||
|
:data="{ month: month.value }"
|
||||||
|
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex w-full font-normal px-4 py-2 text-sm']"
|
||||||
|
>
|
||||||
|
{{ month.name }}
|
||||||
|
<CheckIcon
|
||||||
|
v-if="currentMonth === month.value"
|
||||||
|
class="h-5 w-5 text-blumilk-500 ml-2"
|
||||||
|
/>
|
||||||
|
</InertiaLink>
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
</MenuItems>
|
||||||
|
</transition>
|
||||||
|
</Menu>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
v-for="day in calendar"
|
||||||
|
:key="day.dayOfMonth"
|
||||||
|
class="border border-gray-300 text-lg font-semibold text-gray-900 py-4 px-2"
|
||||||
|
:class="{ 'text-blumilk-600 bg-blumilk-25 font-black': day.isToday }"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{{ day.dayOfMonth }}
|
||||||
|
<p class="font-normal mt-1 text-sm capitalize">
|
||||||
|
{{ day.dayOfWeek }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="user in users.data"
|
||||||
|
:key="user.id"
|
||||||
|
>
|
||||||
|
<th class="border border-gray-300 py-2 px-4">
|
||||||
|
<div class="flex justify-start items-center">
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ml-3">
|
||||||
|
<div class="text-sm font-medium text-gray-900">
|
||||||
|
{{ user.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
v-for="day in calendar"
|
||||||
|
:key="day.dayOfMonth"
|
||||||
|
class="border border-gray-300"
|
||||||
|
:class="{'bg-red-100': day.isWeekend || day.isHoliday, 'bg-blumilk-500': day.vacations.includes(user.id) }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="day.vacations.includes(user.id)"
|
||||||
|
class="flex justify-center items-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-6 h-6 text-white"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 640 512"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M115.38 136.9l102.11 37.18c35.19-81.54 86.21-144.29 139-173.7-95.88-4.89-188.78 36.96-248.53 111.8-6.69 8.4-2.66 21.05 7.42 24.72zm132.25 48.16l238.48 86.83c35.76-121.38 18.7-231.66-42.63-253.98-7.4-2.7-15.13-4-23.09-4-58.02.01-128.27 69.17-172.76 171.15zM521.48 60.5c6.22 16.3 10.83 34.6 13.2 55.19 5.74 49.89-1.42 108.23-18.95 166.98l102.62 37.36c10.09 3.67 21.31-3.43 21.57-14.17 2.32-95.69-41.91-187.44-118.44-245.36zM560 447.98H321.06L386 269.5l-60.14-21.9-72.9 200.37H16c-8.84 0-16 7.16-16 16.01v32.01C0 504.83 7.16 512 16 512h544c8.84 0 16-7.17 16-16.01v-32.01c0-8.84-7.16-16-16-16z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Menu, MenuButton, MenuItem, MenuItems} from '@headlessui/vue'
|
||||||
|
import {CheckIcon, ChevronDownIcon} from '@heroicons/vue/solid'
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useMonthInfo} from '@/Composables/monthInfo'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationCalendar',
|
||||||
|
components: {
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuItems,
|
||||||
|
CheckIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
users: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
calendar: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
currentMonth: {
|
||||||
|
type: String,
|
||||||
|
default: () => 'january',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const {getMonths, findMonth} = useMonthInfo()
|
||||||
|
const months = getMonths()
|
||||||
|
|
||||||
|
const selectedMonth = computed(() => findMonth(props.currentMonth))
|
||||||
|
|
||||||
|
return {
|
||||||
|
months,
|
||||||
|
selectedMonth,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -37,7 +37,7 @@
|
|||||||
<div class="mt-5 flex justify-center sm:mt-0">
|
<div class="mt-5 flex justify-center sm:mt-0">
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
href="#"
|
href="#"
|
||||||
class="flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
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"
|
||||||
>
|
>
|
||||||
View profile
|
View profile
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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 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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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 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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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 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">
|
||||||
@ -144,7 +144,7 @@ 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,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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 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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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 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
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Użytkownicy" />
|
<InertiaHead title="Użytkownicy" />
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white 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">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Użytkownicy" />
|
<InertiaHead title="Użytkownicy" />
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white 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">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Złóż wniosek urlopowy" />
|
<InertiaHead title="Złóż wniosek urlopowy" />
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white 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">
|
||||||
Złóż wniosek urlopowy
|
Złóż wniosek urlopowy
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Twoje wnioski urlopowe" />
|
<InertiaHead title="Twoje wnioski urlopowe" />
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white 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">
|
||||||
@ -57,7 +57,7 @@
|
|||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-4 py-3 text-right 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"
|
||||||
>
|
>
|
||||||
Dni urlopu
|
Dni urlopu
|
||||||
</th>
|
</th>
|
||||||
@ -84,7 +84,7 @@
|
|||||||
{{ request.name }}
|
{{ request.name }}
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
</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 font-medium text-gray-500">
|
||||||
{{ request.type }}
|
{{ request.type }}
|
||||||
</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">
|
||||||
@ -93,15 +93,18 @@
|
|||||||
<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">
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
{{ request.estimatedDays }}
|
{{ request.days.length }}
|
||||||
</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.state }}
|
<Status :status="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
|
||||||
<ChevronRightIcon class="block w-6 h-6 fill-gray-400" />
|
:href="`/vacation-requests/${request.id}`"
|
||||||
|
class="flex justify-around"
|
||||||
|
>
|
||||||
|
<ChevronRightIcon class="block w-6 h-6 fill-blumilk-500" />
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -169,7 +172,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ChevronRightIcon, DotsVerticalIcon, PencilIcon, TrashIcon} from '@heroicons/vue/solid'
|
import {
|
||||||
|
ChevronRightIcon,
|
||||||
|
ClockIcon,
|
||||||
|
DotsVerticalIcon,
|
||||||
|
PencilIcon,
|
||||||
|
ThumbDownIcon,
|
||||||
|
ThumbUpIcon,
|
||||||
|
TrashIcon,
|
||||||
|
XIcon,
|
||||||
|
CheckIcon,
|
||||||
|
DocumentTextIcon,
|
||||||
|
} from '@heroicons/vue/solid'
|
||||||
|
import Status from '@/Shared/Status'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VacationRequestIndex',
|
name: 'VacationRequestIndex',
|
||||||
@ -178,6 +193,13 @@ export default {
|
|||||||
PencilIcon,
|
PencilIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
ThumbUpIcon,
|
||||||
|
ClockIcon,
|
||||||
|
XIcon,
|
||||||
|
CheckIcon,
|
||||||
|
DocumentTextIcon,
|
||||||
|
ThumbDownIcon,
|
||||||
|
Status,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
requests: {
|
requests: {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<InertiaHead :title="`Wniosek ${request.name}`" />
|
<InertiaHead :title="`Wniosek ${request.name}`" />
|
||||||
<div class="grid grid-cols-1 gap-6 xl:grid-flow-col-dense xl:grid-cols-3">
|
<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="space-y-6 xl:col-start-1 xl:col-span-2">
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white shadow-md">
|
||||||
<div class="px-4 py-5 sm:px-6">
|
<div class="px-4 py-5 sm:px-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Informacje na temat wniosku
|
Informacje na temat wniosku
|
||||||
@ -18,6 +18,14 @@
|
|||||||
{{ request.name }}
|
{{ request.name }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</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">
|
||||||
|
Pracownik
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.user.name }}
|
||||||
|
</dd>
|
||||||
|
</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">
|
||||||
Rodzaj urlopu
|
Rodzaj urlopu
|
||||||
@ -26,6 +34,14 @@
|
|||||||
{{ request.type }}
|
{{ request.type }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</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">
|
||||||
|
Obecny status
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<Status :status="request.state" />
|
||||||
|
</dd>
|
||||||
|
</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">
|
||||||
Urlop od
|
Urlop od
|
||||||
@ -47,16 +63,25 @@
|
|||||||
Dni urlopu
|
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">
|
||||||
{{ request.estimatedDays }}
|
{{ request.days.length }}
|
||||||
</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">
|
||||||
<dt class="text-sm font-medium text-gray-500">
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
Komentarz
|
Komentarz
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
<dd
|
||||||
|
v-if="request.comment != null"
|
||||||
|
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
|
||||||
|
>
|
||||||
{{ request.comment }}
|
{{ request.comment }}
|
||||||
</dd>
|
</dd>
|
||||||
|
<dd
|
||||||
|
v-else
|
||||||
|
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
<div class="bg-white px-4 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">
|
||||||
@ -85,14 +110,15 @@
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow sm:rounded-lg">
|
<div class="bg-white shadow">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Zaakceptuj wniosek jako osoba techniczna
|
Zaakceptuj wniosek jako osoba techniczna
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||||
<p>
|
<p>
|
||||||
W zależności od typu wniosku, zostanie on zatwierdzony lub osoba administracyjna będzie musiała go zaakceptować.
|
W zależności od typu wniosku, zostanie on zatwierdzony lub osoba administracyjna będzie
|
||||||
|
musiała go zaakceptować.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
@ -107,7 +133,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow sm:rounded-lg">
|
<div class="bg-white shadow">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Zaakceptuj wniosek jako osoba administracyjna
|
Zaakceptuj wniosek jako osoba administracyjna
|
||||||
@ -129,7 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow sm:rounded-lg">
|
<div class="bg-white shadow">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Odrzuć wniosek
|
Odrzuć wniosek
|
||||||
@ -151,7 +177,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-white shadow sm:rounded-lg border border-red-500">
|
<div class="bg-white shadow border border-red-500">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Anuluj wniosek
|
Anuluj wniosek
|
||||||
@ -175,7 +201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="xl:col-start-3 xl:col-span-1 space-y-6">
|
<div class="xl:col-start-3 xl:col-span-1 space-y-6">
|
||||||
<div class="bg-white sm:rounded-lg shadow-md">
|
<div class="bg-white shadow-md">
|
||||||
<div class="px-4 py-5 sm:px-6">
|
<div class="px-4 py-5 sm:px-6">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
Historia wniosku
|
Historia wniosku
|
||||||
@ -185,31 +211,12 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="(activity, index) in activities.data"
|
v-for="(activity, index) in activities.data"
|
||||||
:key="activity.id"
|
:key="index"
|
||||||
>
|
>
|
||||||
<div :class="{'relative pb-8': index !== activities.data.length - 1}">
|
<Activity
|
||||||
<span
|
:activity="activity"
|
||||||
v-if="(index !== activities.data.length - 1)"
|
:last="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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -219,14 +226,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ThumbUpIcon } from '@heroicons/vue/outline'
|
import {PaperClipIcon} from '@heroicons/vue/outline'
|
||||||
import {PaperClipIcon} from '@heroicons/vue/solid'
|
import Activity from '@/Shared/Activity'
|
||||||
|
import Status from '@/Shared/Status'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VacationRequestShow',
|
name: 'VacationRequestShow',
|
||||||
components: {
|
components: {
|
||||||
ThumbUpIcon,
|
Activity,
|
||||||
PaperClipIcon,
|
PaperClipIcon,
|
||||||
|
Status,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
request: {
|
request: {
|
||||||
|
58
resources/js/Shared/Activity.vue
Normal file
58
resources/js/Shared/Activity.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="{'relative pb-8': last}">
|
||||||
|
<span
|
||||||
|
v-if="last"
|
||||||
|
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="[statusInfo.outline.background, statusInfo.outline.foreground, 'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white']">
|
||||||
|
<component
|
||||||
|
:is="statusInfo.outline.icon"
|
||||||
|
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 class="flex flex-col items-start">
|
||||||
|
<div class="text-sm font-medium text-gray-700">
|
||||||
|
{{ statusInfo.text }}
|
||||||
|
</div>
|
||||||
|
<div class="text-right text-sm whitespace-nowrap font-medium text-gray-400">
|
||||||
|
{{ activity.user }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-right text-sm whitespace-nowrap text-gray-500 flex flex-col">
|
||||||
|
<time>{{ activity.date }}</time>
|
||||||
|
<time>{{ activity.time }}</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useStatusInfo} from '@/Composables/statusInfo'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationRequestActivity',
|
||||||
|
props: {
|
||||||
|
activity: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
last: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const statusInfo = computed(() => useStatusInfo(props.activity.state))
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusInfo,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-full">
|
<div class="min-h-full">
|
||||||
<MainMenu />
|
<MainMenu />
|
||||||
<main class="-mt-24 pb-8">
|
<main class="lg:ml-64 flex flex-col flex-1 py-8">
|
||||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
|
<div>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -1,13 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<Popover
|
<TransitionRoot
|
||||||
v-slot="{ open }"
|
as="template"
|
||||||
as="header"
|
:show="sidebarOpen"
|
||||||
class="pb-24 bg-gradient-to-r from-blumilk-500 to-blumilk-600"
|
|
||||||
>
|
>
|
||||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:max-w-7xl lg:px-8">
|
<Dialog
|
||||||
<div class="relative flex flex-wrap items-center justify-center lg:justify-between">
|
as="div"
|
||||||
<!-- Logo -->
|
class="fixed inset-0 flex z-40 lg:hidden"
|
||||||
<div class="absolute left-0 py-5 flex-shrink-0 lg:static">
|
@close="sidebarOpen = false"
|
||||||
|
>
|
||||||
|
<TransitionChild
|
||||||
|
as="template"
|
||||||
|
enter="transition-opacity ease-linear duration-300"
|
||||||
|
enter-from="opacity-0"
|
||||||
|
enter-to="opacity-100"
|
||||||
|
leave="transition-opacity ease-linear duration-300"
|
||||||
|
leave-from="opacity-100"
|
||||||
|
leave-to="opacity-0"
|
||||||
|
>
|
||||||
|
<DialogOverlay class="fixed inset-0 bg-gray-600 bg-opacity-75" />
|
||||||
|
</TransitionChild>
|
||||||
|
<TransitionChild
|
||||||
|
as="template"
|
||||||
|
enter="transition ease-in-out duration-300 transform"
|
||||||
|
enter-from="-translate-x-full"
|
||||||
|
enter-to="translate-x-0"
|
||||||
|
leave="transition ease-in-out duration-300 transform"
|
||||||
|
leave-from="translate-x-0"
|
||||||
|
leave-to="-translate-x-full"
|
||||||
|
>
|
||||||
|
<div class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-blumilk-700">
|
||||||
|
<TransitionChild
|
||||||
|
as="template"
|
||||||
|
enter="ease-in-out duration-300"
|
||||||
|
enter-from="opacity-0"
|
||||||
|
enter-to="opacity-100"
|
||||||
|
leave="ease-in-out duration-300"
|
||||||
|
leave-from="opacity-100"
|
||||||
|
leave-to="opacity-0"
|
||||||
|
>
|
||||||
|
<div class="absolute top-0 right-0 -mr-12 pt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||||
|
@click="sidebarOpen = false"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Close sidebar</span>
|
||||||
|
<XIcon
|
||||||
|
class="h-6 w-6 text-white"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</TransitionChild>
|
||||||
|
<div class="flex-shrink-0 flex items-center px-4">
|
||||||
<InertiaLink href="/">
|
<InertiaLink href="/">
|
||||||
<img
|
<img
|
||||||
class="h-8 w-auto"
|
class="h-8 w-auto"
|
||||||
@ -16,20 +61,139 @@
|
|||||||
>
|
>
|
||||||
</InertiaLink>
|
</InertiaLink>
|
||||||
</div>
|
</div>
|
||||||
|
<nav
|
||||||
|
class="mt-5 flex-shrink-0 h-full divide-y divide-blumilk-800 overflow-y-auto"
|
||||||
|
aria-label="Sidebar"
|
||||||
|
>
|
||||||
|
<div class="px-2 space-y-1">
|
||||||
|
<InertiaLink
|
||||||
|
v-for="item in navigation"
|
||||||
|
:key="item.name"
|
||||||
|
:href="item.href"
|
||||||
|
:class="[item.current ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-base font-medium rounded-md']"
|
||||||
|
:aria-current="item.current ? 'page' : undefined"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.icon"
|
||||||
|
class="mr-4 flex-shrink-0 h-6 w-6 text-blumilk-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 pt-6">
|
||||||
|
<div class="px-2 space-y-1">
|
||||||
|
<InertiaLink
|
||||||
|
v-for="item in secondaryNavigation"
|
||||||
|
:key="item.name"
|
||||||
|
:href="item.href"
|
||||||
|
class="group flex items-center px-2 py-2 text-base font-medium rounded-md text-blumilk-100 hover:text-white hover:bg-blumilk-600"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.icon"
|
||||||
|
class="mr-4 h-6 w-6 text-blumilk-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</TransitionChild>
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 w-14"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
|
</TransitionRoot>
|
||||||
|
|
||||||
<!-- Right section on desktop -->
|
<!-- Static sidebar for desktop -->
|
||||||
<div class="hidden lg:ml-4 lg:flex lg:items-center lg:py-5 lg:pr-0.5">
|
<div class="hidden lg:flex lg:w-64 lg:flex-col lg:fixed lg:inset-y-0">
|
||||||
<div class="mr-4">
|
<div class="flex flex-col flex-grow bg-blumilk-700 pt-5 pb-4 overflow-y-auto">
|
||||||
|
<div class="flex items-center flex-shrink-0 px-4">
|
||||||
|
<InertiaLink href="/">
|
||||||
|
<img
|
||||||
|
class="h-8 w-auto"
|
||||||
|
src="/img/logo-white.png"
|
||||||
|
alt="Workflow"
|
||||||
|
>
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
<nav
|
||||||
|
class="mt-5 flex-1 flex flex-col divide-y divide-blumilk-800 overflow-y-auto"
|
||||||
|
aria-label="Sidebar"
|
||||||
|
>
|
||||||
|
<div class="px-2 space-y-1">
|
||||||
|
<InertiaLink
|
||||||
|
v-for="item in navigation"
|
||||||
|
:key="item.name"
|
||||||
|
:href="item.href"
|
||||||
|
:class="[item.current ? 'bg-blumilk-800 text-white' : 'text-blumilk-100 hover:text-white hover:bg-blumilk-600', 'group flex items-center px-2 py-2 text-sm leading-6 font-medium rounded-md']"
|
||||||
|
:aria-current="item.current ? 'page' : undefined"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.icon"
|
||||||
|
class="mr-4 flex-shrink-0 h-6 w-6 text-blumilk-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 pt-6">
|
||||||
|
<div class="px-2 space-y-1">
|
||||||
|
<InertiaLink
|
||||||
|
v-for="item in secondaryNavigation"
|
||||||
|
:key="item.name"
|
||||||
|
:href="item.href"
|
||||||
|
class="group flex items-center px-2 py-2 text-sm leading-6 font-medium rounded-md text-blumilk-100 hover:text-white hover:bg-blumilk-600"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.icon"
|
||||||
|
class="mr-4 h-6 w-6 text-blumilk-200"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lg:pl-64 flex flex-col flex-1">
|
||||||
|
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="px-4 border-r border-gray-200 text-gray-400 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blumilk-500 lg:hidden"
|
||||||
|
@click="sidebarOpen = true"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Open sidebar</span>
|
||||||
|
<MenuAlt1Icon
|
||||||
|
class="h-6 w-6"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div class="flex-1 px-4 flex justify-between sm:px-6 lg:px-8">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div>
|
||||||
<Menu
|
<Menu
|
||||||
as="div"
|
as="div"
|
||||||
class="relative inline-block text-left"
|
class="relative inline-block text-left"
|
||||||
>
|
>
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<div class="mr-4">
|
||||||
|
Wybrany rok:
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<MenuButton class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-1 focus:ring-gray-300">
|
<MenuButton
|
||||||
|
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white 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"
|
||||||
|
>
|
||||||
{{ years.current }}
|
{{ years.current }}
|
||||||
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" />
|
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="transition ease-out duration-100"
|
enter-active-class="transition ease-out duration-100"
|
||||||
@ -39,7 +203,9 @@
|
|||||||
leave-from-class="transform opacity-100 scale-100"
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
leave-to-class="transform opacity-0 scale-95"
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<MenuItems class="origin-top-right absolute right-0 mt-2 w-32 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
<MenuItems
|
||||||
|
class="origin-top-right absolute right-0 mt-2 w-32 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
|
>
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
v-for="(item, index) in years.navigation"
|
v-for="(item, index) in years.navigation"
|
||||||
@ -65,44 +231,42 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<div class="ml-4 flex items-center md:ml-6">
|
||||||
type="button"
|
|
||||||
class="flex-shrink-0 p-1 text-cyan-200 rounded-full hover:text-white hover:bg-white hover:bg-opacity-10 focus:outline-none focus:ring-2 focus:ring-white"
|
|
||||||
>
|
|
||||||
<span class="sr-only">View notifications</span>
|
|
||||||
<BellIcon
|
|
||||||
class="h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Profile dropdown -->
|
<!-- Profile dropdown -->
|
||||||
<Menu
|
<Menu
|
||||||
as="div"
|
as="div"
|
||||||
class="ml-4 relative flex-shrink-0"
|
class="ml-3 relative"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
class="rounded-full flex text-sm ring-2 ring-white ring-opacity-20 focus:outline-none focus:ring-opacity-100"
|
class="max-w-xs bg-white rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500 lg:p-2 lg:rounded-md lg:hover:bg-gray-50"
|
||||||
dusk="user-menu"
|
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{ user.avatar }}</span>
|
|
||||||
<img
|
<img
|
||||||
class="h-8 w-8 rounded-full"
|
class="h-8 w-8 rounded-full"
|
||||||
:src="user.avatar"
|
:src="user.avatar"
|
||||||
alt=""
|
alt="Avatar"
|
||||||
>
|
>
|
||||||
|
<span class="hidden ml-3 text-gray-700 text-sm font-medium lg:block">
|
||||||
|
<span class="sr-only">Open user menu for </span>
|
||||||
|
{{ user.name }}
|
||||||
|
</span>
|
||||||
|
<ChevronDownIcon
|
||||||
|
class="hidden flex-shrink-0 ml-1 h-5 w-5 text-gray-400 lg:block"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
<transition
|
<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-active-class="transition ease-in duration-75"
|
||||||
leave-from-class="transform opacity-100 scale-100"
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
leave-to-class="transform opacity-0 scale-95"
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<MenuItems
|
<MenuItems
|
||||||
class="origin-top-right z-40 absolute -right-2 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
dusk="user-menu-list"
|
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
v-for="item in userNavigation"
|
v-for="item in userNavigation"
|
||||||
@ -122,208 +286,88 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full py-5 lg:border-t lg:border-white lg:border-opacity-20">
|
|
||||||
<div class="lg:items-center">
|
|
||||||
<div class="hidden lg:block">
|
|
||||||
<nav class="flex space-x-4">
|
|
||||||
<InertiaLink
|
|
||||||
v-for="item in navigation"
|
|
||||||
:key="item.name"
|
|
||||||
:href="item.href"
|
|
||||||
:class="[item.current ? 'text-white' : 'text-cyan-100', 'text-sm font-medium rounded-md bg-white bg-opacity-0 px-3 py-2 hover:bg-opacity-10']"
|
|
||||||
:aria-current="item.current ? 'page' : undefined"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</InertiaLink>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Menu button -->
|
|
||||||
<div class="absolute right-0 flex-shrink-0 lg:hidden">
|
|
||||||
<!-- Mobile menu button -->
|
|
||||||
<PopoverButton
|
|
||||||
class="bg-transparent p-2 rounded-md inline-flex items-center justify-center text-cyan-200 hover:text-white hover:bg-white hover:bg-opacity-10 focus:outline-none focus:ring-2 focus:ring-white"
|
|
||||||
>
|
|
||||||
<span class="sr-only">Open main menu</span>
|
|
||||||
<MenuIcon
|
|
||||||
v-if="!open"
|
|
||||||
class="block h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<XIcon
|
|
||||||
v-else
|
|
||||||
class="block h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</PopoverButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TransitionRoot
|
|
||||||
as="template"
|
|
||||||
:show="open"
|
|
||||||
>
|
|
||||||
<div class="lg:hidden">
|
|
||||||
<TransitionChild
|
|
||||||
as="template"
|
|
||||||
enter="duration-150 ease-out"
|
|
||||||
enter-from="opacity-0"
|
|
||||||
enter-to="opacity-100"
|
|
||||||
leave="duration-150 ease-in"
|
|
||||||
leave-from="opacity-100"
|
|
||||||
leave-to="opacity-0"
|
|
||||||
>
|
|
||||||
<PopoverOverlay class="z-20 fixed inset-0 bg-black bg-opacity-25" />
|
|
||||||
</TransitionChild>
|
|
||||||
|
|
||||||
<TransitionChild
|
|
||||||
as="template"
|
|
||||||
enter="duration-150 ease-out"
|
|
||||||
enter-from="opacity-0 scale-95"
|
|
||||||
enter-to="opacity-100 scale-100"
|
|
||||||
leave="duration-150 ease-in"
|
|
||||||
leave-from="opacity-100 scale-100"
|
|
||||||
leave-to="opacity-0 scale-95"
|
|
||||||
>
|
|
||||||
<PopoverPanel
|
|
||||||
focus
|
|
||||||
class="z-30 absolute top-0 inset-x-0 max-w-3xl mx-auto w-full p-2 transition transform origin-top"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y divide-gray-200"
|
|
||||||
>
|
|
||||||
<div class="pt-3 pb-2">
|
|
||||||
<div class="flex items-center justify-between px-4">
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
class="h-8 w-auto"
|
|
||||||
src="/img/logo-white.png"
|
|
||||||
alt="Workflow"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="-mr-2">
|
|
||||||
<PopoverButton
|
|
||||||
class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-cyan-500"
|
|
||||||
>
|
|
||||||
<span class="sr-only">Close menu</span>
|
|
||||||
<XIcon
|
|
||||||
class="h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</PopoverButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 px-2 space-y-1">
|
|
||||||
<InertiaLink
|
|
||||||
v-for="item in navigation"
|
|
||||||
:key="item.name"
|
|
||||||
:href="item.href"
|
|
||||||
class="block rounded-md px-3 py-2 text-base text-gray-900 font-medium hover:bg-gray-100 hover:text-gray-800"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</InertiaLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-4 pb-2">
|
|
||||||
<div class="flex items-center px-5">
|
|
||||||
<div class="flex-shrink-0">
|
|
||||||
<img
|
|
||||||
class="h-10 w-10 rounded-full"
|
|
||||||
:src="user.avatar"
|
|
||||||
alt=""
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 min-w-0 flex-1">
|
|
||||||
<div class="text-base font-medium text-gray-800 truncate">
|
|
||||||
{{ user.name }}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm font-medium text-gray-500 truncate">
|
|
||||||
{{ user.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="ml-auto flex-shrink-0 bg-white p-1 text-gray-400 rounded-full hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500"
|
|
||||||
>
|
|
||||||
<span class="sr-only">View notifications</span>
|
|
||||||
<BellIcon
|
|
||||||
class="h-6 w-6"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 px-2 space-y-1">
|
|
||||||
<InertiaLink
|
|
||||||
v-for="item in userNavigation"
|
|
||||||
:key="item.name"
|
|
||||||
:method="item.method"
|
|
||||||
:as="item.as"
|
|
||||||
:href="item.href"
|
|
||||||
class="block w-full text-left rounded-md px-3 py-2 text-base text-gray-900 font-medium hover:bg-gray-100 hover:text-gray-800"
|
|
||||||
>
|
|
||||||
{{ item.name }}
|
|
||||||
</InertiaLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverPanel>
|
|
||||||
</TransitionChild>
|
|
||||||
</div>
|
|
||||||
</TransitionRoot>
|
|
||||||
</Popover>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {ref} from 'vue'
|
||||||
import {
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogOverlay,
|
||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuItems,
|
MenuItems,
|
||||||
Popover,
|
|
||||||
PopoverButton,
|
|
||||||
PopoverOverlay,
|
|
||||||
PopoverPanel,
|
|
||||||
TransitionChild,
|
TransitionChild,
|
||||||
TransitionRoot,
|
TransitionRoot,
|
||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
import {BellIcon, MenuIcon, XIcon} from '@heroicons/vue/outline'
|
import {
|
||||||
|
BellIcon,
|
||||||
|
CogIcon,
|
||||||
|
HomeIcon,
|
||||||
|
CollectionIcon,
|
||||||
|
MenuAlt1Icon,
|
||||||
|
QuestionMarkCircleIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
UserGroupIcon,
|
||||||
|
XIcon,
|
||||||
|
SunIcon,
|
||||||
|
StarIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
} from '@heroicons/vue/outline'
|
||||||
|
import {
|
||||||
|
CashIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
CheckIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
OfficeBuildingIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from '@heroicons/vue/solid'
|
||||||
import {computed} from 'vue'
|
import {computed} from 'vue'
|
||||||
import {usePage} from '@inertiajs/inertia-vue3'
|
import {usePage} from '@inertiajs/inertia-vue3'
|
||||||
import {ChevronDownIcon, CheckIcon} from '@heroicons/vue/solid'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainMenu',
|
|
||||||
components: {
|
components: {
|
||||||
|
Dialog,
|
||||||
|
DialogOverlay,
|
||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuItems,
|
MenuItems,
|
||||||
Popover,
|
|
||||||
PopoverButton,
|
|
||||||
PopoverOverlay,
|
|
||||||
PopoverPanel,
|
|
||||||
TransitionChild,
|
TransitionChild,
|
||||||
TransitionRoot,
|
TransitionRoot,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
MenuIcon,
|
CashIcon,
|
||||||
XIcon,
|
CheckCircleIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
MenuAlt1Icon,
|
||||||
|
OfficeBuildingIcon,
|
||||||
|
SearchIcon,
|
||||||
|
XIcon,
|
||||||
|
StarIcon,
|
||||||
|
HomeIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
UserGroupIcon,
|
||||||
|
SunIcon,
|
||||||
|
CalendarIcon,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const sidebarOpen = ref(false)
|
||||||
|
|
||||||
const user = computed(() => usePage().props.value.auth.user)
|
const user = computed(() => usePage().props.value.auth.user)
|
||||||
const years = computed(() => usePage().props.value.years)
|
const years = computed(() => usePage().props.value.years)
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{name: 'Strona główna', href: '/', current: true},
|
{name: 'Strona główna', href: '/', icon: HomeIcon, current: true},
|
||||||
{name: 'Użytkownicy', href: '/users', current: false},
|
{name: 'Użytkownicy', href: '/users', icon: UserGroupIcon, current: false},
|
||||||
{name: 'Dostępne urlopy', href: '/vacation-limits', current: false},
|
{name: 'Dostępne urlopy', href: '/vacation-limits', icon: SunIcon, current: false},
|
||||||
{name: 'Dni wolne', href: '/holidays', current: false},
|
{name: 'Twoje wnioski', href: '/vacation-requests', icon: CollectionIcon, current: false},
|
||||||
{name: 'Wnioski urlopowe', href: '/vacation-requests', current: false},
|
{name: 'Dni wolne', href: '/holidays', icon: StarIcon, current: false},
|
||||||
|
{name: 'Kalendarz urlopów', href: '/vacation-calendar', icon: CalendarIcon, current: false},
|
||||||
]
|
]
|
||||||
const userNavigation = [
|
const userNavigation = [
|
||||||
{name: 'Your Profile', href: '#'},
|
{name: 'Your Profile', href: '#'},
|
||||||
@ -331,11 +375,18 @@ export default {
|
|||||||
{name: 'Wyloguj się', href: '/logout', method: 'post', as: 'button'},
|
{name: 'Wyloguj się', href: '/logout', method: 'post', as: 'button'},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const secondaryNavigation = [
|
||||||
|
{name: 'Settings', href: '#', icon: CogIcon},
|
||||||
|
{name: 'Help', href: '#', icon: QuestionMarkCircleIcon},
|
||||||
|
{name: 'Privacy', href: '#', icon: ShieldCheckIcon},
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
years,
|
years,
|
||||||
navigation,
|
navigation,
|
||||||
|
secondaryNavigation,
|
||||||
userNavigation,
|
userNavigation,
|
||||||
|
sidebarOpen,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
35
resources/js/Shared/Status.vue
Normal file
35
resources/js/Shared/Status.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component
|
||||||
|
:is="statusInfo.solid.icon"
|
||||||
|
:class="[statusInfo.solid.color ,'w-5 h-5 mr-1']"
|
||||||
|
/>
|
||||||
|
<span>{{ statusInfo.text }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {computed} from 'vue'
|
||||||
|
import {useStatusInfo} from '@/Composables/statusInfo'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationRequestStatus',
|
||||||
|
props: {
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
last: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const statusInfo = computed(() => useStatusInfo(props.status))
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusInfo,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -28,7 +28,7 @@
|
|||||||
"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 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 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 limit has been exceeded.": "Limit urlopu został przekroczony.",
|
||||||
"Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.",
|
"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."
|
"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."
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ 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\VacationCalendarController;
|
||||||
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
||||||
use Toby\Infrastructure\Http\Controllers\VacationRequestController;
|
use Toby\Infrastructure\Http\Controllers\VacationRequestController;
|
||||||
|
|
||||||
@ -23,6 +24,10 @@ Route::middleware("auth")->group(function (): void {
|
|||||||
|
|
||||||
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])
|
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])
|
||||||
->name("vacation.limits");
|
->name("vacation.limits");
|
||||||
|
Route::get("/vacation-calendar", [VacationCalendarController::class, "index"])
|
||||||
|
->name("vacation.calendar");
|
||||||
|
|
||||||
|
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::get("/vacation-requests", [VacationRequestController::class, "index"])
|
Route::get("/vacation-requests", [VacationRequestController::class, "index"])
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
const defaultTheme = require('tailwindcss/defaultTheme');
|
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
'./resources/**/*.vue',
|
'./resources/**/*.vue',
|
||||||
|
'./resources/**/*.js',
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
@ -21,14 +22,14 @@ module.exports = {
|
|||||||
'600': '#3C5F97',
|
'600': '#3C5F97',
|
||||||
'700': '#2C466F',
|
'700': '#2C466F',
|
||||||
'800': '#1C2D47',
|
'800': '#1C2D47',
|
||||||
'900': '#0C141F'
|
'900': '#0C141F',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require('@tailwindcss/forms'),
|
require('@tailwindcss/forms'),
|
||||||
require('@tailwindcss/typography'),
|
require('@tailwindcss/typography'),
|
||||||
require('@tailwindcss/line-clamp')
|
require('@tailwindcss/line-clamp'),
|
||||||
],
|
],
|
||||||
};
|
}
|
||||||
|
@ -88,10 +88,10 @@ class UserTest extends FeatureTestCase
|
|||||||
->post("/users", [
|
->post("/users", [
|
||||||
"firstName" => "John",
|
"firstName" => "John",
|
||||||
"lastName" => "Doe",
|
"lastName" => "Doe",
|
||||||
"role" => Role::EMPLOYEE->value,
|
"role" => Role::Employee->value,
|
||||||
"position" => "Test position",
|
"position" => "Test position",
|
||||||
"email" => "john.doe@example.com",
|
"email" => "john.doe@example.com",
|
||||||
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
"employmentForm" => EmploymentForm::B2bContract->value,
|
||||||
"employmentDate" => Carbon::now()->toDateString(),
|
"employmentDate" => Carbon::now()->toDateString(),
|
||||||
])
|
])
|
||||||
->assertSessionHasNoErrors();
|
->assertSessionHasNoErrors();
|
||||||
@ -100,9 +100,9 @@ class UserTest extends FeatureTestCase
|
|||||||
"first_name" => "John",
|
"first_name" => "John",
|
||||||
"last_name" => "Doe",
|
"last_name" => "Doe",
|
||||||
"email" => "john.doe@example.com",
|
"email" => "john.doe@example.com",
|
||||||
"role" => Role::EMPLOYEE->value,
|
"role" => Role::Employee->value,
|
||||||
"position" => "Test position",
|
"position" => "Test position",
|
||||||
"employment_form" => EmploymentForm::B2B_CONTRACT->value,
|
"employment_form" => EmploymentForm::B2bContract->value,
|
||||||
"employment_date" => Carbon::now()->toDateString(),
|
"employment_date" => Carbon::now()->toDateString(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -127,9 +127,9 @@ 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,
|
"role" => Role::Employee->value,
|
||||||
"position" => "Test position",
|
"position" => "Test position",
|
||||||
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
"employmentForm" => EmploymentForm::B2bContract->value,
|
||||||
"employmentDate" => Carbon::now()->toDateString(),
|
"employmentDate" => Carbon::now()->toDateString(),
|
||||||
])
|
])
|
||||||
->assertSessionHasNoErrors();
|
->assertSessionHasNoErrors();
|
||||||
@ -138,9 +138,9 @@ class UserTest extends FeatureTestCase
|
|||||||
"first_name" => "John",
|
"first_name" => "John",
|
||||||
"last_name" => "Doe",
|
"last_name" => "Doe",
|
||||||
"email" => "john.doe@example.com",
|
"email" => "john.doe@example.com",
|
||||||
"role" => Role::EMPLOYEE->value,
|
"role" => Role::Employee->value,
|
||||||
"position" => "Test position",
|
"position" => "Test position",
|
||||||
"employment_form" => EmploymentForm::B2B_CONTRACT->value,
|
"employment_form" => EmploymentForm::B2bContract->value,
|
||||||
"employment_date" => Carbon::now()->toDateString(),
|
"employment_date" => Carbon::now()->toDateString(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
345
tests/Feature/VacationRequestTest.php
Normal file
345
tests/Feature/VacationRequestTest.php
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\PolishHolidaysRetriever;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationLimit;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
|
class VacationRequestTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
use DatabaseMigrations;
|
||||||
|
|
||||||
|
protected PolishHolidaysRetriever $polishHolidaysRetriever;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCanSeeVacationRequestsList(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationRequest::factory()
|
||||||
|
->count(10)
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->get("/vacation-requests")
|
||||||
|
->assertOk()
|
||||||
|
->assertInertia(
|
||||||
|
fn(Assert $page) => $page
|
||||||
|
->component("VacationRequest/Index")
|
||||||
|
->has("requests.data", 10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCanCreateVacationRequest(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"user_id" => $user->id,
|
||||||
|
"year_period_id" => $currentYearPeriod->id,
|
||||||
|
"name" => "1/" . $currentYearPeriod->year,
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => VacationRequestState::WaitingForTechnical,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTechnicalApproverCanApproveVacationRequest(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$technicalApprover = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"state" => VacationRequestState::WaitingForTechnical,
|
||||||
|
"type" => VacationType::Vacation,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($technicalApprover)
|
||||||
|
->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical")
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"state" => VacationRequestState::WaitingForAdministrative,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdministrativeApproverCanApproveVacationRequest(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$administrativeApprover = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"state" => VacationRequestState::WaitingForAdministrative,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($administrativeApprover)
|
||||||
|
->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative")
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"state" => VacationRequestState::Approved,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTechnicalApproverCanRejectVacationRequest(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$technicalApprover = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
$vacationLimit = VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"state" => VacationRequestState::WaitingForTechnical,
|
||||||
|
"type" => VacationType::Vacation,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($technicalApprover)
|
||||||
|
->post("/vacation-requests/{$vacationRequest->id}/reject")
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"state" => VacationRequestState::Rejected,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestIfHeExceedsHisVacationLimit(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 3,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("Vacation limit has been exceeded."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestAtWeekend(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 5)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
||||||
|
"comment" => "Vacation at weekend.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("Vacation needs minimum one day."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestAtHoliday(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
foreach ($this->polishHolidaysRetriever->getForYearPeriod($currentYearPeriod) as $holiday) {
|
||||||
|
$currentYearPeriod->holidays()->create([
|
||||||
|
"name" => $holiday["name"],
|
||||||
|
"date" => $holiday["date"],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
||||||
|
"comment" => "Vacation at holiday.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("Vacation needs minimum one day."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestIfHeHasPendingVacationRequestInThisRange(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
VacationRequest::factory([
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => VacationRequestState::WaitingForTechnical,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Another comment for the another vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("You have pending vacation request in this range."),
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestIfHeHasApprovedVacationRequestInThisRange(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
$vacationLimit = VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
VacationRequest::factory([
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => VacationRequestState::Approved,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 2)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Another comment for the another vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("You have approved vacation request in this range."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestWithEndDatePriorToTheStartDate(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("Vacation needs minimum one day."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotCreateVacationRequestAtTheTurnOfTheYear(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
$nextYearPeriod = $this->createYearPeriod(Carbon::now()->year + 1);
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 12, 27)->toDateString(),
|
||||||
|
"to" => Carbon::create($nextYearPeriod->year, 1, 2)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasErrors([
|
||||||
|
"vacationRequest" => __("The vacation request cannot be created at the turn of the year."),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@ abstract class FeatureTestCase extends BaseTestCase
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
Carbon::setTestNow(Carbon::now());
|
Carbon::setTestNow(Carbon::createFromDate(2022, 1, 1));
|
||||||
$this->createCurrentYearPeriod();
|
$this->createCurrentYearPeriod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
100
tests/Unit/VacationRequestStatesTest.php
Normal file
100
tests/Unit/VacationRequestStatesTest.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
|
class VacationRequestStatesTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseMigrations;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
|
protected VacationRequestStateManager $stateManager;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
|
||||||
|
|
||||||
|
$this->createCurrentYearPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAfterCreatingVacationRequestOfTypeVacationItTransitsToProperState(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
/** @var VacationRequest $vacationRequest */
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => VacationRequestState::Created,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->stateManager->waitForTechnical($vacationRequest);
|
||||||
|
|
||||||
|
$this->assertEquals(VacationRequestState::WaitingForTechnical, $vacationRequest->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAfterCreatingVacationRequestOfTypeSickVacationItTransitsToProperState(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
/** @var VacationRequest $vacationRequest */
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"type" => VacationType::Sick->value,
|
||||||
|
"state" => VacationRequestState::Created,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->stateManager->approve($vacationRequest);
|
||||||
|
|
||||||
|
$this->assertEquals(VacationRequestState::Approved, $vacationRequest->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAfterCreatingVacationRequestOfTypeTimeInLieuItTransitsToProperState(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
/** @var VacationRequest $vacationRequest */
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"type" => VacationType::TimeInLieu->value,
|
||||||
|
"state" => VacationRequestState::Created,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 2)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 2)->toDateString(),
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->stateManager->approve($vacationRequest);
|
||||||
|
|
||||||
|
$this->assertEquals(VacationRequestState::Approved, $vacationRequest->state);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
const mix = require('laravel-mix');
|
const mix = require('laravel-mix')
|
||||||
const webpackConfig = require('./webpack.config');
|
const webpackConfig = require('./webpack.config')
|
||||||
|
|
||||||
mix.js("resources/js/app.js", "public/js")
|
mix.js('resources/js/app.js', 'public/js')
|
||||||
.vue(3)
|
.vue(3)
|
||||||
.postCss("resources/css/app.css", "public/css", [
|
.postCss('resources/css/app.css', 'public/css', [
|
||||||
require("tailwindcss"),
|
require('tailwindcss'),
|
||||||
])
|
])
|
||||||
.webpackConfig(webpackConfig)
|
.webpackConfig(webpackConfig)
|
||||||
.sourceMaps();
|
.sourceMaps()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user