Files
web-probindo/resources/js/pages/admin/users/index.tsx
T
afriadirahman 8c6dad2a31
tests / ci (push) Canceled after 0s
Admin Panel
2026-09-29 14:57:37 +07:00

267 lines
12 KiB
TypeScript

import { Head, usePage } from '@inertiajs/react';
import { Pencil, Plus, Trash2 } from 'lucide-react';
import type { ReactNode } from 'react';
import {
ConfirmDelete,
FormDialog,
FormField,
NativeSelect,
PageHeader,
} from '@/components/admin/form-controls';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { formatDate, optionsOf, roleLabels } from '@/lib/admin-labels';
import { destroy, index, store, update } from '@/routes/admin/users';
import type { UserRole } from '@/types';
type PanelUser = {
id: number;
name: string;
email: string;
role: UserRole;
created_at: string;
};
const roleDescriptions: Record<UserRole, string> = {
admin: 'Semua menu, termasuk kelola pengguna.',
editor: 'Konten website dan inbox konsultasi.',
};
export default function UsersIndex({ users }: { users: PanelUser[] }) {
const { auth } = usePage().props;
return (
<>
<Head title="Pengguna" />
<div className="flex flex-col gap-6 p-4 md:p-6">
<PageHeader
title="Pengguna"
description="Akun yang dapat masuk ke admin panel. Registrasi publik dinonaktifkan."
action={
<UserDialog
trigger={
<Button>
<Plus /> Tambah Pengguna
</Button>
}
/>
}
/>
<div className="bg-card overflow-x-auto rounded-xl border">
<table className="w-full text-sm">
<thead className="text-muted-foreground border-b text-left text-xs">
<tr>
<th className="px-4 py-3 font-medium">Nama</th>
<th className="px-4 py-3 font-medium">Peran</th>
<th className="px-4 py-3 font-medium">
Dibuat
</th>
<th className="px-4 py-3" />
</tr>
</thead>
<tbody className="divide-border divide-y">
{users.map((user) => {
const isSelf = user.id === auth.user?.id;
return (
<tr key={user.id}>
<td className="px-4 py-3">
<div className="flex flex-col">
<span className="font-semibold">
{user.name}
{isSelf && (
<span className="text-muted-foreground font-normal">
{' '}
(Anda)
</span>
)}
</span>
<span className="text-muted-foreground text-xs">
{user.email}
</span>
</div>
</td>
<td className="px-4 py-3">
<Badge
variant={
user.role === 'admin'
? 'default'
: 'secondary'
}
>
{roleLabels[user.role]}
</Badge>
</td>
<td className="text-muted-foreground px-4 py-3 text-xs">
{formatDate(user.created_at)}
</td>
<td className="px-4 py-3">
<div className="flex justify-end gap-1">
<UserDialog
user={user}
isSelf={isSelf}
trigger={
<Button
variant="ghost"
size="icon"
aria-label="Ubah"
>
<Pencil />
</Button>
}
/>
{!isSelf && (
<ConfirmDelete
title="Hapus pengguna ini?"
description={`${user.name} tidak akan bisa masuk ke admin panel lagi.`}
form={destroy.form(
user.id,
)}
trigger={
<Button
variant="ghost"
size="icon"
aria-label="Hapus"
className="text-destructive"
>
<Trash2 />
</Button>
}
/>
)}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{optionsOf(roleLabels).map(({ value, label }) => (
<div
key={value}
className="text-muted-foreground rounded-lg border border-dashed p-4 text-sm"
>
<span className="text-foreground font-semibold">
{label}:
</span>{' '}
{roleDescriptions[value]}
</div>
))}
</div>
</div>
</>
);
}
function UserDialog({
user,
isSelf = false,
trigger,
}: {
user?: PanelUser;
isSelf?: boolean;
trigger: ReactNode;
}) {
const id = (field: string) => `user-${user?.id ?? 'new'}-${field}`;
return (
<FormDialog
trigger={trigger}
title={user ? 'Ubah Pengguna' : 'Tambah Pengguna'}
form={user ? update.form(user.id) : store.form()}
>
{(errors) => (
<>
<FormField id={id('name')} label="Nama" error={errors.name}>
<Input
id={id('name')}
name="name"
defaultValue={user?.name}
required
/>
</FormField>
<FormField
id={id('email')}
label="Email"
error={errors.email}
>
<Input
id={id('email')}
name="email"
type="email"
defaultValue={user?.email}
required
/>
</FormField>
<FormField
id={id('role')}
label="Peran"
error={errors.role}
>
<NativeSelect
id={id('role')}
name="role"
defaultValue={user?.role ?? 'editor'}
options={optionsOf(roleLabels)}
disabled={isSelf}
/>
{isSelf && (
<>
<input
type="hidden"
name="role"
value={user?.role}
/>
<p className="text-muted-foreground text-xs">
Anda tidak dapat mengubah peran akun
sendiri.
</p>
</>
)}
</FormField>
<div className="grid gap-4 sm:grid-cols-2">
<FormField
id={id('password')}
label={user ? 'Password baru' : 'Password'}
error={errors.password}
>
<Input
id={id('password')}
name="password"
type="password"
autoComplete="new-password"
placeholder={
user ? 'Kosongkan jika tidak diubah' : ''
}
required={!user}
/>
</FormField>
<FormField
id={id('password_confirmation')}
label="Ulangi password"
error={errors.password_confirmation}
>
<Input
id={id('password_confirmation')}
name="password_confirmation"
type="password"
autoComplete="new-password"
required={!user}
/>
</FormField>
</div>
</>
)}
</FormDialog>
);
}
UsersIndex.layout = {
breadcrumbs: [{ title: 'Pengguna', href: index() }],
};