import { Eye, EyeOff, Plus, Trash2 } from 'lucide-react'; import type { FormEvent } from 'react'; import { useMemo, useState } from 'react'; import { toast } from 'sonner'; import { ConfigError, Field, InfoHint } from '@/components/admin/admin-form-parts'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Spinner } from '@/components/ui/spinner'; import { emptyUserForm, formatDate, isUserActive, roleLabel, userToForm, } from '@/lib/admin'; import { createAdminUser, deleteAdminUser, updateAdminUser, } from '@/lib/rag-client'; import type { AdminRole, AdminUser, UserFilterForm, UserForm, } from '@/types/admin'; export function AdminUserCreateCard({ onCreated, }: { onCreated?: (user: AdminUser) => void; }) { const [formData, setFormData] = useState(emptyUserForm); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(); function setField( key: K, value: UserForm[K], ): void { setFormData((prev) => ({ ...prev, [key]: value })); } async function handleSubmit( event: FormEvent, ): Promise { event.preventDefault(); setIsSubmitting(true); setError(undefined); try { const payload = buildCreatePayload(formData); const created = await createAdminUser(payload); setFormData(emptyUserForm); onCreated?.(created); } catch (e) { setError( e instanceof Error ? e.message : 'Gagal membuat user.', ); } finally { setIsSubmitting(false); } } return ( Tambah User Payload mengikuti schema UpsertUser dari API. { void handleSubmit(e); }} /> ); } export function AdminUserManagement({ users: initialUsers = [], }: { users?: AdminUser[]; }) { const [users, setUsers] = useState(initialUsers); const [isCreating, setIsCreating] = useState(false); const [createFormData, setCreateFormData] = useState(emptyUserForm); const [isSubmittingCreate, setIsSubmittingCreate] = useState(false); const [createError, setCreateError] = useState(); const [editingUser, setEditingUser] = useState(null); const [editFormData, setEditFormData] = useState(emptyUserForm); const [isEditing, setIsEditing] = useState(false); const [editError, setEditError] = useState(); const [filterForm, setFilterForm] = useState({ email: '', name: '', role: 'all', }); const userStats = useMemo(() => { const adminCount = users.filter((user) => user.role === 'admin').length; return { adminCount, total: users.length, }; }, [users]); const filteredUsers = useMemo(() => { return users.filter((user) => { const nameMatch = filterForm.name === '' || (user.name ?? '').toLowerCase().includes(filterForm.name.toLowerCase()); const emailMatch = filterForm.email === '' || (user.email ?? '').toLowerCase().includes(filterForm.email.toLowerCase()); const roleMatch = filterForm.role === 'all' || user.role === filterForm.role; return nameMatch && emailMatch && roleMatch; }); }, [users, filterForm]); function setCreateField( key: K, value: UserForm[K], ): void { setCreateFormData((prev) => ({ ...prev, [key]: value })); } function setEditField( key: K, value: UserForm[K], ): void { setEditFormData((prev) => ({ ...prev, [key]: value })); } function openEditUserDialog(user: AdminUser): void { setEditingUser(user); setEditFormData(userToForm(user)); setEditError(undefined); } async function handleCreateUser( event: FormEvent, ): Promise { event.preventDefault(); setIsSubmittingCreate(true); setCreateError(undefined); try { const payload = buildCreatePayload(createFormData); const created = await createAdminUser(payload); setUsers((prev) => [created, ...prev]); setCreateFormData(emptyUserForm); setIsCreating(false); } catch (e) { setCreateError( e instanceof Error ? e.message : 'Gagal membuat user.', ); } finally { setIsSubmittingCreate(false); } } async function handleUpdateUser( event: FormEvent, ): Promise { event.preventDefault(); if (!editingUser) { return; } setIsEditing(true); setEditError(undefined); try { const payload = buildUpdatePayload(editFormData); const updated = await updateAdminUser(editingUser.id, payload); setUsers((prev) => prev.map((u) => (u.id === updated.id ? updated : u)), ); setEditingUser(null); } catch (e) { setEditError( e instanceof Error ? e.message : 'Gagal memperbarui user.', ); } finally { setIsEditing(false); } } const [deletingUser, setDeletingUser] = useState(null); const [isDeleting, setIsDeleting] = useState(false); async function handleDeleteUser(): Promise { if (!deletingUser) return; setIsDeleting(true); try { await deleteAdminUser(deletingUser.id); window.location.reload(); } catch (e) { toast.error( e instanceof Error ? e.message : 'Gagal menghapus user.', ); setIsDeleting(false); } } return ( <>
User Management Total {userStats.total} user,{' '} {userStats.adminCount} admin.
setFilterForm((prev) => ({ ...prev, name: event.target.value, })) } /> setFilterForm((prev) => ({ ...prev, email: event.target.value, })) } />
{filteredUsers.map((user) => ( ))} {filteredUsers.length === 0 ? ( ) : null}
Nama Role Status Superuser Dibuat Aksi
{user.name ?? '-'}
{user.email ?? '-'}
{roleLabel(user.role)} {isUserActive(user) ? 'Aktif' : 'Nonaktif'} {user.is_superuser ? 'Ya' : 'Tidak'} {formatDate(user.created_at)}
{users.length === 0 ? 'Tidak ada user dari response backend.' : 'Tidak ada user yang cocok dengan filter.'}
Tambah User Payload mengikuti schema UpsertUser dari API. { void handleCreateUser(e); }} /> setEditingUser(open ? editingUser : null)} open={editingUser !== null} > Edit User Perubahan dikirim ke endpoint update user backend. { void handleUpdateUser(e); }} /> { if (!open) setDeletingUser(null); }} > Hapus user? Akun {deletingUser?.email ?? '-'} akan dihapus permanen dan tidak bisa dikembalikan. ); } function buildCreatePayload(form: UserForm): Record { // POST /auth/register — RegisterRequest schema const payload: Record = { role: form.role }; if (form.email) payload.email = form.email; if (form.name) payload.name = form.name; if (form.password) payload.password = form.password; if (form.identity_number) payload.identity_number = form.identity_number; if (form.phone) payload.phone = form.phone; return payload; } function buildUpdatePayload(form: UserForm): Record { // PATCH /user/{id} — PatchUser schema (additionalProperties: false) // Only: email, name, phone, role, is_active, password const payload: Record = { is_active: form.is_active, role: form.role, }; if (form.email) payload.email = form.email; if (form.name) payload.name = form.name; if (form.phone) payload.phone = form.phone; if (form.password) payload.password = form.password; return payload; } function UserFormFields({ data, error, isSubmitting, mode = 'create', onSubmit, setField, submitLabel, }: { data: UserForm; error?: string; isSubmitting: boolean; mode?: 'create' | 'edit'; onSubmit: (event: FormEvent) => void; setField: (key: K, value: UserForm[K]) => void; submitLabel: string; }) { const [showPassword, setShowPassword] = useState(false); return (
setField('name', event.target.value) } /> setField('email', event.target.value) } />
setField('password', event.target.value) } />
setField('identity_number', event.target.value) } /> setField('phone', event.target.value) } />
); }