This commit is contained in:
Adrian Hopek 2022-01-18 07:39:03 +01:00
parent 7dca5d1e6c
commit acf2eba95d
17 changed files with 644 additions and 32 deletions

View File

@ -15,4 +15,14 @@ enum FormOfEmployment: string
{
return __($this->value);
}
public static function casesToSelect(): array
{
$cases = collect(FormOfEmployment::cases());
return $cases->map(fn(FormOfEmployment $enum) => [
"label" => $enum->label(),
"value" => $enum->value]
)->toArray();
}
}

View File

@ -4,8 +4,12 @@ declare(strict_types=1);
namespace Toby\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Response;
use Toby\Enums\FormOfEmployment;
use Toby\Http\Requests\UserRequest;
use Toby\Http\Resources\UserFormDataResource;
use Toby\Http\Resources\UserResource;
use Toby\Models\User;
@ -14,6 +18,7 @@ class UserController extends Controller
public function index(Request $request): Response
{
$users = User::query()
->withTrashed()
->search($request->query("search"))
->paginate()
->withQueryString();
@ -23,4 +28,55 @@ class UserController extends Controller
"filters" => $request->only("search"),
]);
}
public function create(): Response
{
return inertia("Users/Create", [
"employmentForms" => FormOfEmployment::casesToSelect(),
]);
}
public function store(UserRequest $request): RedirectResponse
{
User::query()->create($request->data());
return redirect()
->route("users.index")
->with("success", __("User has been created"));
}
public function edit(User $user): Response
{
return inertia("Users/Edit", [
"user" => new UserFormDataResource($user),
"employmentForms" => FormOfEmployment::casesToSelect()
]);
}
public function update(UserRequest $request, User $user): RedirectResponse
{
$user->update($request->data());
return redirect()
->route("users.index")
->with("success", __("User has been updated"));
}
public function destroy(User $user): RedirectResponse
{
$user->delete();
return redirect()
->route("users.index")
->with("success", __("User has been deleted"));
}
public function restore(User $user): RedirectResponse
{
$user->restore();
return redirect()
->route("users.index")
->with("success", __("User has been restored"));
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Toby\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Toby\Enums\FormOfEmployment;
class UserRequest extends FormRequest
{
public function rules(): array
{
return [
"name" => ["required", "min:3", "max: 150"],
"email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)],
"employmentForm" => ["required", new Enum(FormOfEmployment::class)],
"employmentDate" => ["required", "date"],
];
}
public function data(): array
{
return [
"name" => $this->get("name"),
"email" => $this->get("email"),
"employment_form" => $this->get("employmentForm"),
"employment_date" => $this->get("employmentDate"),
];
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Toby\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserFormDataResource extends JsonResource
{
public static $wrap = false;
public function toArray($request): array
{
return [
"id" => $this->id,
"name" => $this->name,
"email" => $this->email,
"employmentForm" => $this->employment_form,
"employmentDate" => $this->employment_date,
];
}
}

View File

@ -16,8 +16,9 @@ class UserResource extends JsonResource
"email" => $this->email,
"role" => "Human Resources Manager",
"avatar" => asset($this->avatar),
"trashed" => $this->trashed(),
"employmentForm" => $this->employment_form->label(),
"employmentStartDate" => $this->employment_start_date->translatedFormat("j F Y"),
"employmentDate" => $this->employment_date->translatedFormat("j F Y"),
];
}
}

View File

@ -6,6 +6,7 @@ namespace Toby\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
@ -17,12 +18,13 @@ use Toby\Enums\FormOfEmployment;
* @property string $email
* @property string $avatar
* @property FormOfEmployment $employment_form
* @property Carbon $empoyment_start_date
* @property Carbon $empoyment_date
*/
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
use SoftDeletes;
protected $perPage = 10;
@ -31,12 +33,12 @@ class User extends Authenticatable
"email",
"avatar",
"employment_form",
"employment_start_date",
"employment_date",
];
protected $casts = [
"employment_form" => FormOfEmployment::class,
"employment_start_date" => "datetime",
"employment_date" => "datetime",
];
protected $hidden = [

View File

@ -29,7 +29,7 @@ class UserObserver
}
}
public function deleted(User $user): void
public function forceDeleted(User $user): void
{
Storage::delete($user->avatar);
}

