"use client"; import ComponentCard from "@/components/common/ComponentCard"; import Swal from "sweetalert2"; import PageBreadcrumb from "@/components/common/PageBreadCrumb"; import BasicTableOne from "@/components/tables/BasicTableOne"; import ChangeRoleModal from "@/components/tables/ChangeRoleModal"; import UserDetailModal from "@/components/tables/UserDetailModal"; import { responseUserTable, getUserDto, fullDataUser, createUser } from "@/interface/admin"; import { apiDeleteUser, apiGetAllRole, apiGetListUser, apiRestoreUser, apiCreateUser, apiResetPassword, } from "@/service/adminService"; import { useEffect, useState, useCallback } from "react"; import Pagination from "@/components/tables/Pagination"; import { LIMIT_ITEM_TABLE, IS_SEND_EMAIL } from "../../../../../../constant"; import CustomDateRangePicker from "@/components/common/CustomDateRangePicker"; import Input from "@/components/form/input/InputField"; export type SortColumn = "created_at" | "updated_at" | "display_name" | "email"; const formatDateTimeToISO = ( dateStr: string, timeStr: string, isEndOfDay: boolean = false, ): string | undefined => { if (!dateStr) return undefined; const time = timeStr || (isEndOfDay ? "23:59" : "00:00"); return `${dateStr}T${time}:00.000000+07:00`; }; export default function UserTable() { const [page, setPage] = useState(1); const [limitInput, setLimitInput] = useState(LIMIT_ITEM_TABLE.toString()); const [selectedRole, setSelectedRole] = useState(""); const [roles, setRoles] = useState<{ id: string; name: string }[]>([]); const [searchTerm, setSearchTerm] = useState(""); const [authProvider, setAuthProvider] = useState(""); const [isDeleted, setIsDeleted] = useState(undefined); const [fromDate, setFromDate] = useState(""); const [fromTime, setFromTime] = useState(""); const [toDate, setToDate] = useState(""); const [toTime, setToTime] = useState(""); const [selectedUser, setSelectedUser] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [roleUser, setRoleUser] = useState(null); const [isRoleModalOpen, setIsRoleModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [createPayload, setCreatePayload] = useState({ email: "", display_name: "", password: "", role_ids: [], }); const [isResetModalOpen, setIsResetModalOpen] = useState(false); const [resetUser, setResetUser] = useState(null); const [resetPayload, setResetPayload] = useState({ new_password: "", is_send_email: IS_SEND_EMAIL, }); const [debouncedParams, setDebouncedParams] = useState({ search: "", limit: 5, authProvider: "", fromDate: "", fromTime: "", toDate: "", toTime: "", }); const [tableData, setTableData] = useState(null); const [loading, setLoading] = useState(true); const [sortBy, setSortBy] = useState(undefined); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const handleReset = () => { setSearchTerm(""); setAuthProvider(""); setIsDeleted(undefined); setSelectedRole(""); setLimitInput(LIMIT_ITEM_TABLE.toString()); setFromDate(""); setFromTime(""); setToDate(""); setToTime(""); setPage(1); setDebouncedParams({ search: "", limit: LIMIT_ITEM_TABLE, authProvider: "", fromDate: "", fromTime: "", toDate: "", toTime: "", }); }; useEffect(() => { const fetchRoles = async () => { try { const res = await apiGetAllRole(); if (res?.status) { setRoles(res.data); } } catch (err) { console.error("Lỗi lấy danh sách role:", err); } }; fetchRoles(); }, []); useEffect(() => { const handler = setTimeout(() => { setDebouncedParams({ search: searchTerm, limit: parseInt(limitInput) || LIMIT_ITEM_TABLE, authProvider: authProvider, fromDate, fromTime, toDate, toTime, }); setPage(1); }, 600); return () => clearTimeout(handler); }, [ searchTerm, limitInput, authProvider, fromDate, fromTime, toDate, toTime, ]); const fetchUsers = useCallback(async () => { setLoading(true); try { const payload: any = { page: page, limit: debouncedParams.limit, search: debouncedParams.search || undefined, auth_provider: debouncedParams.authProvider || undefined, is_deleted: isDeleted, sort: sortBy, order: sortOrder, role_ids: selectedRole ? [selectedRole] : undefined, }; // Thêm format ngày giờ vào payload const createdFrom = formatDateTimeToISO( debouncedParams.fromDate, debouncedParams.fromTime, false, ); if (createdFrom) payload.created_from = createdFrom; const createdTo = formatDateTimeToISO( debouncedParams.toDate, debouncedParams.toTime, true, ); if (createdTo) payload.created_to = createdTo; const response = await apiGetListUser(payload as getUserDto); if (response?.status) { setTableData(response); } } catch (err) { console.error("Fetch error:", err); setTableData(null); } finally { setLoading(false); } }, [page, debouncedParams, isDeleted, sortBy, sortOrder, selectedRole]); useEffect(() => { fetchUsers(); }, [fetchUsers]); const handleSort = (column: SortColumn) => { setPage(1); if (sortBy === column) { setSortOrder(sortOrder === "asc" ? "desc" : "asc"); } else { setSortBy(column); setSortOrder("desc"); } }; const pagination = tableData?.pagination; // console.log(pagination); const handleOpenDetail = (user: fullDataUser) => { setSelectedUser(user); setIsModalOpen(true); }; const generateRandomPassword = () => { const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const lowercase = "abcdefghijklmnopqrstuvwxyz"; const numbers = "0123456789"; const specials = "!@#$%^&*()_+~`|}{[]:;?><,./-="; let password = ""; password += uppercase[Math.floor(Math.random() * uppercase.length)]; password += numbers[Math.floor(Math.random() * numbers.length)]; password += specials[Math.floor(Math.random() * specials.length)]; const allChars = lowercase + uppercase + numbers + specials; for (let i = 0; i < 5; i++) { password += allChars[Math.floor(Math.random() * allChars.length)]; } return password.split('').sort(() => 0.5 - Math.random()).join(''); }; const handleOpenCreateModal = () => { setCreatePayload({ email: "", display_name: "", password: generateRandomPassword(), role_ids: [], }); setIsCreateModalOpen(true); }; const handleCreateUser = async () => { try { const res = await apiCreateUser(createPayload as any); if (res?.status) { Swal.fire("Thành công", "Tạo tài khoản thành công.", "success"); setIsCreateModalOpen(false); fetchUsers(); } else { Swal.fire("Thất bại", res?.message || "Không thể tạo tài khoản.", "error"); } } catch (err: any) { Swal.fire("Lỗi!", err?.response?.data?.message || "Có lỗi xảy ra.", "error"); } }; const handleOpenResetModal = (user: fullDataUser) => { setResetUser(user); setResetPayload({ new_password: generateRandomPassword(), is_send_email: IS_SEND_EMAIL, }); setIsResetModalOpen(true); }; const handleResetPassword = async () => { if (!resetUser) return; try { const res = await apiResetPassword(resetUser.id, resetPayload as any); if (res?.status) { Swal.fire("Thành công", "Đặt lại mật khẩu thành công.", "success"); setIsResetModalOpen(false); } else { Swal.fire("Thất bại", res?.message || "Không thể đặt lại mật khẩu.", "error"); } } catch (err: any) { Swal.fire("Lỗi!", err?.response?.data?.message || "Có lỗi xảy ra.", "error"); } }; const handleOpenRoleModal = (user: fullDataUser) => { setRoleUser(user); setIsRoleModalOpen(true); }; const handleDelete = async (user: fullDataUser) => { const result = await Swal.fire({ title: "Xác nhận khóa?", text: `Bạn có chắc muốn khóa người dùng ${user.profile?.display_name || user.email}?`, icon: "warning", showCancelButton: true, showCloseButton: true, confirmButtonColor: "#d33", cancelButtonColor: "#3085d6", confirmButtonText: "Đồng ý, khóa!", cancelButtonText: "Hủy", reverseButtons: true, }); if (result.isConfirmed) { try { await apiDeleteUser(user.id); Swal.fire( "Đã khóa!", "Người dùng đã bị tạm dừng hoạt động.", "success", ); fetchUsers(); } catch (err) { Swal.fire("Lỗi!", "Không thể thực hiện thao tác này.", "error"); } } }; const handleRestore = async (user: fullDataUser) => { const result = await Swal.fire({ title: "Khôi phục tài khoản?", text: `Khôi phục quyền truy cập cho ${user.profile?.display_name || user.email}?`, icon: "question", showCancelButton: true, showCloseButton: true, confirmButtonColor: "#28a745", confirmButtonText: "Xác nhận", cancelButtonText: "Hủy", }); if (result.isConfirmed) { try { await apiRestoreUser(user.id); Swal.fire("Thành công", "Tài khoản đã được khôi phục.", "success"); fetchUsers(); } catch (err) { Swal.fire("Thất bại", "Vui lòng thử lại sau.", "error"); } } }; return (
} >
setSearchTerm(e.target.value)} className="w-full px-3 py-2 border rounded-lg dark:bg-gray-800 dark:border-gray-700 outline-none focus:border-brand-500" />
{ setFromDate(sDate); setToDate(eDate); setFromTime(sTime); setToTime(eTime); }} />
{/* Limit */}
setLimitInput(e.target.value)} className="w-full px-3 py-2 border rounded-lg dark:bg-gray-800 dark:border-gray-700 outline-none focus:border-brand-500" />
Tạo tài khoản } >
{loading && (
)} setSelectedRole(role)} />

