@@ -10,8 +10,13 @@ import * as Icons from "lucide-react";
1010import {
1111 Tooltip , TooltipContent , TooltipProvider , TooltipTrigger ,
1212} from "@/components/ui/tooltip" ;
13- import { SaveConfirmModal } from "@/components/modals/AlertsModal " ;
13+ import EditUserModal from "@/components/forms/users/EditUserForm " ;
1414
15+ import { SaveConfirmModal , DeleteConfirmModal } from "@/components/modals/AlertsModal" ;
16+ import { showAlert } from "@/components/modals/StatusAlert" ;
17+ import { useMe } from "@/hooks/useMe" ;
18+
19+ import { DeleteUserDialog } from "@/components/forms/users/DeleteUserForm" ;
1520/* ------------------------------ ROLE COLORS ------------------------------ */
1621const ROLE_STYLES = {
1722 admin : "bg-emerald-50 text-emerald-700 ring-emerald-200" ,
@@ -42,6 +47,14 @@ function IconAction({
4247 variant = "primary" ,
4348 onClick,
4449 className = "" ,
50+ disabled,
51+ } : {
52+ label : string ;
53+ children : React . ReactNode ;
54+ variant ?: "primary" | "danger" | "info" | "success" ;
55+ onClick ?: ( ) => void ;
56+ className ?: string ;
57+ disabled ?: boolean ;
4558} ) {
4659 const palette =
4760 variant === "danger"
@@ -79,6 +92,7 @@ function IconAction({
7992 < button
8093 onClick = { onClick }
8194 aria-label = { label }
95+ disabled = { disabled }
8296 className = { [
8397 "inline-flex items-center justify-center h-8 w-8 rounded-full border" ,
8498 palette . border ,
@@ -87,6 +101,7 @@ function IconAction({
87101 "hover:text-white hover:border-transparent" ,
88102 "transition focus:outline-none focus:ring-2 focus:ring-offset-2" ,
89103 palette . focusRing ,
104+ "disabled:opacity-50 disabled:cursor-not-allowed" ,
90105 className ,
91106 ] . join ( " " ) }
92107 >
@@ -122,7 +137,7 @@ export default function UserTable({ users }: { users: User[] }) {
122137 useEffect ( ( ) => setRows ( users ) , [ users ] ) ;
123138
124139 // Pagination
125- const PAGE_SIZE = 25 ;
140+ const PAGE_SIZE = 10 ;
126141 const [ page , setPage ] = useState ( 1 ) ;
127142
128143 // Sorting
@@ -190,7 +205,12 @@ export default function UserTable({ users }: { users: User[] }) {
190205 return ;
191206 }
192207
193- alert ( "Password has been reset." ) ;
208+ showAlert ( {
209+ title : "Success" ,
210+ message : "Password has been reset successfully." ,
211+ variant : "success" ,
212+ duration : 3000 ,
213+ } ) ;
194214 } catch ( err ) {
195215 alert ( "Error resetting password." ) ;
196216 } finally {
@@ -200,14 +220,91 @@ export default function UserTable({ users }: { users: User[] }) {
200220 } ;
201221
202222 /* ----------------------------- ACTIONS ----------------------------- */
203- const onResetPassword = ( id : number ) => {
204- setResetUserId ( id ) ;
205- setConfirmOpen ( true ) ;
223+ const [ editingUserId , setEditingUserId ] = useState < number | null > ( null ) ;
224+ const [ editingOpen , setEditingOpen ] = useState ( false ) ;
225+
226+ const handleOpenEdit = ( id : number ) => {
227+ setEditingUserId ( id ) ;
228+ setEditingOpen ( true ) ;
206229 } ;
230+ const handleUserUpdated = ( updated : User ) => {
231+ setRows ( ( prev ) => prev . map ( ( r ) => ( r . usr_id === updated . usr_id ? { ...r , ...updated } : r ) ) ) ;
232+ } ;
233+
234+ // --- new delete-related state ---
235+ const { me, loading : loadingMe , error : meError } = useMe ( ) ;
236+ const [ busyId , setBusyId ] = useState < number | null > ( null ) ;
237+ const [ deleteOpen , setDeleteOpen ] = useState ( false ) ;
238+ const [ deleteTarget , setDeleteTarget ] = useState < { id : number ; name ?: string } | null > ( null ) ;
207239
208- // ฟังก์ชันสำหรับปุ่ม Action พร้อมให้เชื่อมต่อ logic จริง เช่น แก้ไขหรือ ลบผู้ใช้
209- const onEdit = ( id : number ) => console . log ( "Edit user:" , id ) ;
210- const onDelete = ( id : number ) => console . log ( "Delete user:" , id ) ;
240+ function openDelete ( u : User ) {
241+ setDeleteTarget ( { id : u . usr_id , name : u . usr_username } ) ;
242+ setDeleteOpen ( true ) ;
243+ }
244+
245+ // onConfirmDelete signature follows DeleteConfirmModal -> receives object { input?, note? }
246+ async function onConfirmDelete ( _ : { input ?: string ; note ?: string } ) {
247+ if ( ! deleteTarget ) return ;
248+
249+ // get user id who performs the action
250+ const user_id = Number ( ( me as any ) ?. usr_id ) ;
251+ if ( ! Number . isFinite ( user_id ) || user_id <= 0 ) {
252+ // fallback: show alert and keep modal open
253+ showAlert ( {
254+ title : "Error" ,
255+ message : meError || "Cannot resolve user_id from current user." ,
256+ variant : "destructive" ,
257+ duration : 4000 ,
258+ } ) ;
259+ return ;
260+ }
261+
262+ try {
263+ setBusyId ( deleteTarget . id ) ;
264+
265+ // call backend to deactivate/delete user
266+ const res = await fetch ( `/api/users/${ deleteTarget . id } /deactivate` , {
267+ method : "PATCH" ,
268+ credentials : "include" ,
269+ cache : "no-store" ,
270+ headers : { "Content-Type" : "application/json" } ,
271+ body : JSON . stringify ( { user_id } ) ,
272+ } ) ;
273+
274+ const json = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
275+ if ( ! res . ok ) throw new Error ( json ?. message || "Delete failed" ) ;
276+
277+ // remove from local rows
278+ setRows ( ( prev ) => prev . filter ( ( r ) => r . usr_id !== deleteTarget . id ) ) ;
279+
280+ // show status alert
281+ showAlert ( {
282+ title : "Deleted" ,
283+ message : `User "${ deleteTarget . name ?? deleteTarget . id } " has been deactivated.` ,
284+ variant : "success" ,
285+ duration : 3000 ,
286+ } ) ;
287+
288+ setDeleteOpen ( false ) ;
289+ setDeleteTarget ( null ) ;
290+ } catch ( e : any ) {
291+ // show status alert error
292+ showAlert ( {
293+ title : "Error" ,
294+ message : e ?. message || "Delete failed" ,
295+ variant : "destructive" ,
296+ duration : 4000 ,
297+ } ) ;
298+ } finally {
299+ setBusyId ( null ) ;
300+ }
301+ }
302+
303+ const onDeleteClick = ( id : number ) => {
304+ // kept for compatibility or if you want to pass to child props
305+ const u = rows . find ( ( r ) => r . usr_id === id ) ;
306+ if ( u ) openDelete ( u ) ;
307+ } ;
211308
212309 if ( ! pagedUsers . length ) {
213310 return < div className = "text-sm text-gray-500" > No users to display.</ div > ;
@@ -249,7 +346,7 @@ export default function UserTable({ users }: { users: User[] }) {
249346 < TableBody >
250347 { pagedUsers . map ( ( usr ) => (
251348 < TableRow key = { usr . usr_id } >
252- < TableCell > { `USR${ String ( usr . usr_id ) . padStart ( 4 , "0" ) } ` } </ TableCell >
349+ < TableCell > { `USR${ String ( usr . usr_id ) . padStart ( 3 , "0" ) } ` } </ TableCell >
253350 < TableCell > { usr . usr_name } </ TableCell >
254351 < TableCell > < RoleBadge value = { usr . usr_role } /> </ TableCell >
255352 < TableCell > { usr . usr_username } </ TableCell >
@@ -262,23 +359,24 @@ export default function UserTable({ users }: { users: User[] }) {
262359 < IconAction
263360 label = "Reset Password"
264361 variant = "primary"
265- onClick = { ( ) => onResetPassword ( usr . usr_id ) }
362+ onClick = { ( ) => { setResetUserId ( usr . usr_id ) ; setConfirmOpen ( true ) ; } }
266363 >
267364 < KeyRound className = "w-4 h-4" />
268365 </ IconAction >
269366
270367 < IconAction
271368 label = "Edit"
272369 variant = "primary"
273- onClick = { ( ) => onEdit ( usr . usr_id ) }
370+ onClick = { ( ) => handleOpenEdit ( usr . usr_id ) }
274371 >
275372 < Pencil className = "w-4 h-4" />
276373 </ IconAction >
277374
278375 < IconAction
279376 label = "Delete"
280377 variant = "danger"
281- onClick = { ( ) => onDelete ( usr . usr_id ) }
378+ onClick = { ( ) => openDelete ( usr ) }
379+ disabled = { busyId === usr . usr_id }
282380 >
283381 < Trash2 className = "w-4 h-4" />
284382 </ IconAction >
@@ -335,6 +433,27 @@ export default function UserTable({ users }: { users: User[] }) {
335433 onConfirm = { handleConfirmReset }
336434 icon = { < KeyRound className = "h-7 w-7 text-blue-600" /> }
337435 />
436+
437+ { /* edit modal */ }
438+ < EditUserModal
439+ usrId = { editingUserId }
440+ open = { editingOpen }
441+ setOpen = { setEditingOpen }
442+ onUpdated = { handleUserUpdated }
443+ />
444+
445+ { /* Delete confirm modal — no typing required (just Cancel / Delete) */ }
446+ < DeleteConfirmModal
447+ open = { deleteOpen }
448+ onOpenChange = { ( v ) => {
449+ if ( ! v ) setDeleteTarget ( null ) ;
450+ setDeleteOpen ( v ) ;
451+ } }
452+ title = "Delete User?"
453+ description = { `This will deactivate "${ deleteTarget ?. name ?? "this user" } " (ID: ${ deleteTarget ?. id ?? "—" } ) and related data. This action cannot be undone.` }
454+ confirmText = "Delete"
455+ onConfirm = { onConfirmDelete }
456+ />
338457 </ div >
339458 ) ;
340- }
459+ }
0 commit comments