View File

@ -16,7 +16,7 @@ class UserFactory extends Factory
"name" => "{$this->faker->firstName} {$this->faker->lastName}",
"email" => $this->faker->unique()->safeEmail(),
"employment_form" => $this->faker->randomElement(FormOfEmployment::cases()),
"employment_start_date" => $this->faker->dateTimeBetween("2020-10-27"),
"employment_date" => $this->faker->dateTimeBetween("2020-10-27"),
"remember_token" => Str::random(10),
];
}

View File

@ -15,8 +15,9 @@ return new class() extends Migration {
$table->string("email")->unique();
$table->string("avatar")->nullable();
$table->string("employment_form");
$table->dateTime("employment_start_date");
$table->dateTime("employment_date");
$table->rememberToken();
$table->softDeletes();
$table->timestamps();
});
}

34
package-lock.json generated
View File

@ -15,11 +15,13 @@
"@tailwindcss/typography": "^0.5.0",
"@vue/compiler-sfc": "^3.2.26",
"autoprefixer": "^10.4.2",
"flatpickr": "^4.6.9",
"laravel-mix": "^6.0.6",
"lodash": "^4.17.21",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.13",
"vue": "^3.2.26",
"vue-flatpickr-component": "^9.0.5",
"vue-loader": "^17.0.0"
},
"devDependencies": {
@ -4723,6 +4725,11 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"node_modules/flatted": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
@ -8893,6 +8900,20 @@
"node": ">=4.0"
}
},
"node_modules/vue-flatpickr-component": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/vue-flatpickr-component/-/vue-flatpickr-component-9.0.5.tgz",
"integrity": "sha512-fKuz/D4ePQKi+jPo4xjYRgBCLTWrTsCoKbx8nam63x4kTDtSqvFOjNwLvy0QgwC0lC+aFpUWa1dNYTH0hgUcCA==",
"dependencies": {
"flatpickr": "^4.6.9"
},
"engines": {
"node": ">=10.13.0"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-loader": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",
@ -12983,6 +13004,11 @@
"rimraf": "^3.0.2"
}
},
"flatpickr": {
"version": "4.6.9",
"resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz",
"integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw=="
},
"flatted": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz",
@ -15971,6 +15997,14 @@
}
}
},
"vue-flatpickr-component": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/vue-flatpickr-component/-/vue-flatpickr-component-9.0.5.tgz",
"integrity": "sha512-fKuz/D4ePQKi+jPo4xjYRgBCLTWrTsCoKbx8nam63x4kTDtSqvFOjNwLvy0QgwC0lC+aFpUWa1dNYTH0hgUcCA==",
"requires": {
"flatpickr": "^4.6.9"
}
},
"vue-loader": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",

View File

@ -22,11 +22,13 @@
"@tailwindcss/typography": "^0.5.0",
"@vue/compiler-sfc": "^3.2.26",
"autoprefixer": "^10.4.2",
"flatpickr": "^4.6.9",
"laravel-mix": "^6.0.6",
"lodash": "^4.17.21",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.13",
"vue": "^3.2.26",
"vue-flatpickr-component": "^9.0.5",
"vue-loader": "^17.0.0"
},
"devDependencies": {

View File

@ -1,3 +1,49 @@
@import 'flatpickr/dist/themes/light.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
fill: #4F46E5;
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: #527ABA;
-webkit-box-shadow: none;
box-shadow: none;
color: #fff;
border-color: #527ABA;
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 #527ABA;
box-shadow: -10px 0 0 #527ABA;
}
.flatpickr-day.week.selected {
border-radius: 0;
-webkit-box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
}

View File

