* #23 - wip * #23 - wip * #23 - wip * #23 - wip * #23 - fix * #23 - ecs fix * #23 - fix * #23 - fix * #23 - cr fix
This commit is contained in:
parent
652587dbf1
commit
e147d24365
57
app/Helpers/YearPeriodRetriever.php
Normal file
57
app/Helpers/YearPeriodRetriever.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Helpers;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Session\Session;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class YearPeriodRetriever
|
||||||
|
{
|
||||||
|
public const SESSION_KEY = "selected_year_period";
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected Session $session,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selected(): YearPeriod
|
||||||
|
{
|
||||||
|
/** @var YearPeriod $yearPeriod */
|
||||||
|
$yearPeriod = YearPeriod::query()->find($this->session->get(static::SESSION_KEY));
|
||||||
|
|
||||||
|
return $yearPeriod !== null ? $yearPeriod : $this->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current(): YearPeriod
|
||||||
|
{
|
||||||
|
return YearPeriod::current();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function links(): array
|
||||||
|
{
|
||||||
|
$current = $this->selected();
|
||||||
|
|
||||||
|
$years = YearPeriod::query()->whereIn("year", $this->offset($current->year))->get();
|
||||||
|
$navigation = $years->map(fn(YearPeriod $yearPeriod) => $this->toNavigation($yearPeriod));
|
||||||
|
|
||||||
|
return [
|
||||||
|
"current" => $current->year,
|
||||||
|
"navigation" => $navigation->toArray(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function offset(int $year): array
|
||||||
|
{
|
||||||
|
return range($year - 2, $year + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toNavigation(YearPeriod $yearPeriod): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"year" => $yearPeriod->year,
|
||||||
|
"link" => route("year-periods.select", $yearPeriod->id),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
22
app/Http/Controllers/SelectYearPeriodController.php
Normal file
22
app/Http/Controllers/SelectYearPeriodController.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class SelectYearPeriodController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Request $request, YearPeriod $yearPeriod): RedirectResponse
|
||||||
|
{
|
||||||
|
$request->session()->put(YearPeriodRetriever::SESSION_KEY, $yearPeriod->id);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with("success", __("Selected year period has been changed"));
|
||||||
|
}
|
||||||
|
}
|
34
app/Http/Controllers/VacationLimitController.php
Normal file
34
app/Http/Controllers/VacationLimitController.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Toby\Http\Requests\VacationLimitRequest;
|
||||||
|
use Toby\Http\Resources\VacationLimitResource;
|
||||||
|
use Toby\Models\VacationLimit;
|
||||||
|
|
||||||
|
class VacationLimitController extends Controller
|
||||||
|
{
|
||||||
|
public function edit(): Response
|
||||||
|
{
|
||||||
|
return inertia("VacationLimits", [
|
||||||
|
"limits" => VacationLimitResource::collection(VacationLimit::query()->with("user")->get()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(VacationLimitRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$data = $request->data();
|
||||||
|
|
||||||
|
foreach ($request->vacationLimits() as $limit) {
|
||||||
|
$limit->update($data[$limit->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->back()
|
||||||
|
->with("success", __("Vacation limits have been updated"));
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,16 @@ namespace Toby\Http\Middleware;
|
|||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
use Toby\Http\Resources\UserResource;
|
use Toby\Http\Resources\UserResource;
|
||||||
|
|
||||||
class HandleInertiaRequests extends Middleware
|
class HandleInertiaRequests extends Middleware
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected YearPeriodRetriever $yearPeriodRetriever,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function share(Request $request): array
|
public function share(Request $request): array
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
@ -22,6 +28,7 @@ class HandleInertiaRequests extends Middleware
|
|||||||
"success" => $request->session()->get("success"),
|
"success" => $request->session()->get("success"),
|
||||||
"error" => $request->session()->get("error"),
|
"error" => $request->session()->get("error"),
|
||||||
],
|
],
|
||||||
|
"years" => fn() => $user ? $this->yearPeriodRetriever->links() : [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
app/Http/Requests/VacationLimitRequest.php
Normal file
33
app/Http/Requests/VacationLimitRequest.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Toby\Models\VacationLimit;
|
||||||
|
|
||||||
|
class VacationLimitRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"items" => ["required", "array"],
|
||||||
|
"items.*.id" => ["required", "exists:vacation_limits,id"],
|
||||||
|
"items.*.days" => ["nullable", "integer", "min:0"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function vacationLimits(): Collection
|
||||||
|
{
|
||||||
|
return VacationLimit::query()->find($this->collect("items")->pluck("id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return $this->collect("items")
|
||||||
|
->keyBy("id")
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
|||||||
|
|
||||||
class UserFormDataResource extends JsonResource
|
class UserFormDataResource extends JsonResource
|
||||||
{
|
{
|
||||||
public static $wrap = false;
|
public static $wrap = null;
|
||||||
|
|
||||||
public function toArray($request): array
|
public function toArray($request): array
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ use Illuminate\Http\Resources\Json\JsonResource;
|
|||||||
|
|
||||||
class UserResource extends JsonResource
|
class UserResource extends JsonResource
|
||||||
{
|
{
|
||||||
public static $wrap = false;
|
public static $wrap = null;
|
||||||
|
|
||||||
public function toArray($request): array
|
public function toArray($request): array
|
||||||
{
|
{
|
||||||
|
22
app/Http/Resources/VacationLimitResource.php
Normal file
22
app/Http/Resources/VacationLimitResource.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class VacationLimitResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"id" => $this->id,
|
||||||
|
"user" => new UserResource($this->user),
|
||||||
|
"hasVacation" => $this->hasVacation(),
|
||||||
|
"days" => $this->days,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,12 @@ namespace Toby\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Toby\Enums\EmploymentForm;
|
use Toby\Enums\EmploymentForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +21,7 @@ use Toby\Enums\EmploymentForm;
|
|||||||
* @property string $avatar
|
* @property string $avatar
|
||||||
* @property EmploymentForm $employment_form
|
* @property EmploymentForm $employment_form
|
||||||
* @property Carbon $employment_date
|
* @property Carbon $employment_date
|
||||||
|
* @property Collection $vacationLimits
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
@ -43,6 +46,11 @@ class User extends Authenticatable
|
|||||||
"remember_token",
|
"remember_token",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function vacationLimits(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(VacationLimit::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeSearch(Builder $query, ?string $text): Builder
|
public function scopeSearch(Builder $query, ?string $text): Builder
|
||||||
{
|
{
|
||||||
if ($text === null) {
|
if ($text === null) {
|
||||||
@ -53,4 +61,11 @@ class User extends Authenticatable
|
|||||||
->where("name", "LIKE", "%{$text}%")
|
->where("name", "LIKE", "%{$text}%")
|
||||||
->orWhere("email", "LIKE", "%{$text}%");
|
->orWhere("email", "LIKE", "%{$text}%");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function saveAvatar(string $path): void
|
||||||
|
{
|
||||||
|
$this->avatar = $path;
|
||||||
|
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
37
app/Models/VacationLimit.php
Normal file
37
app/Models/VacationLimit.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property User $user
|
||||||
|
* @property YearPeriod $yearPeriod
|
||||||
|
* @property int $days
|
||||||
|
*/
|
||||||
|
class VacationLimit extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
public function hasVacation(): bool
|
||||||
|
{
|
||||||
|
return $this->days !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function yearPeriod(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(YearPeriod::class);
|
||||||
|
}
|
||||||
|
}
|
@ -7,10 +7,13 @@ namespace Toby\Models;
|
|||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property int $year
|
* @property int $year
|
||||||
|
* @property Collection $vacationLimits
|
||||||
*/
|
*/
|
||||||
class YearPeriod extends Model
|
class YearPeriod extends Model
|
||||||
{
|
{
|
||||||
@ -27,4 +30,9 @@ class YearPeriod extends Model
|
|||||||
|
|
||||||
return $year;
|
return $year;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function vacationLimits(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(VacationLimit::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,24 @@ namespace Toby\Observers;
|
|||||||
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Toby\Helpers\UserAvatarGenerator;
|
use Toby\Helpers\UserAvatarGenerator;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
|
||||||
class UserObserver
|
class UserObserver
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected UserAvatarGenerator $generator,
|
protected UserAvatarGenerator $generator,
|
||||||
|
protected YearPeriodRetriever $yearPeriodRetriever,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function created(User $user): void
|
public function created(User $user): void
|
||||||
{
|
{
|
||||||
$user->avatar = $this->generator->generateFor($user);
|
$user->saveAvatar($this->generator->generateFor($user));
|
||||||
|
|
||||||
$user->save();
|
$user->vacationLimits()->create([
|
||||||
|
"year_period_id" => $this->yearPeriodRetriever->current()->id,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updating(User $user): void
|
public function updating(User $user): void
|
||||||
|
28
app/Observers/YearPeriodObserver.php
Normal file
28
app/Observers/YearPeriodObserver.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Observers;
|
||||||
|
|
||||||
|
use Toby\Helpers\UserAvatarGenerator;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class YearPeriodObserver
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected UserAvatarGenerator $generator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function created(YearPeriod $yearPeriod): void
|
||||||
|
{
|
||||||
|
$users = User::all();
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$yearPeriod->vacationLimits()->create([
|
||||||
|
"user_id" => $user->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,15 +6,15 @@ namespace Toby\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Toby\Models\User;
|
use Toby\Models\VacationLimit;
|
||||||
use Toby\Observers\UserObserver;
|
use Toby\Scopes\SelectedYearPeriodScope;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
User::observe(UserObserver::class);
|
|
||||||
|
|
||||||
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
|
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
|
||||||
|
|
||||||
|
VacationLimit::addGlobalScope($this->app->make(SelectedYearPeriodScope::class));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
app/Providers/ObserverServiceProvider.php
Normal file
20
app/Providers/ObserverServiceProvider.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
use Toby\Observers\UserObserver;
|
||||||
|
use Toby\Observers\YearPeriodObserver;
|
||||||
|
|
||||||
|
class ObserverServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
User::observe(UserObserver::class);
|
||||||
|
YearPeriod::observe(YearPeriodObserver::class);
|
||||||
|
}
|
||||||
|
}
|
23
app/Scopes/SelectedYearPeriodScope.php
Normal file
23
app/Scopes/SelectedYearPeriodScope.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Scopes;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
|
|
||||||
|
class SelectedYearPeriodScope implements Scope
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected YearPeriodRetriever $yearPeriodRetriever,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(Builder $builder, Model $model): Builder
|
||||||
|
{
|
||||||
|
return $builder->where("year_period_id", $this->yearPeriodRetriever->selected()->id);
|
||||||
|
}
|
||||||
|
}
|
@ -42,5 +42,6 @@ return [
|
|||||||
Toby\Providers\EventServiceProvider::class,
|
Toby\Providers\EventServiceProvider::class,
|
||||||
Toby\Providers\RouteServiceProvider::class,
|
Toby\Providers\RouteServiceProvider::class,
|
||||||
Toby\Providers\TelescopeServiceProvider::class,
|
Toby\Providers\TelescopeServiceProvider::class,
|
||||||
|
Toby\Providers\ObserverServiceProvider::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
24
database/factories/VacationLimitFactory.php
Normal file
24
database/factories/VacationLimitFactory.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class VacationLimitFactory extends Factory
|
||||||
|
{
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$hasVacation = $this->faker->boolean(75);
|
||||||
|
|
||||||
|
return [
|
||||||
|
"user_id" => User::factory(),
|
||||||
|
"year_period_id" => YearPeriod::factory(),
|
||||||
|
"has_vacation" => $hasVacation,
|
||||||
|
"days" => $hasVacation ? $this->faker->numberBetween(20, 26) : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
return new class() extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create("vacation_limits", function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->integer("days")->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists("vacation_limits");
|
||||||
|
}
|
||||||
|
};
|
@ -6,23 +6,61 @@ namespace Database\Seeders;
|
|||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Toby\Helpers\UserAvatarGenerator;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\VacationLimit;
|
||||||
use Toby\Models\YearPeriod;
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected UserAvatarGenerator $avatarGenerator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
User::factory(35)->create();
|
User::unsetEventDispatcher();
|
||||||
|
YearPeriod::unsetEventDispatcher();
|
||||||
|
|
||||||
|
User::factory(9)->create();
|
||||||
User::factory([
|
User::factory([
|
||||||
"email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"),
|
"email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"),
|
||||||
])->create();
|
])->create();
|
||||||
|
|
||||||
YearPeriod::factory([
|
$users = User::all();
|
||||||
|
|
||||||
|
$this->generateAvatarsForUsers($users);
|
||||||
|
|
||||||
|
YearPeriod::factory()
|
||||||
|
->count(3)
|
||||||
|
->sequence(
|
||||||
|
[
|
||||||
|
"year" => Carbon::now()->year - 1,
|
||||||
|
],
|
||||||
|
[
|
||||||
"year" => Carbon::now()->year,
|
"year" => Carbon::now()->year,
|
||||||
])->create();
|
],
|
||||||
YearPeriod::factory([
|
[
|
||||||
"year" => Carbon::now()->year + 1,
|
"year" => Carbon::now()->year + 1,
|
||||||
])->create();
|
],
|
||||||
|
)
|
||||||
|
->afterCreating(function (YearPeriod $yearPeriod) use ($users): void {
|
||||||
|
foreach ($users as $user) {
|
||||||
|
VacationLimit::factory()
|
||||||
|
->for($yearPeriod)
|
||||||
|
->for($user)
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateAvatarsForUsers(Collection $users): void
|
||||||
|
{
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$user->saveAvatar($this->avatarGenerator->generateFor($user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "application",
|
"name": "toby",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
175
resources/js/Pages/VacationLimits.vue
Normal file
175
resources/js/Pages/VacationLimits.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<InertiaHead title="Użytkownicy" />
|
||||||
|
<div class="bg-white sm:rounded-lg shadow-md">
|
||||||
|
<div class="flex justify-between items-center p-4 sm:px-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Dostępne dni urlopu dla użytkowników
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
Zarządzaj dostepnymi dniami urlopów dla użytkowników.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-200">
|
||||||
|
<div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible">
|
||||||
|
<form @submit.prevent="submitVacationDays">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Imię i nazwisko
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Forma zatrudnienia
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Posiada urlop?
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Dostępne dni w roku
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in form.items"
|
||||||
|
:key="item.id"
|
||||||
|
class="hover:bg-blumilk-25"
|
||||||
|
>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<div class="flex">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center justify-center h-10 w-10 rounded-full"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="h-10 w-10 rounded-full"
|
||||||
|
:src="item.user.avatar"
|
||||||
|
alt=""
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ml-3">
|
||||||
|
<p class="text-sm font-medium break-all text-gray-900">
|
||||||
|
{{ item.user.name }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm break-all text-gray-500">
|
||||||
|
{{ item.user.email }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{{ item.user.employmentForm }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<Switch
|
||||||
|
v-model="item.hasVacation"
|
||||||
|
:class="[item.hasVacation ? 'bg-blumilk-500' : 'bg-gray-200', 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500']"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[item.hasVacation ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200']"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<input
|
||||||
|
v-model="item.days"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="block w-full shadow-sm rounded-md sm:text-sm disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none disabled:cursor-not-allowed"
|
||||||
|
:disabled="!item.hasVacation"
|
||||||
|
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors[`items.${index}.days`], 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors[`items.${index}.days`] }"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
v-if="form.errors[`items.${index}.days`]"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors[`items.${index}.days`] }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-if="!form.items.length"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
colspan="100%"
|
||||||
|
class="text-center py-4 text-xl leading-5 text-gray-700"
|
||||||
|
>
|
||||||
|
Brak danych
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="flex justify-end py-3 px-4">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
|
>
|
||||||
|
Zapisz
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Switch} from '@headlessui/vue';
|
||||||
|
import {useForm} from '@inertiajs/inertia-vue3';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationLimits',
|
||||||
|
components: {
|
||||||
|
Switch,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
limits: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
years: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const form = useForm({
|
||||||
|
items: props.limits.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitVacationDays() {
|
||||||
|
this.form
|
||||||
|
.transform(data => ({
|
||||||
|
items: data.items.map(item => ({
|
||||||
|
id: item.id,
|
||||||
|
days: item.hasVacation ? item.days : null,
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
.put('/vacation-limits', {
|
||||||
|
preserveState: (page) => Object.keys(page.props.errors).length,
|
||||||
|
preserveScroll: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
@ -19,6 +19,53 @@
|
|||||||
|
|
||||||
<!-- Right section on desktop -->
|
<!-- Right section on desktop -->
|
||||||
<div class="hidden lg:ml-4 lg:flex lg:items-center lg:py-5 lg:pr-0.5">
|
<div class="hidden lg:ml-4 lg:flex lg:items-center lg:py-5 lg:pr-0.5">
|
||||||
|
<div class="mr-4">
|
||||||
|
<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-1 focus:ring-gray-300">
|
||||||
|
{{ years.current }}
|
||||||
|
<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="(item, index) in years.navigation"
|
||||||
|
:key="index"
|
||||||
|
v-slot="{ active }"
|
||||||
|
>
|
||||||
|
<InertiaLink
|
||||||
|
:href="item.link"
|
||||||
|
as="button"
|
||||||
|
method="post"
|
||||||
|
:preserve-state="false"
|
||||||
|
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex w-full px-4 py-2 text-sm']"
|
||||||
|
>
|
||||||
|
{{ item.year }}
|
||||||
|
<CheckIcon
|
||||||
|
v-if="item.year === years.current"
|
||||||
|
class="h-5 w-5 text-blumilk-500 ml-2"
|
||||||
|
/>
|
||||||
|
</InertiaLink>
|
||||||
|
</MenuItem>
|
||||||
|
</div>
|
||||||
|
</MenuItems>
|
||||||
|
</transition>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
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"
|
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"
|
||||||
@ -246,6 +293,7 @@ import {
|
|||||||
import {BellIcon, MenuIcon, XIcon} from '@heroicons/vue/outline';
|
import {BellIcon, MenuIcon, XIcon} from '@heroicons/vue/outline';
|
||||||
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',
|
name: 'MainMenu',
|
||||||
@ -263,13 +311,17 @@ export default {
|
|||||||
BellIcon,
|
BellIcon,
|
||||||
MenuIcon,
|
MenuIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
CheckIcon,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const user = computed(() => usePage().props.value.auth.user);
|
const user = computed(() => usePage().props.value.auth.user);
|
||||||
|
const years = computed(() => usePage().props.value.years);
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{name: 'Strona główna', href: '/', current: true},
|
{name: 'Strona główna', href: '/', current: true},
|
||||||
{name: 'Użytkownicy', href: '/users', current: false},
|
{name: 'Użytkownicy', href: '/users', current: false},
|
||||||
{name: 'Resources', href: '#', current: false},
|
{name: 'Dostępne urlopy', href: '/vacation-limits', current: false},
|
||||||
{name: 'Company Directory', href: '#', current: false},
|
{name: 'Company Directory', href: '#', current: false},
|
||||||
{name: 'Openings', href: '#', current: false},
|
{name: 'Openings', href: '#', current: false},
|
||||||
];
|
];
|
||||||
@ -281,6 +333,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
|
years,
|
||||||
navigation,
|
navigation,
|
||||||
userNavigation,
|
userNavigation,
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,9 @@ declare(strict_types=1);
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Toby\Http\Controllers\GoogleController;
|
use Toby\Http\Controllers\GoogleController;
|
||||||
use Toby\Http\Controllers\LogoutController;
|
use Toby\Http\Controllers\LogoutController;
|
||||||
|
use Toby\Http\Controllers\SelectYearPeriodController;
|
||||||
use Toby\Http\Controllers\UserController;
|
use Toby\Http\Controllers\UserController;
|
||||||
|
use Toby\Http\Controllers\VacationLimitController;
|
||||||
|
|
||||||
Route::middleware("auth")->group(function (): void {
|
Route::middleware("auth")->group(function (): void {
|
||||||
Route::get("/", fn() => inertia("Dashboard"))->name("dashboard");
|
Route::get("/", fn() => inertia("Dashboard"))->name("dashboard");
|
||||||
@ -13,6 +15,11 @@ Route::middleware("auth")->group(function (): void {
|
|||||||
|
|
||||||
Route::resource("users", UserController::class);
|
Route::resource("users", UserController::class);
|
||||||
Route::post("users/{user}/restore", [UserController::class, "restore"])->withTrashed();
|
Route::post("users/{user}/restore", [UserController::class, "restore"])->withTrashed();
|
||||||
|
|
||||||
|
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits");
|
||||||
|
Route::put("/vacation-limits", [VacationLimitController::class, "update"]);
|
||||||
|
|
||||||
|
Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)->name("year-periods.select");
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware("guest")->group(function (): void {
|
Route::middleware("guest")->group(function (): void {
|
||||||
|
@ -5,10 +5,10 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Tests\TestCase;
|
use Tests\FeatureTestCase;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
|
||||||
class AuthenticationTest extends TestCase
|
class AuthenticationTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
use DatabaseMigrations;
|
use DatabaseMigrations;
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ namespace Tests\Feature;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Inertia\Testing\AssertableInertia as Assert;
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
use Tests\TestCase;
|
use Tests\FeatureTestCase;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
|
||||||
class InertiaTest extends TestCase
|
class InertiaTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
use DatabaseMigrations;
|
use DatabaseMigrations;
|
||||||
|
|
||||||
|
53
tests/Feature/SelectYearPeriodTest.php
Normal file
53
tests/Feature/SelectYearPeriodTest.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Models\User;
|
||||||
|
|
||||||
|
class SelectYearPeriodTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
use DatabaseMigrations;
|
||||||
|
|
||||||
|
protected YearPeriodRetriever $yearPeriodRetriever;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->yearPeriodRetriever = $this->app->make(YearPeriodRetriever::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCanSelectNextYearPeriod(): void
|
||||||
|
{
|
||||||
|
$nextYearPeriod = $this->createYearPeriod(Carbon::now()->year + 1);
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/year-periods/{$nextYearPeriod->id}/select")
|
||||||
|
->assertRedirect();
|
||||||
|
|
||||||
|
$this->assertSame($nextYearPeriod->id, $this->yearPeriodRetriever->selected()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCannotSelectNextYearPeriodIfDoesntExist(): void
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->post("/year-periods/25/select")
|
||||||
|
->assertNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIfUserDoesntSelectAnyYearPeriodCurrentActsAsSelected(): void
|
||||||
|
{
|
||||||
|
$currentYearPeriod = $this->yearPeriodRetriever->current();
|
||||||
|
|
||||||
|
$this->assertSame($currentYearPeriod->id, $this->yearPeriodRetriever->selected()->id);
|
||||||
|
}
|
||||||
|
}
|
@ -6,23 +6,15 @@ namespace Tests\Feature;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
use Inertia\Testing\AssertableInertia as Assert;
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
use Tests\TestCase;
|
use Tests\FeatureTestCase;
|
||||||
use Toby\Enums\EmploymentForm;
|
use Toby\Enums\EmploymentForm;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
|
||||||
class UserTest extends TestCase
|
class UserTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
use DatabaseMigrations;
|
use DatabaseMigrations;
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
Storage::fake();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testAdminCanSeeUsersList(): void
|
public function testAdminCanSeeUsersList(): void
|
||||||
{
|
{
|
||||||
User::factory()->count(10)->create();
|
User::factory()->count(10)->create();
|
||||||
|
77
tests/Feature/VacationLimitTest.php
Normal file
77
tests/Feature/VacationLimitTest.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
|
use Tests\FeatureTestCase;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\VacationLimit;
|
||||||
|
|
||||||
|
class VacationLimitTest extends FeatureTestCase
|
||||||
|
{
|
||||||
|
use DatabaseMigrations;
|
||||||
|
|
||||||
|
public function testAdminCanSeeVacationLimits(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
User::factory(10)->create();
|
||||||
|
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->get("/vacation-limits")
|
||||||
|
->assertOk()
|
||||||
|
->assertInertia(
|
||||||
|
fn(Assert $page) => $page
|
||||||
|
->component("VacationLimits")
|
||||||
|
->has("limits.data", 10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdminCanUpdateVacationLimits(): void
|
||||||
|
{
|
||||||
|
$admin = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
User::factory(3)->create();
|
||||||
|
|
||||||
|
[$limit1, $limit2, $limit3] = VacationLimit::all();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
"id" => $limit1->id,
|
||||||
|
"days" => 25,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"id" => $limit2->id,
|
||||||
|
"days" => null,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"id" => $limit3->id,
|
||||||
|
"days" => 20,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->put("/vacation-limits", [
|
||||||
|
"items" => $data,
|
||||||
|
])
|
||||||
|
->assertRedirect();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_limits", [
|
||||||
|
"id" => $limit1->id,
|
||||||
|
"days" => 25,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_limits", [
|
||||||
|
"id" => $limit2->id,
|
||||||
|
"days" => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_limits", [
|
||||||
|
"id" => $limit3->id,
|
||||||
|
"days" => 20,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
23
tests/FeatureTestCase.php
Normal file
23
tests/FeatureTestCase.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
|
|
||||||
|
abstract class FeatureTestCase extends BaseTestCase
|
||||||
|
{
|
||||||
|
use CreatesApplication;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
Carbon::setTestNow(Carbon::now());
|
||||||
|
$this->createCurrentYearPeriod();
|
||||||
|
}
|
||||||
|
}
|
49
tests/Traits/InteractsWithYearPeriods.php
Normal file
49
tests/Traits/InteractsWithYearPeriods.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\Concerns\InteractsWithSession;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
trait InteractsWithYearPeriods
|
||||||
|
{
|
||||||
|
use InteractsWithSession;
|
||||||
|
|
||||||
|
public function createYearPeriod(int $year): YearPeriod
|
||||||
|
{
|
||||||
|
/** @var YearPeriod $yearPeriod */
|
||||||
|
$yearPeriod = YearPeriod::factory()->create([
|
||||||
|
"year" => $year,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $yearPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createCurrentYearPeriod(): YearPeriod
|
||||||
|
{
|
||||||
|
return $this->createYearPeriod(Carbon::now()->year);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markYearPeriodAsSelected(YearPeriod $yearPeriod): void
|
||||||
|
{
|
||||||
|
$this->session([
|
||||||
|
YearPeriodRetriever::SESSION_KEY => $yearPeriod->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearSelectedYearPeriod(): void
|
||||||
|
{
|
||||||
|
$this->session([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cleanYearPeriods(): void
|
||||||
|
{
|
||||||
|
$this->clearSelectedYearPeriod();
|
||||||
|
|
||||||
|
YearPeriod::query()->delete();
|
||||||
|
}
|
||||||
|
}
|
@ -7,16 +7,19 @@ namespace Tests\Unit;
|
|||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
use Toby\Models\User;
|
use Toby\Models\User;
|
||||||
|
|
||||||
class AvatarTest extends TestCase
|
class AvatarTest extends TestCase
|
||||||
{
|
{
|
||||||
use DatabaseMigrations;
|
use DatabaseMigrations;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->createCurrentYearPeriod();
|
||||||
Storage::fake();
|
Storage::fake();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,13 @@ namespace Tests\Unit;
|
|||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
use Toby\Jobs\CheckYearPeriod;
|
use Toby\Jobs\CheckYearPeriod;
|
||||||
use Toby\Models\YearPeriod;
|
|
||||||
|
|
||||||
class CheckYearPeriodTest extends TestCase
|
class CheckYearPeriodTest extends TestCase
|
||||||
{
|
{
|
||||||
use RefreshDatabase;
|
use RefreshDatabase;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
public function testYearPeriodsAreCreatedWhenDontExist(): void
|
public function testYearPeriodsAreCreatedWhenDontExist(): void
|
||||||
{
|
{
|
||||||
@ -54,9 +55,7 @@ class CheckYearPeriodTest extends TestCase
|
|||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
Carbon::setTestNow($now);
|
Carbon::setTestNow($now);
|
||||||
|
|
||||||
YearPeriod::factory([
|
$this->createCurrentYearPeriod();
|
||||||
"year" => $now->year,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->assertDatabaseMissing("year_periods", [
|
$this->assertDatabaseMissing("year_periods", [
|
||||||
"year" => $now->year + 1,
|
"year" => $now->year + 1,
|
||||||
@ -74,12 +73,8 @@ class CheckYearPeriodTest extends TestCase
|
|||||||
$now = Carbon::now();
|
$now = Carbon::now();
|
||||||
Carbon::setTestNow($now);
|
Carbon::setTestNow($now);
|
||||||
|
|
||||||
YearPeriod::factory([
|
$this->createCurrentYearPeriod();
|
||||||
"year" => $now->year,
|
$this->createYearPeriod($now->year + 1);
|
||||||
])->create();
|
|
||||||
YearPeriod::factory([
|
|
||||||
"year" => $now->year + 1,
|
|
||||||
])->create();
|
|
||||||
|
|
||||||
$this->assertDatabaseCount("year_periods", 2);
|
$this->assertDatabaseCount("year_periods", 2);
|
||||||
|
|
||||||
|
50
tests/Unit/VacationLimitTest.php
Normal file
50
tests/Unit/VacationLimitTest.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
|
use Toby\Models\User;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class VacationLimitTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseMigrations;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->createCurrentYearPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhenUserIsCreatedThenVacationLimitIsCreatedForCurrentYearPeriod(): void
|
||||||
|
{
|
||||||
|
$this->assertDatabaseCount("vacation_limits", 0);
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$this->assertDatabaseCount("vacation_limits", 1);
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_limits", [
|
||||||
|
"user_id" => $user->id,
|
||||||
|
"year_period_id" => $currentYearPeriod->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWhenYearPeriodIsCreatedThenVacationLimitsForThisYearPeriodAreCreated(): void
|
||||||
|
{
|
||||||
|
$this->assertDatabaseCount("vacation_limits", 0);
|
||||||
|
|
||||||
|
User::factory(10)->createQuietly();
|
||||||
|
|
||||||
|
YearPeriod::factory()->create();
|
||||||
|
|
||||||
|
$this->assertDatabaseCount("vacation_limits", 10);
|
||||||
|
}
|
||||||
|
}
|
82
tests/Unit/YearPeriodRetrieverTest.php
Normal file
82
tests/Unit/YearPeriodRetrieverTest.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Unit;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\Concerns\InteractsWithSession;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Tests\TestCase;
|
||||||
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
|
use Toby\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Models\YearPeriod;
|
||||||
|
|
||||||
|
class YearPeriodRetrieverTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
use InteractsWithSession;
|
||||||
|
use InteractsWithYearPeriods;
|
||||||
|
|
||||||
|
public Carbon $current;
|
||||||
|
public YearPeriod $previousYearPeriod;
|
||||||
|
public YearPeriod $currentYearPeriod;
|
||||||
|
public YearPeriod $nextYearPeriod;
|
||||||
|
public YearPeriodRetriever $yearPeriodRetriever;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->current = Carbon::now();
|
||||||
|
Carbon::setTestNow($this->current);
|
||||||
|
|
||||||
|
$this->yearPeriodRetriever = $this->app->make(YearPeriodRetriever::class);
|
||||||
|
|
||||||
|
$this->previousYearPeriod = $this->createYearPeriod($this->current->year - 1);
|
||||||
|
$this->currentYearPeriod = $this->createCurrentYearPeriod();
|
||||||
|
$this->nextYearPeriod = $this->createYearPeriod($this->current->year + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetrievesCorrectCurrentYearPeriod(): void
|
||||||
|
{
|
||||||
|
$this->assertSame($this->currentYearPeriod->id, $this->yearPeriodRetriever->current()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetrievesCurrentYearPeriodWhenNoneIsSelected(): void
|
||||||
|
{
|
||||||
|
$this->clearSelectedYearPeriod();
|
||||||
|
|
||||||
|
$this->assertSame($this->currentYearPeriod->id, $this->yearPeriodRetriever->selected()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRetrievesCorrectYearPeriodWhenSelected(): void
|
||||||
|
{
|
||||||
|
$this->markYearPeriodAsSelected($this->nextYearPeriod);
|
||||||
|
|
||||||
|
$this->assertSame($this->nextYearPeriod->id, $this->yearPeriodRetriever->selected()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLinks(): void
|
||||||
|
{
|
||||||
|
$expected = [
|
||||||
|
"current" => $this->current->year,
|
||||||
|
"navigation" => [
|
||||||
|
[
|
||||||
|
"year" => $this->previousYearPeriod->year,
|
||||||
|
"link" => route("year-periods.select", $this->previousYearPeriod),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"year" => $this->currentYearPeriod->year,
|
||||||
|
"link" => route("year-periods.select", $this->currentYearPeriod),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"year" => $this->nextYearPeriod->year,
|
||||||
|
"link" => route("year-periods.select", $this->nextYearPeriod),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertSame($expected, $this->yearPeriodRetriever->links());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user