165 lines
4.7 KiB
TypeScript
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>
|
|
);
|
|
}
|