@ -0,0 +1,162 @@
<template>
<InertiaHead title="Dodawanie użytkownika" />
<div class="bg-white sm:rounded-lg shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Dodaj użytkownika
</h2>
<p class="mt-1 text-sm text-gray-500">
Tylko dodani użytkownicy będą mogli zalogować się do aplikacji.
</p>
</div>
<form
class="border-t border-gray-200 px-6 divide-y"
@submit.prevent="form.post('/users')"
>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="name"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Imię i nazwisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="name"
v-model="form.name"
type="text"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }"
>
<p
v-if="form.errors.name"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.name }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="email"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Adres email
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="email"
v-model="form.email"
type="email"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }"
>
<p
v-if="form.errors.email"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.email }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="employment_form"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Forma zatrudnienia
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<select
id="employment_form"
v-model="form.employmentForm"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }"
>
<option
v-for="employmentForm in employmentForms"
:key="employmentForm.value"
:value="employmentForm.value"
>
{{ employmentForm.label }}
</option>
</select>
<p
v-if="form.errors.employmentForm"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.employmentForm }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="employment_date"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Data zatrudnienia
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<FlatPickr
id="employment_date"
v-model="form.employmentDate"
placeholder="Wybierz datę"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }"
/>
<p
v-if="form.errors.employmentDate"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.employmentDate }}
</p>
</div>
</div>
<div class="flex justify-end py-3">
<div class="space-x-3">
<InertiaLink
href="/users"
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
>
Anuluj
</InertiaLink>
<button
type="submit"
:disabled="form.processing"
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>
</div>
</form>
</div>
</template>
<script>
import {useForm} from '@inertiajs/inertia-vue3';
import FlatPickr from 'vue-flatpickr-component';
export default {
employmentDate: 'UserCreate',
components: {
FlatPickr,
},
props: {
employmentForms: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
name: null,
email: null,
employmentForm: props.employmentForms[0].value,
employmentDate: new Date(),
});
return { form };
}
};
</script>

View File

@ -0,0 +1,166 @@
<template>
<InertiaHead title="Edycja użytkownika" />
<div class="bg-white sm:rounded-lg shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Edytuj użytkownika
</h2>
<p class="mt-1 text-sm text-gray-500">
Edytuj dane użytkownika, takie jak email czy formę zatrudnienia.
</p>
</div>
<form
class="border-t border-gray-200 px-6 divide-y"
@submit.prevent="form.put(`/users/${user.id}`)"
>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="name"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Imię i nazwisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="name"
v-model="form.name"
type="text"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.name, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.name }"
>
<p
v-if="form.errors.name"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.name }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="email"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Adres email
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="email"
v-model="form.email"
type="email"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.email, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.email }"
>
<p
v-if="form.errors.email"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.email }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="employment_form"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Forma zatrudnienia
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<select
id="employment_form"
v-model="form.employmentForm"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }"
>
<option
v-for="employmentForm in employmentForms"
:key="employmentForm.value"
:value="employmentForm.value"
>
{{ employmentForm.label }}
</option>
</select>
<p
v-if="form.errors.employmentForm"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.employmentForm }}
</p>
</div>
</div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="employment_date"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Data zatrudnienia
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<FlatPickr
id="employment_date"
v-model="form.employmentDate"
placeholder="Wybierz datę"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.employmentDate, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentDate }"
/>
<p
v-if="form.errors.employmentDate"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.employmentDate }}
</p>
</div>
</div>
<div class="flex justify-end py-3">
<div class="space-x-3">
<InertiaLink
href="/users"
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
>
Anuluj
</InertiaLink>
<button
type="submit"
:disabled="form.processing"
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>
</div>
</form>
</div>
</template>
<script>
import {useForm} from '@inertiajs/inertia-vue3';
import FlatPickr from 'vue-flatpickr-component';
export default {
employmentDate: 'UserEdit',
components: {
FlatPickr,
},
props: {
employmentForms: {
type: Object,
default: () => null,
},
user: {
type: Object,
default: () => null,
},
},
setup(props) {
const form = useForm({
name: props.user.name,
email: props.user.email,
employmentForm: props.user.employmentForm,
employmentDate: new Date(props.user.employmentDate),
});
return { form };
}
};
</script>

View File