Hiển thị trang {pagination?.current_page || 1} /{" "} {pagination?.total_pages || 1} ({pagination?.total_records || 0}{" "} kết quả)

{pagination && pagination.total_pages > 1 && ( setPage(newPage)} /> )}
setIsModalOpen(false)} user={selectedUser} onChangeRole={handleOpenRoleModal} onResetPassword={(u) => { handleOpenResetModal(u); setIsModalOpen(false); }} onDelete={(u) => { handleDelete(u); setIsModalOpen(false); }} onRestore={(u) => { handleRestore(u); setIsModalOpen(false); }} /> setIsRoleModalOpen(false)} user={roleUser} onSuccess={fetchUsers} /> {/* Modal Tạo Tài Khoản */} {isCreateModalOpen && (

Tạo tài khoản mới

setCreatePayload({...createPayload, display_name: e.target.value})} className="w-full px-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-brand-500" placeholder="Nhập tên hiển thị..." />
setCreatePayload({...createPayload, email: e.target.value})} className="w-full px-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-brand-500" placeholder="Nhập email..." />
{roles.map((role) => ( ))}
setCreatePayload({...createPayload, password: e.target.value})} className="w-full px-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-brand-500" />
)} {isResetModalOpen && (

Reset mật khẩu

setResetPayload({...resetPayload, new_password: e.target.value})} className="w-full px-4 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100 outline-none focus:ring-2 focus:ring-brand-500" />
setResetPayload({...resetPayload, is_send_email: e.target.checked})} className="w-4 h-4 text-brand-500 border-gray-300 rounded focus:ring-brand-500 cursor-pointer" />
)}
); }