listing-facebook/client/app/routes/users/resources.tsx

165 lines
4.7 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import _ from "lodash";
import { FolderMinus, FolderOpen } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "react-router";
import { resourceApi } from "~/api/resource-api.service";
import { DataContainer } from "~/components/core/data-container";
import { type TableState } from "~/components/core/data-table";
import Loader from "~/components/loader";
import {
stateToURLQuery,
urlQueryToState,
} from "~/features/state-url-converter";
import { useUsersContext } from "~/layouts/contexts/user-layout.context";
import ResourceItem from "./components/resources/resource-item";
import { delay } from "~/features/delay";
export default function Resources() {
const { setTitle, setDescription, setAction } = useUsersContext();
const [searchParams, setSearchParams] = useSearchParams();
const [openAll, setOpenAll] = useState(false);
const prevStates = useRef<Record<string, any> | null>(null);
const [initStates, setInitStates] = useState<
Record<string, any> | undefined
>();
const [states, setStates] = useState<Record<string, any> | undefined>();
// Init title + description
useEffect(() => {
setTitle("Danh sách resources");
setDescription("Bạn có thể điều chỉnh và tìm kiếm resource tại đây.");
setAction(null);
}, []);
// Init state từ URL
useEffect(() => {
const s = urlQueryToState(searchParams);
setInitStates(s);
setStates((prev) => (prev ? prev : s)); // chỉ gán nếu chưa có state
}, []);
// Chuyển state sang query string + kiểm tra nếu khác với lần trước mới update
useEffect(() => {
if (!states) return;
const query = stateToURLQuery(states as TableState<IResource>);
setSearchParams(query);
const { data, ...curStates } = states;
if (_.isEqual(curStates, prevStates.current)) return;
prevStates.current = curStates;
}, [states]);
// 👉 Ổn định queryKey để tránh refetch không cần thiết
const queryKey = useMemo(() => ["resources", states], [states]);
const { data, isLoading } = useQuery({
queryKey,
queryFn: async () => {
if (!states) return [];
await delay(300); // Giả lập delay để thấy loading
const res = await resourceApi.index(states as TableState<IResource>);
return res;
},
enabled: !!states,
});
const filterOptions: FilterOption[] = [
{
key: "name",
label: "Name",
type: "text" as const,
},
{
key: "group_key",
label: "Group key",
type: "text" as const,
},
{
key: "created_at",
label: "Ngày tạo",
type: "dateRange" as const,
},
];
const customeActions = [
{
key: "expose",
label: "Mở rộng tất cả",
icon: <FolderOpen className="h-4 w-4" />,
variant: "outline" as const,
action: () => setOpenAll(true),
},
{
key: "excluse",
label: "Thu gọn tất cả",
icon: <FolderMinus className="h-4 w-4" />,
variant: "outline" as const,
action: () => setOpenAll(false),
},
];
const sortOptions: SortOption<IResource>[] = [
{ key: "id", label: "ID" },
{ key: "name", label: "Tên" },
{ key: "created_at", label: "Ngày tạo" },
];
if (!initStates) return <Loader />;
return (
<div>
<DataContainer
sortOptions={sortOptions}
filterOptions={filterOptions}
initialState={initStates}
data={data?.data || []}
searchKeys={["id"]}
selectable
loading={isLoading}
pagination={{
currentPage: data?.current_page || 1,
pageSize: data?.per_page || 10,
totalItems: data?.total || 0,
totalPages: data?.last_page || 0,
startIndex: data?.from || 0,
endIndex: data?.to || 0,
}}
customActions={customeActions}
onPaginationChange={(p) => {
setStates((prev) => ({
...prev,
pagination: {
...prev?.pagination,
currentPage: p.currentPage,
pageSize: p.pageSize,
},
}));
}}
onFilterChange={({ search, ...filters }) => {
setStates((prev) => ({
...prev,
search,
filters,
}));
}}
onSortChange={(sort) => {
setStates((prev) => ({
...prev,
sort,
}));
}}
>
<div className="w-full flex flex-col gap-4 py-4">
{(data?.data || []).map((item: any) => (
<ResourceItem key={item.id} openOnMount={openAll} data={item} />
))}
</div>
</DataContainer>
</div>
);
}