feat: implement role assignment functionality for user creation and editing via combobox component
This commit is contained in:
parent
665394d511
commit
2ea62fa033
@ -12,6 +12,7 @@
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
@ -19,13 +20,16 @@ public function index(): Response
|
||||
{
|
||||
return Inertia::render('admin/master/user/index', [
|
||||
'users' => User::with(['profile', 'roles'])->latest()->get(),
|
||||
'roles' => Role::all(),
|
||||
'defaultPassword' => config('auth.password_default', 'password'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('admin/master/user/create');
|
||||
return Inertia::render('admin/master/user/create', [
|
||||
'roles' => Role::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(UserRequest $request): RedirectResponse
|
||||
@ -49,6 +53,10 @@ public function store(UserRequest $request): RedirectResponse
|
||||
'birth_date' => $validated['birth_date'],
|
||||
'base_salary' => $validated['base_salary'],
|
||||
]);
|
||||
|
||||
if (! empty($validated['roles'])) {
|
||||
$user->assignRole($validated['roles']);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('user.index')->with('success', 'Data berhasil disimpan');
|
||||
@ -67,10 +75,11 @@ public function store(UserRequest $request): RedirectResponse
|
||||
|
||||
public function edit(User $user): Response
|
||||
{
|
||||
$user->load('profile');
|
||||
$user->load(['profile', 'roles']);
|
||||
|
||||
return Inertia::render('admin/master/user/edit', [
|
||||
'user' => $user,
|
||||
'roles' => Role::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -94,6 +103,10 @@ public function update(UserRequest $request, User $user): RedirectResponse
|
||||
'birth_date' => $validated['birth_date'],
|
||||
'base_salary' => $validated['base_salary'],
|
||||
]);
|
||||
|
||||
if (isset($validated['roles'])) {
|
||||
$user->syncRoles($validated['roles']);
|
||||
}
|
||||
});
|
||||
|
||||
return redirect()->route('user.index')->with('success', 'Data berhasil diperbarui');
|
||||
|
||||
@ -37,6 +37,7 @@ public function rules(): array
|
||||
'birth_place' => ['required', 'string', 'max:100'],
|
||||
'birth_date' => ['required', 'date'],
|
||||
'base_salary' => ['required', 'integer', 'min:0'],
|
||||
'roles' => ['nullable', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,27 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Field, FieldError } from "@/components/ui/field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Field, FieldError } from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
} from '@/components/ui/popover';
|
||||
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxValue,
|
||||
useComboboxAnchor,
|
||||
} from '@/components/ui/combobox';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import userRoutes from '@/routes/user';
|
||||
import { Head, Link, useForm } from '@inertiajs/react';
|
||||
@ -17,7 +30,7 @@ import React from 'react';
|
||||
import { NumericFormat } from 'react-number-format';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function UserCreate() {
|
||||
export default function UserCreate({ roles }: { roles: any[] }) {
|
||||
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
|
||||
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
@ -30,6 +43,7 @@ export default function UserCreate() {
|
||||
birth_place: '',
|
||||
birth_date: '',
|
||||
base_salary: 0,
|
||||
roles: [] as string[],
|
||||
});
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
@ -41,16 +55,20 @@ export default function UserCreate() {
|
||||
});
|
||||
};
|
||||
|
||||
const anchor = useComboboxAnchor();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
<Head title="Tambah Pegawai" />
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Tambah Pegawai</h1>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
Tambah Pegawai
|
||||
</h1>
|
||||
</div>
|
||||
<Link href={userRoutes.index().url}>
|
||||
<Button variant='outline' className="gap-2">
|
||||
<Button variant="outline" className="gap-2">
|
||||
<ArrowLeft className="size-4" />
|
||||
Kembali
|
||||
</Button>
|
||||
@ -58,56 +76,88 @@ export default function UserCreate() {
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div className="space-y-6 lg:col-span-2">
|
||||
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
||||
<CardHeader className="border-b">
|
||||
<CardTitle>Informasi Profil</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<Field>
|
||||
<Label htmlFor="full_name" required>Nama Lengkap</Label>
|
||||
<Label htmlFor="full_name" required>
|
||||
Nama Lengkap
|
||||
</Label>
|
||||
<Input
|
||||
id="full_name"
|
||||
name="full_name"
|
||||
value={data.full_name}
|
||||
onChange={e => setData('full_name', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: John Doe'
|
||||
onChange={(e) =>
|
||||
setData(
|
||||
'full_name',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: John Doe"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.full_name}
|
||||
label="Nama Lengkap"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.full_name} label="Nama Lengkap" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="nik" required>NIK</Label>
|
||||
<Label htmlFor="nik" required>
|
||||
NIK
|
||||
</Label>
|
||||
<Input
|
||||
id="nik"
|
||||
name="nik"
|
||||
value={data.nik}
|
||||
onChange={e => setData('nik', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: 3213051307900001'
|
||||
onChange={(e) =>
|
||||
setData('nik', e.target.value)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: 3213051307900001"
|
||||
maxLength={16}
|
||||
/>
|
||||
<FieldError error={errors.nik} label="NIK" className="text-xs" />
|
||||
<FieldError
|
||||
error={errors.nik}
|
||||
label="NIK"
|
||||
className="text-xs"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="phone_number" required>Nomor Telepon</Label>
|
||||
<Label htmlFor="phone_number" required>
|
||||
Nomor Telepon
|
||||
</Label>
|
||||
<Input
|
||||
id="phone_number"
|
||||
name="phone_number"
|
||||
value={data.phone_number}
|
||||
onChange={e => setData('phone_number', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: 08123456789'
|
||||
onChange={(e) =>
|
||||
setData(
|
||||
'phone_number',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: 08123456789"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.phone_number}
|
||||
label="Nomor Telepon"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.phone_number} label="Nomor Telepon" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="base_salary" required>Gaji Pokok</Label>
|
||||
<Label htmlFor="base_salary" required>
|
||||
Gaji Pokok
|
||||
</Label>
|
||||
<NumericFormat
|
||||
id="base_salary"
|
||||
customInput={Input}
|
||||
@ -116,30 +166,53 @@ export default function UserCreate() {
|
||||
prefix="Rp "
|
||||
value={data.base_salary}
|
||||
onValueChange={(values) => {
|
||||
setData('base_salary', values.floatValue || 0)
|
||||
setData(
|
||||
'base_salary',
|
||||
values.floatValue || 0,
|
||||
);
|
||||
}}
|
||||
placeholder="Rp 0"
|
||||
autoComplete='off'
|
||||
autoComplete="off"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.base_salary}
|
||||
label="Gaji Pokok"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.base_salary} label="Gaji Pokok" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="birth_place" required>Tempat Lahir</Label>
|
||||
<Label htmlFor="birth_place" required>
|
||||
Tempat Lahir
|
||||
</Label>
|
||||
<Input
|
||||
id="birth_place"
|
||||
name="birth_place"
|
||||
value={data.birth_place}
|
||||
onChange={e => setData('birth_place', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: Jakarta'
|
||||
onChange={(e) =>
|
||||
setData(
|
||||
'birth_place',
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: Jakarta"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.birth_place}
|
||||
label="Tempat Lahir"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.birth_place} label="Tempat Lahir" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="birth_date" required>Tanggal Lahir</Label>
|
||||
<Popover open={isCalendarOpen} onOpenChange={setIsCalendarOpen}>
|
||||
<Label htmlFor="birth_date" required>
|
||||
Tanggal Lahir
|
||||
</Label>
|
||||
<Popover
|
||||
open={isCalendarOpen}
|
||||
onOpenChange={setIsCalendarOpen}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -147,78 +220,223 @@ export default function UserCreate() {
|
||||
className="w-full justify-start font-normal"
|
||||
>
|
||||
{data.birth_date ? (
|
||||
new Intl.DateTimeFormat("id-ID", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}).format(new Date(data.birth_date))
|
||||
new Intl.DateTimeFormat(
|
||||
'id-ID',
|
||||
{
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
},
|
||||
).format(
|
||||
new Date(
|
||||
data.birth_date,
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<span className="text-muted-foreground">Pilih Tanggal</span>
|
||||
<span className="text-muted-foreground">
|
||||
Pilih Tanggal
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||
<PopoverContent
|
||||
className="w-auto overflow-hidden p-0"
|
||||
align="start"
|
||||
>
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={data.birth_date ? new Date(data.birth_date) : undefined}
|
||||
defaultMonth={data.birth_date ? new Date(data.birth_date) : new Date(2000, 0, 1)}
|
||||
selected={
|
||||
data.birth_date
|
||||
? new Date(
|
||||
data.birth_date,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
defaultMonth={
|
||||
data.birth_date
|
||||
? new Date(
|
||||
data.birth_date,
|
||||
)
|
||||
: new Date(
|
||||
2000,
|
||||
0,
|
||||
1,
|
||||
)
|
||||
}
|
||||
captionLayout="dropdown"
|
||||
onSelect={(selectedDate: Date | undefined) => {
|
||||
onSelect={(
|
||||
selectedDate:
|
||||
| Date
|
||||
| undefined,
|
||||
) => {
|
||||
if (selectedDate) {
|
||||
setData('birth_date', selectedDate.getFullYear() + "-" + String(selectedDate.getMonth() + 1).padStart(2, '0') + "-" + String(selectedDate.getDate()).padStart(2, '0'));
|
||||
setData(
|
||||
'birth_date',
|
||||
selectedDate.getFullYear() +
|
||||
'-' +
|
||||
String(
|
||||
selectedDate.getMonth() +
|
||||
1,
|
||||
).padStart(
|
||||
2,
|
||||
'0',
|
||||
) +
|
||||
'-' +
|
||||
String(
|
||||
selectedDate.getDate(),
|
||||
).padStart(
|
||||
2,
|
||||
'0',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setData('birth_date', '');
|
||||
setData(
|
||||
'birth_date',
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
setIsCalendarOpen(false);
|
||||
setIsCalendarOpen(
|
||||
false,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<FieldError error={errors.birth_date} label="Tanggal Lahir" className="text-xs" />
|
||||
<FieldError
|
||||
error={errors.birth_date}
|
||||
label="Tanggal Lahir"
|
||||
className="text-xs"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="address" required>Alamat</Label>
|
||||
<Textarea id='address' name='address' value={data.address} onChange={e => setData('address', e.target.value)} placeholder='Contoh: Kp. Bakan Sampeu' />
|
||||
<FieldError error={errors.address} label="Alamat" className="text-xs" />
|
||||
<Label htmlFor="address" required>
|
||||
Alamat
|
||||
</Label>
|
||||
<Textarea
|
||||
id="address"
|
||||
name="address"
|
||||
value={data.address}
|
||||
onChange={(e) =>
|
||||
setData('address', e.target.value)
|
||||
}
|
||||
placeholder="Contoh: Kp. Bakan Sampeu"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.address}
|
||||
label="Alamat"
|
||||
className="text-xs"
|
||||
/>
|
||||
</Field>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
|
||||
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
||||
<CardHeader className="border-b">
|
||||
<CardTitle>Kredensial Akun</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<Field>
|
||||
<Label htmlFor="email" required>Alamat Surel</Label>
|
||||
<Label htmlFor="email" required>
|
||||
Alamat Surel
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
value={data.email}
|
||||
onChange={e => setData('email', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: john@example.com'
|
||||
onChange={(e) =>
|
||||
setData('email', e.target.value)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: john@example.com"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.email}
|
||||
label="Alamat Surel"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.email} label="Alamat Surel" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="username" required>Nama Pengguna</Label>
|
||||
<Label htmlFor="username" required>
|
||||
Nama Pengguna
|
||||
</Label>
|
||||
<Input
|
||||
id="username"
|
||||
name="username"
|
||||
value={data.username}
|
||||
onChange={e => setData('username', e.target.value)}
|
||||
autoComplete='off'
|
||||
placeholder='Contoh: johndoe'
|
||||
onChange={(e) =>
|
||||
setData('username', e.target.value)
|
||||
}
|
||||
autoComplete="off"
|
||||
placeholder="Contoh: johndoe"
|
||||
/>
|
||||
<FieldError
|
||||
error={errors.username}
|
||||
label="Nama Pengguna"
|
||||
className="text-xs"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="roles">Peran</Label>
|
||||
<Combobox
|
||||
multiple
|
||||
autoHighlight
|
||||
items={roles.map((r) => r.name)}
|
||||
value={data.roles}
|
||||
onValueChange={(val) =>
|
||||
setData('roles', val)
|
||||
}
|
||||
>
|
||||
<ComboboxChips
|
||||
ref={anchor}
|
||||
className="w-full"
|
||||
>
|
||||
<ComboboxValue>
|
||||
{(values) => (
|
||||
<React.Fragment>
|
||||
{values.map(
|
||||
(value: string) => (
|
||||
<ComboboxChip
|
||||
key={value}
|
||||
value={value}
|
||||
>
|
||||
{value}
|
||||
</ComboboxChip>
|
||||
),
|
||||
)}
|
||||
<ComboboxChipsInput placeholder="Pilih Peran" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ComboboxValue>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent anchor={anchor}>
|
||||
<ComboboxEmpty>
|
||||
Peran tidak ditemukan
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item: string) => (
|
||||
<ComboboxItem
|
||||
key={item}
|
||||
value={item}
|
||||
>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldError
|
||||
error={errors.roles}
|
||||
label="Peran"
|
||||
className="text-xs"
|
||||
/>
|
||||
<FieldError error={errors.username} label="Nama Pengguna" className="text-xs" />
|
||||
</Field>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@ -226,11 +444,14 @@ export default function UserCreate() {
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={processing}>
|
||||
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
|
||||
{processing ? (
|
||||
<Loader className="size-4 animate-spin" />
|
||||
) : (
|
||||
<Save className="size-4" />
|
||||
)}
|
||||
{processing ? 'Menyimpan...' : 'Simpan'}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -16,9 +16,22 @@ import { ArrowLeft, Loader, Save } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { NumericFormat } from 'react-number-format';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxValue,
|
||||
useComboboxAnchor,
|
||||
} from '@/components/ui/combobox';
|
||||
|
||||
export default function UserEdit({ user }: { user: any }) {
|
||||
export default function UserEdit({ user, roles }: { user: any, roles: any[] }) {
|
||||
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
|
||||
const anchor = useComboboxAnchor();
|
||||
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
username: user.username || '',
|
||||
@ -30,6 +43,7 @@ export default function UserEdit({ user }: { user: any }) {
|
||||
birth_place: user.profile?.birth_place || '',
|
||||
birth_date: user.profile?.birth_date || '',
|
||||
base_salary: user.profile?.base_salary || 0,
|
||||
roles: user.roles?.map((role: any) => role.name) || [] as string[],
|
||||
_method: 'PATCH',
|
||||
});
|
||||
|
||||
@ -221,6 +235,62 @@ export default function UserEdit({ user }: { user: any }) {
|
||||
/>
|
||||
<FieldError error={errors.username} label="Nama Pengguna" className="text-xs" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Label htmlFor="roles">Peran</Label>
|
||||
<Combobox
|
||||
multiple
|
||||
autoHighlight
|
||||
items={roles.map((r) => r.name)}
|
||||
value={data.roles}
|
||||
onValueChange={(val) =>
|
||||
setData('roles', val)
|
||||
}
|
||||
>
|
||||
<ComboboxChips
|
||||
ref={anchor}
|
||||
className="w-full"
|
||||
>
|
||||
<ComboboxValue>
|
||||
{(values) => (
|
||||
<React.Fragment>
|
||||
{values.map(
|
||||
(value: string) => (
|
||||
<ComboboxChip
|
||||
key={value}
|
||||
value={value}
|
||||
>
|
||||
{value}
|
||||
</ComboboxChip>
|
||||
),
|
||||
)}
|
||||
<ComboboxChipsInput placeholder="Pilih Peran" />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ComboboxValue>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent anchor={anchor}>
|
||||
<ComboboxEmpty>
|
||||
Peran tidak ditemukan
|
||||
</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{(item: string) => (
|
||||
<ComboboxItem
|
||||
key={item}
|
||||
value={item}
|
||||
>
|
||||
{item}
|
||||
</ComboboxItem>
|
||||
)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<FieldError
|
||||
error={errors.roles}
|
||||
label="Peran"
|
||||
className="text-xs"
|
||||
/>
|
||||
</Field>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
use function Pest\Laravel\assertDatabaseHas;
|
||||
@ -49,6 +50,7 @@
|
||||
->assertInertia(fn ($page) => $page
|
||||
->component('admin/master/user/index')
|
||||
->has('users')
|
||||
->has('roles')
|
||||
->has('defaultPassword')
|
||||
);
|
||||
});
|
||||
@ -58,6 +60,7 @@
|
||||
->assertOk()
|
||||
->assertInertia(fn ($page) => $page
|
||||
->component('admin/master/user/create')
|
||||
->has('roles')
|
||||
);
|
||||
});
|
||||
|
||||
@ -72,8 +75,11 @@
|
||||
'birth_place' => 'Test City',
|
||||
'birth_date' => '1990-01-01',
|
||||
'base_salary' => 5000000,
|
||||
'roles' => ['Admin'],
|
||||
];
|
||||
|
||||
Role::create(['name' => 'Admin']);
|
||||
|
||||
postJson(route('user.store'), $data)
|
||||
->assertRedirect(route('user.index'))
|
||||
->assertSessionHas('success');
|
||||
@ -87,6 +93,9 @@
|
||||
'nik' => '1234567890123456',
|
||||
'full_name' => 'New User Full Name',
|
||||
]);
|
||||
|
||||
$newUser = User::where('username', 'newuser')->first();
|
||||
expect($newUser->hasRole('Admin'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('validates user creation', function () {
|
||||
@ -103,6 +112,7 @@
|
||||
->assertInertia(fn ($page) => $page
|
||||
->component('admin/master/user/edit')
|
||||
->has('user')
|
||||
->has('roles')
|
||||
);
|
||||
});
|
||||
|
||||
@ -119,8 +129,11 @@
|
||||
'birth_place' => 'Updated City',
|
||||
'birth_date' => '1995-05-05',
|
||||
'base_salary' => 6000000,
|
||||
'roles' => ['Staff'],
|
||||
];
|
||||
|
||||
Role::create(['name' => 'Staff']);
|
||||
|
||||
patchJson(route('user.update', $user), $newData)
|
||||
->assertRedirect(route('user.index'))
|
||||
->assertSessionHas('success');
|
||||
@ -134,6 +147,8 @@
|
||||
'user_id' => $user->id,
|
||||
'full_name' => 'Updated Name',
|
||||
]);
|
||||
|
||||
expect($user->fresh()->hasRole('Staff'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('can reset user password', function () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user