diff --git a/api.ts b/api.ts index 7da9c05..0871f4f 100644 --- a/api.ts +++ b/api.ts @@ -17,5 +17,8 @@ export const API = { VERIFYOTP: `${API_URL_ROOT}/auth/token/verify`, REFRESH: `${API_URL_ROOT}/auth/refresh`, GOOGLE_LOGIN: `${API_URL_ROOT}/auth/google/login` + }, + Admin:{ + GET_LIST_USERS: `${API_URL_ROOT}/users`, } } \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index 8e26e05..24a47b6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,6 +10,12 @@ const nextConfig: NextConfig = { port: '', pathname: '/**', }, + { + protocol: 'https', + hostname: 'cdn.kain.id.vn', + port: '', + pathname: '/**', + }, ], }, diff --git a/src/app/(admin)/(others-pages)/(tables)/basic-tables/page.tsx b/src/app/(admin)/(others-pages)/(tables)/basic-tables/page.tsx index c0cae1c..7b63561 100644 --- a/src/app/(admin)/(others-pages)/(tables)/basic-tables/page.tsx +++ b/src/app/(admin)/(others-pages)/(tables)/basic-tables/page.tsx @@ -15,11 +15,11 @@ export default function BasicTables() { return (
-
+ {/*
-
+
*/}
); } diff --git a/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx b/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx new file mode 100644 index 0000000..b21d5b9 --- /dev/null +++ b/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx @@ -0,0 +1,111 @@ +"use client"; +import ComponentCard from "@/components/common/ComponentCard"; +import PageBreadcrumb from "@/components/common/PageBreadCrumb"; +import BasicTableOne from "@/components/tables/BasicTableOne"; +import { fullDataUser, getUserDto } from "@/interface/admin"; +import { apiGetListUser } from "@/service/adminService"; +import { useEffect, useState } from "react"; + +// Trích xuất type sort cho dễ tái sử dụng +export type SortColumn = "created_at" | "updated_at" | "display_name" | "email"; + +export default function UserTable() { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + + const [searchTerm, setSearchTerm] = useState(""); + const [debouncedSearch, setDebouncedSearch] = useState(""); + + const [sortBy, setSortBy] = useState(undefined); + const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearch(searchTerm); + }, 500); + + return () => clearTimeout(handler); + }, [searchTerm]); + + useEffect(() => { + const fetchUser = async () => { + setLoading(true); + try { + const payload: getUserDto = { limit: 10 }; + if (debouncedSearch) payload.search = debouncedSearch; + if (sortBy) { + payload.sort = sortBy; + payload.order = sortOrder; + } + + const response = await apiGetListUser(payload); + // console.log("Request Payload:", payload); + // console.log("API Response:", response); + if (response && response.data) { + setUsers(response.data); + } else { + setUsers([]); + } + } catch (err) { + console.error("Lỗi:", err); + setUsers([]); + } finally { + setLoading(false); + } + }; + + fetchUser(); + }, [debouncedSearch, sortBy, sortOrder]); + + const handleSort = (column: SortColumn) => { + if (sortBy === column) { + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); + } else { + setSortBy(column); + setSortOrder("desc"); + } + }; + + return ( +
+ +
+ + {/* Ô nhập tìm kiếm */} +
+ setSearchTerm(e.target.value)} + className="w-full sm:w-1/3 px-4 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-transparent dark:bg-gray-800 dark:border-gray-700 dark:text-white" + /> +
+ +
+ {loading && ( +
+
+ {/* Spinner xoay tròn */} +
+

+ Đang tải... +

+
+
+ )} + + {/* Bảng vẫn hiển thị ở dưới (mờ đi) hoặc ẩn tùy bạn, + nhưng truyền data vào để tránh giật lag layout */} + +
+
+
+
+ ); +} diff --git a/src/components/auth/SignInForm.tsx b/src/components/auth/SignInForm.tsx index 0794ca5..f9872e5 100644 --- a/src/components/auth/SignInForm.tsx +++ b/src/components/auth/SignInForm.tsx @@ -70,6 +70,7 @@ export default function SignInForm() { if (res.status === true) { toast.success("Đăng nhập thành công!"); const data = await apiGetCurrentUser(); + console.log("Current User Data:", data); if (data?.data) { dispatch(setUserData(data.data)); diff --git a/src/components/header/UserDropdown.tsx b/src/components/header/UserDropdown.tsx index 8c064db..3b5479e 100644 --- a/src/components/header/UserDropdown.tsx +++ b/src/components/header/UserDropdown.tsx @@ -1,37 +1,60 @@ "use client"; import Image from "next/image"; import Link from "next/link"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Dropdown } from "../ui/dropdown/Dropdown"; import { DropdownItem } from "../ui/dropdown/DropdownItem"; +import { fullDataUser } from "@/interface/admin"; +import { UserMetaCardProps } from "@/interface/user"; +import { apiGetCurrentUser } from "@/service/auth"; export default function UserDropdown() { const [isOpen, setIsOpen] = useState(false); - -function toggleDropdown(e: React.MouseEvent) { - e.stopPropagation(); - setIsOpen((prev) => !prev); -} + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + function toggleDropdown(e: React.MouseEvent) { + e.stopPropagation(); + setIsOpen((prev) => !prev); + } function closeDropdown() { setIsOpen(false); } + + useEffect(() => { + const fetchUser = async () => { + try { + const userData = await apiGetCurrentUser(); + console.log("User data in dropdown:", userData); + setUser(userData); + } catch (err) { + console.error("Lỗi:", err); + } finally { + setLoading(false); + } + }; + fetchUser(); + }, []); + + return (