#118 - keys
This commit is contained in:
		| @@ -7,7 +7,9 @@ namespace Toby\Architecture\Providers; | |||||||
| use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; | use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; | ||||||
| use Illuminate\Support\Facades\Gate; | use Illuminate\Support\Facades\Gate; | ||||||
| use Toby\Domain\Enums\Role; | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Policies\KeyPolicy; | ||||||
| use Toby\Domain\Policies\VacationRequestPolicy; | use Toby\Domain\Policies\VacationRequestPolicy; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| @@ -15,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider | |||||||
| { | { | ||||||
|     protected $policies = [ |     protected $policies = [ | ||||||
|         VacationRequest::class => VacationRequestPolicy::class, |         VacationRequest::class => VacationRequestPolicy::class, | ||||||
|  |         Key::class => KeyPolicy::class, | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								app/Domain/Policies/KeyPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Domain/Policies/KeyPolicy.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Policies; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class KeyPolicy | ||||||
|  | { | ||||||
|  |     public function manage(User $user): bool | ||||||
|  |     { | ||||||
|  |         return $user->role === Role::AdministrativeApprover; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function give(User $user, Key $key): bool | ||||||
|  |     { | ||||||
|  |         if ($key->user()->is($user)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->role === Role::AdministrativeApprover; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,8 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
|  * @property User $owner |  * @property User $user | ||||||
|  * @property User $previousOwner |  | ||||||
|  */ |  */ | ||||||
| class Key extends Model | class Key extends Model | ||||||
| { | { | ||||||
| @@ -20,14 +19,9 @@ class Key extends Model | |||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     public function owner(): BelongsTo |     public function user(): BelongsTo | ||||||
|     { |     { | ||||||
|         return $this->belongsTo(User::class, "owner_id"); |         return $this->belongsTo(User::class); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public function previousOwner(): BelongsTo |  | ||||||
|     { |  | ||||||
|         return $this->belongsTo(User::class, "previous_owner_id"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): KeyFactory |     protected static function newFactory(): KeyFactory | ||||||
|   | |||||||
| @@ -74,6 +74,11 @@ class User extends Authenticatable | |||||||
|         return $this->hasMany(Vacation::class); |         return $this->hasMany(Vacation::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function keys(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(Key::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function hasRole(Role $role): bool |     public function hasRole(Role $role): bool | ||||||
|     { |     { | ||||||
|         return $this->role === $role; |         return $this->role === $role; | ||||||
|   | |||||||
| @@ -4,20 +4,83 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Infrastructure\Http\Controllers; | namespace Toby\Infrastructure\Http\Controllers; | ||||||
|  |  | ||||||
|  | use Illuminate\Auth\Access\AuthorizationException; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Inertia\Response; | use Inertia\Response; | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; | use Symfony\Component\HttpFoundation\RedirectResponse; | ||||||
| use Toby\Eloquent\Models\Key; | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Infrastructure\Http\Requests\GiveKeyRequest; | ||||||
| use Toby\Infrastructure\Http\Resources\KeyResource; | use Toby\Infrastructure\Http\Resources\KeyResource; | ||||||
|  | use Toby\Infrastructure\Http\Resources\SimpleUserResource; | ||||||
|  |  | ||||||
| class KeysController extends Controller | class KeysController extends Controller | ||||||
| { | { | ||||||
|     public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response |     public function index(Request $request): Response | ||||||
|     { |     { | ||||||
|         $keys = Key::query()->get(); |         $keys = Key::query() | ||||||
|  |             ->oldest() | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|         return inertia("Keys/Index", [ |         $users = User::query() | ||||||
|  |             ->where("id", "!=", $request->user()->id) | ||||||
|  |             ->orderByProfileField("last_name") | ||||||
|  |             ->orderByProfileField("first_name") | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         return inertia("Keys", [ | ||||||
|             "keys" => KeyResource::collection($keys), |             "keys" => KeyResource::collection($keys), | ||||||
|  |             "users" => SimpleUserResource::collection($users), | ||||||
|  |             "can" => [ | ||||||
|  |                 "manageKeys" => $request->user()->can("manage", Key::class), | ||||||
|  |             ], | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws AuthorizationException | ||||||
|  |      */ | ||||||
|  |     public function store(Request $request): RedirectResponse | ||||||
|  |     { | ||||||
|  |         $this->authorize("manageKeys"); | ||||||
|  |  | ||||||
|  |         $request->user()->keys()->create(); | ||||||
|  |  | ||||||
|  |         return redirect() | ||||||
|  |             ->back() | ||||||
|  |             ->with("success", __("Key has been created.")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function take(Key $key, Request $request): RedirectResponse | ||||||
|  |     { | ||||||
|  |         $key->user()->associate($request->user()); | ||||||
|  |  | ||||||
|  |         $key->save(); | ||||||
|  |  | ||||||
|  |         return redirect() | ||||||
|  |             ->back() | ||||||
|  |             ->with("success", __("Key has been taked.")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function give(Key $key, GiveKeyRequest $request): RedirectResponse | ||||||
|  |     { | ||||||
|  |         $key->user()->associate($request->recipient()); | ||||||
|  |  | ||||||
|  |         $key->save(); | ||||||
|  |  | ||||||
|  |         return redirect() | ||||||
|  |             ->back() | ||||||
|  |             ->with("success", __("Key has been given.")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function destroy(Key $key): RedirectResponse | ||||||
|  |     { | ||||||
|  |         $this->authorize("manageKeys"); | ||||||
|  |  | ||||||
|  |         $key->delete(); | ||||||
|  |  | ||||||
|  |         return redirect() | ||||||
|  |             ->back() | ||||||
|  |             ->with("success", __("Key has been deleted.")); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								app/Infrastructure/Http/Requests/GiveKeyRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/Infrastructure/Http/Requests/GiveKeyRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Requests; | ||||||
|  |  | ||||||
|  | use Illuminate\Foundation\Http\FormRequest; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class GiveKeyRequest extends FormRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "user" => ["required", "exists:users,id"], | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function recipient(): User | ||||||
|  |     { | ||||||
|  |         return User::find($this->get("user")); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -14,9 +14,12 @@ class KeyResource extends JsonResource | |||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|             "id" => $this->id, |             "id" => $this->id, | ||||||
|             "owner" => new UserResource($this->owner), |             "user" => new SimpleUserResource($this->user), | ||||||
|             "previousOwner" => new UserResource($this->previousOwner), |  | ||||||
|             "updatedAt" => $this->updated_at->toDatetimeString(), |             "updatedAt" => $this->updated_at->toDatetimeString(), | ||||||
|  |             "can" => [ | ||||||
|  |                 "give" => $request->user()->can("give", $this->resource), | ||||||
|  |                 "take" => !$this->user()->is($request->user()), | ||||||
|  |             ], | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,8 +15,7 @@ class KeyFactory extends Factory | |||||||
|     public function definition(): array |     public function definition(): array | ||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|             "owner_id" => User::factory(), |             "user_id" => User::factory(), | ||||||
|             "previous_owner_id" => User::factory(), |  | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,8 +12,9 @@ return new class() extends Migration { | |||||||
|     { |     { | ||||||
|         Schema::create("keys", function (Blueprint $table): void { |         Schema::create("keys", function (Blueprint $table): void { | ||||||
|             $table->id(); |             $table->id(); | ||||||
|             $table->foreignIdFor(User::class, "owner_id")->constrained("users")->cascadeOnDelete(); |             $table->foreignIdFor(User::class) | ||||||
|             $table->foreignIdFor(User::class, "previous_owner_id")->constrained("users")->cascadeOnDelete(); |                 ->constrained() | ||||||
|  |                 ->cascadeOnDelete(); | ||||||
|             $table->timestamps(); |             $table->timestamps(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ use Toby\Domain\States\VacationRequest\Rejected; | |||||||
| use Toby\Domain\States\VacationRequest\WaitingForAdministrative; | use Toby\Domain\States\VacationRequest\WaitingForAdministrative; | ||||||
| use Toby\Domain\States\VacationRequest\WaitingForTechnical; | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\VacationDaysCalculator; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationLimit; | use Toby\Eloquent\Models\VacationLimit; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
| @@ -328,5 +329,11 @@ class DemoSeeder extends Seeder | |||||||
|  |  | ||||||
|         $vacationRequestRejected->state = new Rejected($vacationRequestRejected); |         $vacationRequestRejected->state = new Rejected($vacationRequestRejected); | ||||||
|         $vacationRequestRejected->save(); |         $vacationRequestRejected->save(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             Key::factory() | ||||||
|  |                 ->for($user) | ||||||
|  |                 ->create(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										349
									
								
								resources/js/Pages/Keys.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								resources/js/Pages/Keys.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,349 @@ | |||||||
|  | <template> | ||||||
|  |   <InertiaHead title="Klucze" /> | ||||||
|  |   <div class="bg-white shadow-md"> | ||||||
|  |     <div class="flex justify-between items-center p-4 sm:px-6"> | ||||||
|  |       <div> | ||||||
|  |         <h2 class="text-lg font-medium leading-6 text-gray-900"> | ||||||
|  |           Klucze | ||||||
|  |         </h2> | ||||||
|  |       </div> | ||||||
|  |       <div v-if="can.manageKeys"> | ||||||
|  |         <InertiaLink | ||||||
|  |           as="button" | ||||||
|  |           href="/keys" | ||||||
|  |           method="post" | ||||||
|  |           class="inline-flex items-center py-3 px-4 text-sm font-medium leading-4 text-white bg-blumilk-600 hover:bg-blumilk-700 rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm" | ||||||
|  |         > | ||||||
|  |           Dodaj klucz | ||||||
|  |         </InertiaLink> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="border-t border-gray-200"> | ||||||
|  |       <div class="overflow-auto xl:overflow-visible"> | ||||||
|  |         <table class="min-w-full divide-y divide-gray-200"> | ||||||
|  |           <thead class="bg-gray-50"> | ||||||
|  |             <tr> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" | ||||||
|  |               > | ||||||
|  |                 Nazwa | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" | ||||||
|  |               > | ||||||
|  |                 Kto ma | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" | ||||||
|  |               > | ||||||
|  |                 Od kiedy | ||||||
|  |               </th> | ||||||
|  |               <th | ||||||
|  |                 scope="col" | ||||||
|  |                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" | ||||||
|  |               /> | ||||||
|  |             </tr> | ||||||
|  |           </thead> | ||||||
|  |           <tbody class="bg-white divide-y divide-gray-100"> | ||||||
|  |             <tr | ||||||
|  |               v-for="key in keys.data" | ||||||
|  |               :key="key.id" | ||||||
|  |               class="hover:bg-blumilk-25" | ||||||
|  |             > | ||||||
|  |               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||||
|  |                 Klucz nr {{ key.id }} | ||||||
|  |               </td> | ||||||
|  |               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||||
|  |                 <div class="flex"> | ||||||
|  |                   <span class="inline-flex justify-center items-center w-10 h-10 rounded-full"> | ||||||
|  |                     <img | ||||||
|  |                       class="w-10 h-10 rounded-full" | ||||||
|  |                       :src="key.user.avatar" | ||||||
|  |                     > | ||||||
|  |                   </span> | ||||||
|  |                   <div class="ml-3"> | ||||||
|  |                     <p class="text-sm font-medium text-gray-900 break-all"> | ||||||
|  |                       {{ key.user.name }} | ||||||
|  |                     </p> | ||||||
|  |                     <p class="text-sm text-gray-500 break-all"> | ||||||
|  |                       {{ key.user.email }} | ||||||
|  |                     </p> | ||||||
|  |                   </div> | ||||||
|  |                 </div> | ||||||
|  |               </td> | ||||||
|  |               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> | ||||||
|  |                 {{ DateTime.fromSQL(key.updatedAt).toRelative() }} | ||||||
|  |               </td> | ||||||
|  |               <td class="p-4 text-sm text-right text-gray-500 whitespace-nowrap"> | ||||||
|  |                 <Menu | ||||||
|  |                   as="div" | ||||||
|  |                   class="inline-block relative text-left" | ||||||
|  |                 > | ||||||
|  |                   <MenuButton | ||||||
|  |                     class="flex items-center text-gray-400 hover:text-gray-600 rounded-full focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 focus:ring-offset-gray-100" | ||||||
|  |                   > | ||||||
|  |                     <DotsVerticalIcon class="w-5 h-5" /> | ||||||
|  |                   </MenuButton> | ||||||
|  |  | ||||||
|  |                   <transition | ||||||
|  |                     enter-active-class="transition ease-out duration-100" | ||||||
|  |                     enter-from-class="transform opacity-0 scale-95" | ||||||
|  |                     enter-to-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-active-class="transition ease-in duration-75" | ||||||
|  |                     leave-from-class="transform opacity-100 scale-100" | ||||||
|  |                     leave-to-class="transform opacity-0 scale-95" | ||||||
|  |                   > | ||||||
|  |                     <MenuItems | ||||||
|  |                       class="absolute right-0 z-10 mt-2 w-56 bg-white rounded-md focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg origin-top-right" | ||||||
|  |                     > | ||||||
|  |                       <div class="py-1"> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-if="key.can.take" | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             as="button" | ||||||
|  |                             method="post" | ||||||
|  |                             preserve-scroll | ||||||
|  |                             :href="`/keys/${key.id}/take`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <DominoMaskIcon class="mr-2 w-5 h-5 text-purple-500" /> | ||||||
|  |                             Zabierz klucze | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-if="key.can.give" | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <button | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                             @click="giveKey(key)" | ||||||
|  |                           > | ||||||
|  |                             <HandshakeIcon class="mr-2 w-5 h-5 text-emerald-500" /> | ||||||
|  |                             Przekaż klucze | ||||||
|  |                           </button> | ||||||
|  |                         </MenuItem> | ||||||
|  |                         <MenuItem | ||||||
|  |                           v-if="can.manageKeys" | ||||||
|  |                           v-slot="{ active }" | ||||||
|  |                           class="flex" | ||||||
|  |                         > | ||||||
|  |                           <InertiaLink | ||||||
|  |                             as="button" | ||||||
|  |                             method="delete" | ||||||
|  |                             preserve-scroll | ||||||
|  |                             :href="`/keys/${key.id}`" | ||||||
|  |                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" | ||||||
|  |                           > | ||||||
|  |                             <TrashIcon class="mr-2 w-5 h-5 text-red-500" /> | ||||||
|  |                             Usuń | ||||||
|  |                           </InertiaLink> | ||||||
|  |                         </MenuItem> | ||||||
|  |                       </div> | ||||||
|  |                     </MenuItems> | ||||||
|  |                   </transition> | ||||||
|  |                 </Menu> | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |             <tr v-if="!keys.data.length"> | ||||||
|  |               <td | ||||||
|  |                 colspan="100%" | ||||||
|  |                 class="py-4 text-xl leading-5 text-center text-gray-700" | ||||||
|  |               > | ||||||
|  |                 Brak danych | ||||||
|  |               </td> | ||||||
|  |             </tr> | ||||||
|  |           </tbody> | ||||||
|  |         </table> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <TransitionRoot | ||||||
|  |     as="template" | ||||||
|  |     :show="giving" | ||||||
|  |   > | ||||||
|  |     <Dialog | ||||||
|  |       is="div" | ||||||
|  |       class="overflow-y-auto fixed inset-0 z-10" | ||||||
|  |       @close="giving = false" | ||||||
|  |     > | ||||||
|  |       <div class="flex justify-center items-end px-4 pt-4 pb-20 min-h-screen text-center sm:block sm:p-0"> | ||||||
|  |         <TransitionChild | ||||||
|  |           as="template" | ||||||
|  |           enter="ease-out duration-300" | ||||||
|  |           enter-from="opacity-0" | ||||||
|  |           enter-to="opacity-100" | ||||||
|  |           leave="ease-in duration-200" | ||||||
|  |           leave-from="opacity-100" | ||||||
|  |           leave-to="opacity-0" | ||||||
|  |         > | ||||||
|  |           <DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> | ||||||
|  |         </TransitionChild> | ||||||
|  |  | ||||||
|  |         <span class="hidden sm:inline-block sm:h-screen sm:align-middle">​</span> | ||||||
|  |         <TransitionChild | ||||||
|  |           as="template" | ||||||
|  |           enter="ease-out duration-300" | ||||||
|  |           enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | ||||||
|  |           enter-to="opacity-100 translate-y-0 sm:scale-100" | ||||||
|  |           leave="ease-in duration-200" | ||||||
|  |           leave-from="opacity-100 translate-y-0 sm:scale-100" | ||||||
|  |           leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" | ||||||
|  |         > | ||||||
|  |           <form | ||||||
|  |             class="inline-block relative px-4 pt-5 pb-4 text-left align-bottom bg-white rounded-lg shadow-xl transition-all transform sm:p-6 sm:my-8 sm:w-full sm:max-w-sm sm:align-middle" | ||||||
|  |             @submit.prevent="submitGiveKey" | ||||||
|  |           > | ||||||
|  |             <div> | ||||||
|  |               <div> | ||||||
|  |                 <DialogTitle | ||||||
|  |                   as="h3" | ||||||
|  |                   class="text-lg font-medium leading-6 text-center text-gray-900 font-sembiold" | ||||||
|  |                 > | ||||||
|  |                   Przekaż klucz nr {{ keyToGive?.id }} | ||||||
|  |                 </DialogTitle> | ||||||
|  |                 <div class="mt-5"> | ||||||
|  |                   <Listbox | ||||||
|  |                     v-model="form.user" | ||||||
|  |                     as="div" | ||||||
|  |                   > | ||||||
|  |                     <ListboxLabel class="block text-sm font-medium text-left text-gray-700"> | ||||||
|  |                       Użytkownik | ||||||
|  |                     </ListboxLabel> | ||||||
|  |                     <div class="relative mt-2"> | ||||||
|  |                       <ListboxButton | ||||||
|  |                         class="relative py-2 pr-10 pl-3 w-full max-w-lg text-left bg-white rounded-md border focus:outline-none focus:ring-1 shadow-sm cursor-default sm:text-sm" | ||||||
|  |                         :class="{ 'border-red-300 text-red-900 focus:ring-red-500 focus:border-red-500': form.errors.user, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.user }" | ||||||
|  |                       > | ||||||
|  |                         <span class="flex items-center"> | ||||||
|  |                           <img | ||||||
|  |                             :src="form.user.avatar" | ||||||
|  |                             class="shrink-0 w-6 h-6 rounded-full" | ||||||
|  |                           > | ||||||
|  |                           <span class="block ml-3 truncate">{{ form.user.name }}</span> | ||||||
|  |                         </span> | ||||||
|  |                         <span class="flex absolute inset-y-0 right-0 items-center pr-2 pointer-events-none"> | ||||||
|  |                           <SelectorIcon class="w-5 h-5 text-gray-400" /> | ||||||
|  |                         </span> | ||||||
|  |                       </ListboxButton> | ||||||
|  |  | ||||||
|  |                       <transition | ||||||
|  |                         leave-active-class="transition ease-in duration-100" | ||||||
|  |                         leave-from-class="opacity-100" | ||||||
|  |                         leave-to-class="opacity-0" | ||||||
|  |                       > | ||||||
|  |                         <ListboxOptions | ||||||
|  |                           class="overflow-auto absolute z-10 py-1 mt-1 w-full max-w-lg max-h-60 text-base bg-white rounded-md focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg sm:text-sm" | ||||||
|  |                         > | ||||||
|  |                           <ListboxOption | ||||||
|  |                             v-for="user in users.data" | ||||||
|  |                             :key="user.id" | ||||||
|  |                             v-slot="{ active, selected }" | ||||||
|  |                             as="template" | ||||||
|  |                             :value="user" | ||||||
|  |                           > | ||||||
|  |                             <li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']"> | ||||||
|  |                               <div class="flex items-center"> | ||||||
|  |                                 <img | ||||||
|  |                                   :src="user.avatar" | ||||||
|  |                                   alt="" | ||||||
|  |                                   class="shrink-0 w-6 h-6 rounded-full" | ||||||
|  |                                 > | ||||||
|  |                                 <span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']"> | ||||||
|  |                                   {{ user.name }} | ||||||
|  |                                 </span> | ||||||
|  |                               </div> | ||||||
|  |  | ||||||
|  |                               <span | ||||||
|  |                                 v-if="selected" | ||||||
|  |                                 :class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']" | ||||||
|  |                               > | ||||||
|  |                                 <CheckIcon class="w-5 h-5" /> | ||||||
|  |                               </span> | ||||||
|  |                             </li> | ||||||
|  |                           </ListboxOption> | ||||||
|  |                         </ListboxOptions> | ||||||
|  |                       </transition> | ||||||
|  |                       <p | ||||||
|  |                         v-if="form.errors.type" | ||||||
|  |                         class="mt-2 text-sm text-red-600" | ||||||
|  |                       > | ||||||
|  |                         {{ form.errors.type }} | ||||||
|  |                       </p> | ||||||
|  |                     </div> | ||||||
|  |                   </Listbox> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |             <div class="mt-5 sm:mt-6"> | ||||||
|  |               <div class="flex justify-end space-x-3"> | ||||||
|  |                 <button | ||||||
|  |                   type="button" | ||||||
|  |                   class="py-2 px-4 text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm" | ||||||
|  |                   @click="giving = false" | ||||||
|  |                 > | ||||||
|  |                   Anuluj | ||||||
|  |                 </button> | ||||||
|  |                 <button | ||||||
|  |                   type="submit" | ||||||
|  |                   :disabled="form.processing" | ||||||
|  |                   class="inline-flex justify-center py-2 px-4 text-base font-medium text-white bg-blumilk-600 hover:bg-blumilk-700 rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm sm:text-sm" | ||||||
|  |                 > | ||||||
|  |                   Przekaż | ||||||
|  |                 </button> | ||||||
|  |               </div> | ||||||
|  |             </div> | ||||||
|  |           </form> | ||||||
|  |         </TransitionChild> | ||||||
|  |       </div> | ||||||
|  |     </Dialog> | ||||||
|  |   </TransitionRoot> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup> | ||||||
|  | import { DotsVerticalIcon, TrashIcon, CheckIcon, SelectorIcon } from '@heroicons/vue/solid' | ||||||
|  | import DominoMaskIcon from 'vue-material-design-icons/DominoMask.vue' | ||||||
|  | import HandshakeIcon from 'vue-material-design-icons/Handshake.vue' | ||||||
|  | import { DateTime } from 'luxon' | ||||||
|  | import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' | ||||||
|  | import { ref } from 'vue' | ||||||
|  | import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue' | ||||||
|  | import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue' | ||||||
|  | import { useForm } from '@inertiajs/inertia-vue3' | ||||||
|  |  | ||||||
|  | const props = defineProps({ | ||||||
|  |   keys: Object, | ||||||
|  |   users: Object, | ||||||
|  |   can: Object, | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const keyToGive = ref(null) | ||||||
|  | const giving = ref(false) | ||||||
|  |  | ||||||
|  | const form = useForm({ | ||||||
|  |   user: props.users.data[0], | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | function giveKey(key) { | ||||||
|  |   keyToGive.value = key | ||||||
|  |   giving.value = true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function submitGiveKey() { | ||||||
|  |   form | ||||||
|  |     .transform(data => ({ | ||||||
|  |       user: data.user.id, | ||||||
|  |     })) | ||||||
|  |     .post(`/keys/${keyToGive.value.id}/give`, { | ||||||
|  |       preserveState: (page) => Object.keys(page.props.errors).length, | ||||||
|  |       preserveScroll: true, | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | </script> | ||||||
| @@ -1,162 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <InertiaHead title="Klucze" /> |  | ||||||
|   <div class="bg-white shadow-md"> |  | ||||||
|     <div class="flex justify-between items-center p-4 sm:px-6"> |  | ||||||
|       <div> |  | ||||||
|         <h2 class="text-lg font-medium leading-6 text-gray-900"> |  | ||||||
|           Klucze |  | ||||||
|         </h2> |  | ||||||
|       </div> |  | ||||||
|       <div v-if="true"> |  | ||||||
|         <InertiaLink |  | ||||||
|           href="#" |  | ||||||
|           class="inline-flex items-center py-3 px-4 text-sm font-medium leading-4 text-white bg-blumilk-600 hover:bg-blumilk-700 rounded-md border border-transparent focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 shadow-sm" |  | ||||||
|         > |  | ||||||
|           Dodaj klucz |  | ||||||
|         </InertiaLink> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|     <div class="border-t border-gray-200"> |  | ||||||
|       <div class="overflow-auto xl:overflow-visible"> |  | ||||||
|         <table class="min-w-full divide-y divide-gray-200"> |  | ||||||
|           <thead class="bg-gray-50"> |  | ||||||
|             <tr> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" |  | ||||||
|               > |  | ||||||
|                 Nazwa |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" |  | ||||||
|               > |  | ||||||
|                 Właściciel |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" |  | ||||||
|               > |  | ||||||
|                 Od kiedy |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" |  | ||||||
|               > |  | ||||||
|                 Od kogo |  | ||||||
|               </th> |  | ||||||
|               <th |  | ||||||
|                 scope="col" |  | ||||||
|                 class="py-3 px-4 text-xs font-semibold tracking-wider text-left text-gray-500 uppercase whitespace-nowrap" |  | ||||||
|               /> |  | ||||||
|             </tr> |  | ||||||
|           </thead> |  | ||||||
|           <tbody class="bg-white divide-y divide-gray-100"> |  | ||||||
|             <tr |  | ||||||
|               v-for="key in keys.data" |  | ||||||
|               :key="key.id" |  | ||||||
|               class="hover:bg-blumilk-25" |  | ||||||
|             > |  | ||||||
|               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |  | ||||||
|                 Klucz nr {{ key.id }} |  | ||||||
|               </td> |  | ||||||
|               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |  | ||||||
|                 {{ key.owner.name }} |  | ||||||
|               </td> |  | ||||||
|               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |  | ||||||
|                 {{ key.updatedAt }} |  | ||||||
|               </td> |  | ||||||
|               <td class="p-4 text-sm text-gray-500 whitespace-nowrap"> |  | ||||||
|                 {{ key.previousOwner.name }} |  | ||||||
|               </td> |  | ||||||
|               <td class="p-4 text-sm text-right text-gray-500 whitespace-nowrap"> |  | ||||||
|                 <Menu |  | ||||||
|                   v-if="true" |  | ||||||
|                   as="div" |  | ||||||
|                   class="inline-block relative text-left" |  | ||||||
|                 > |  | ||||||
|                   <MenuButton class="flex items-center text-gray-400 hover:text-gray-600 rounded-full focus:outline-none focus:ring-2 focus:ring-blumilk-500 focus:ring-offset-2 focus:ring-offset-gray-100"> |  | ||||||
|                     <DotsVerticalIcon |  | ||||||
|                       class="w-5 h-5" |  | ||||||
|                       aria-hidden="true" |  | ||||||
|                     /> |  | ||||||
|                   </MenuButton> |  | ||||||
|  |  | ||||||
|                   <transition |  | ||||||
|                     enter-active-class="transition ease-out duration-100" |  | ||||||
|                     enter-from-class="transform opacity-0 scale-95" |  | ||||||
|                     enter-to-class="transform opacity-100 scale-100" |  | ||||||
|                     leave-active-class="transition ease-in duration-75" |  | ||||||
|                     leave-from-class="transform opacity-100 scale-100" |  | ||||||
|                     leave-to-class="transform opacity-0 scale-95" |  | ||||||
|                   > |  | ||||||
|                     <MenuItems class="absolute right-0 z-10 mt-2 w-56 bg-white rounded-md focus:outline-none ring-1 ring-black ring-opacity-5 shadow-lg origin-top-right"> |  | ||||||
|                       <div class="py-1"> |  | ||||||
|                         <MenuItem |  | ||||||
|                           v-slot="{ active }" |  | ||||||
|                           class="flex" |  | ||||||
|                         > |  | ||||||
|                           <InertiaLink |  | ||||||
|                             :href="`#`" |  | ||||||
|                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                           > |  | ||||||
|                             <DominoMaskIcon class="mr-2 w-5 h-5 text-purple-500" /> Zabierz klucze |  | ||||||
|                           </InertiaLink> |  | ||||||
|                         </MenuItem> |  | ||||||
|                         <MenuItem |  | ||||||
|                           v-slot="{ active }" |  | ||||||
|                           class="flex" |  | ||||||
|                         > |  | ||||||
|                           <InertiaLink |  | ||||||
|                             :href="`#`" |  | ||||||
|                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                           > |  | ||||||
|                             <HandshakeIcon class="mr-2 w-5 h-5 text-emerald-500" /> Daj klucze |  | ||||||
|                           </InertiaLink> |  | ||||||
|                         </MenuItem> |  | ||||||
|                         <MenuItem |  | ||||||
|                           v-slot="{ active }" |  | ||||||
|                           class="flex" |  | ||||||
|                         > |  | ||||||
|                           <InertiaLink |  | ||||||
|                             as="button" |  | ||||||
|                             method="delete" |  | ||||||
|                             :preserve-scroll="true" |  | ||||||
|                             :href="`#`" |  | ||||||
|                             :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left font-medium px-4 py-2 text-sm']" |  | ||||||
|                           > |  | ||||||
|                             <TrashIcon class="mr-2 w-5 h-5 text-red-500" /> Usuń |  | ||||||
|                           </InertiaLink> |  | ||||||
|                         </MenuItem> |  | ||||||
|                       </div> |  | ||||||
|                     </MenuItems> |  | ||||||
|                   </transition> |  | ||||||
|                 </Menu> |  | ||||||
|               </td> |  | ||||||
|             </tr> |  | ||||||
|             <tr v-if="!keys.data.length"> |  | ||||||
|               <td |  | ||||||
|                 colspan="100%" |  | ||||||
|                 class="py-4 text-xl leading-5 text-center text-gray-700" |  | ||||||
|               > |  | ||||||
|                 Brak danych |  | ||||||
|               </td> |  | ||||||
|             </tr> |  | ||||||
|           </tbody> |  | ||||||
|         </table> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script setup> |  | ||||||
| import { DotsVerticalIcon, TrashIcon } from '@heroicons/vue/solid' |  | ||||||
| import DominoMaskIcon from 'vue-material-design-icons/DominoMask.vue' |  | ||||||
| import HandshakeIcon from 'vue-material-design-icons/Handshake.vue' |  | ||||||
| import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' |  | ||||||
|  |  | ||||||
| defineProps({ |  | ||||||
|   keys: Object, |  | ||||||
|   can: Object, |  | ||||||
| }) |  | ||||||
| </script>s |  | ||||||
| @@ -34,10 +34,15 @@ Route::middleware(["auth", TrackUserLastActivity::class])->group(function (): vo | |||||||
|         ->except("show") |         ->except("show") | ||||||
|         ->whereNumber("holiday"); |         ->whereNumber("holiday"); | ||||||
|  |  | ||||||
|     Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class) |     Route::get("/keys", [KeysController::class, "index"]); | ||||||
|  |     Route::post("/keys", [KeysController::class, "store"]); | ||||||
|  |     Route::delete("/keys/{key}", [KeysController::class, "destroy"]); | ||||||
|  |     Route::post("/keys/{key}/take", [KeysController::class, "take"]); | ||||||
|  |     Route::post("/keys/{key}/give", [KeysController::class, "give"]); | ||||||
|  |  | ||||||
|  |     Route::post("/year-periods/{yearPeriod}/select", SelectYearPeriodController::class) | ||||||
|         ->whereNumber("yearPeriod") |         ->whereNumber("yearPeriod") | ||||||
|         ->name("year-periods.select"); |         ->name("year-periods.select"); | ||||||
|     Route::resource("keys", KeysController::class); |  | ||||||
|  |  | ||||||
|     Route::prefix("/vacation")->as("vacation.")->group(function (): void { |     Route::prefix("/vacation")->as("vacation.")->group(function (): void { | ||||||
|         Route::get("/limits", [VacationLimitController::class, "edit"]) |         Route::get("/limits", [VacationLimitController::class, "edit"]) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user