@ -1,13 +1,23 @@
<template>
<InertiaHead title="Użytkownicy" />
<div class="bg-white sm:rounded-lg shadow-md">
<div class="p-4 sm:px-6">
<h2 class="text-lg leading-6 font-medium text-gray-900">
Użytkownicy w organizacji
</h2>
<p class="mt-1 text-sm text-gray-500">
Lista użytkowników w organizacji.
</p>
<div class="flex justify-between items-center p-4 sm:px-6">
<div>
<h2 class="text-lg leading-6 font-medium text-gray-900">
Użytkownicy w organizacji
</h2>
<p class="mt-1 text-sm text-gray-500">
Lista użytkowników w organizacji.
</p>
</div>
<div>
<InertiaLink
href="users/create"
class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
>
Dodaj użytkownika
</InertiaLink>
</div>
</div>
<div class="border-t border-gray-200">
<div class="px-4 py-3">
@ -61,7 +71,7 @@
<tr
v-for="user in users.data"
:key="user.id"
class="hover:bg-white"
:class="{ 'bg-red-50': user.trashed, 'hover:bg-blumilk-25': !user.trashed }"
>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
<div class="flex">
@ -91,25 +101,72 @@
{{ user.employmentForm }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.employmentStartDate }}
{{ user.employmentDate }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500 text-right">
<div>
<button
class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500"
>
<svg
<Menu
as="div"
class="relative inline-block text-left"
>
<MenuButton class="rounded-full flex items-center text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blumilk-500">
<DotsVerticalIcon
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</button>
</div>
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="origin-top-right absolute right-0 mt-2 w-56 z-10 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<div
v-if="!user.trashed"
class="py-1"
>
<MenuItem v-slot="{ active }">
<InertiaLink
:href="`/users/${user.id}/edit`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block px-4 py-2 text-sm']"
>
Edytuj
</InertiaLink>
</MenuItem>
<MenuItem v-slot="{ active }">
<InertiaLink
as="button"
method="delete"
:preserve-scroll="true"
:href="`/users/${user.id}`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left px-4 py-2 text-sm']"
>
Usuń
</InertiaLink>
</MenuItem>
</div>
<div
v-else
class="py-1"
>
<MenuItem v-slot="{ active }">
<InertiaLink
as="button"
method="post"
:preserve-scroll="true"
:href="`/users/${user.id}/restore`"
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'block w-full text-left px-4 py-2 text-sm']"
>
Przywróć
</InertiaLink>
</MenuItem>
</div>
</MenuItems>
</transition>
</Menu>
</td>
</tr>
<tr
@ -162,6 +219,7 @@
<Component
:is="link.url ? 'InertiaLink' : 'span'"
:href="link.url"
:preserve-scroll="true"
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
v-text="link.label"
@ -180,11 +238,18 @@ import { ref, watch } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { debounce } from 'lodash';
import { SearchIcon } from '@heroicons/vue/outline';
import {DotsVerticalIcon} from '@heroicons/vue/solid';
import {Menu, MenuButton, MenuItem, MenuItems} from '@headlessui/vue';
export default {
name: 'UserIndex',
components: {
SearchIcon,
DotsVerticalIcon,
Menu,
MenuButton,
MenuItem,
MenuItems,
},
props: {
users: {

View File

@ -2,6 +2,8 @@ import {createApp, h} from 'vue';
import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3';
import {InertiaProgress} from '@inertiajs/progress';
import AppLayout from '@/Shared/Layout/AppLayout';
import Flatpickr from 'flatpickr';
import { Polish } from 'flatpickr/dist/l10n/pl.js';
createInertiaApp({
resolve: name => {
@ -25,3 +27,12 @@ InertiaProgress.init({
delay: 0,
color: 'red',
});
Flatpickr.localize(Polish);
Flatpickr.setDefaults({
dateFormat: 'Y-m-d',
enableTime: false,
altFormat: 'j F Y',
altInput: true
});

View File

@ -12,7 +12,7 @@ Route::middleware("auth")->group(function (): void {
Route::post("/logout", LogoutController::class);
Route::resource("users", UserController::class);
Route::post("users/{user}/restore", [UserController::class, "restore"])->withTrashed();
});
Route::middleware("guest")->group(function (): void {