173 lines
5.0 KiB
TypeScript
173 lines
5.0 KiB
TypeScript
import { useQuery } from "@tanstack/react-query";
|
|
import _ from "lodash";
|
|
import { FolderMinus, FolderOpen, Plus } from "lucide-react";
|
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
import { useSearchParams } from "react-router";
|
|
import { rolesApi } from "~/api/roles-api.service";
|
|
import { DataContainer } from "~/components/core/data-container";
|
|
import { type TableState } from "~/components/core/data-table";
|
|
import Loader from "~/components/loader";
|
|
import { Button } from "~/components/ui/button";
|
|
import { delay } from "~/features/delay";
|
|
import {
|
|
stateToURLQuery,
|
|
urlQueryToState,
|
|
} from "~/features/state-url-converter";
|
|
import { useUsersContext } from "~/layouts/contexts/user-layout.context";
|
|
import RoleItem from "./components/roles/role-item";
|
|
import { RoleCreateModal } from "./components/roles/role-create-modal";
|
|
import { queryClient } from "~/lib/query-client";
|
|
|
|
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 vai trò");
|
|
setDescription("Bạn có thể điều chỉnh và tìm kiếm vai trò tại đây.");
|
|
setAction(
|
|
<>
|
|
<RoleCreateModal onSubmited={refetch}>
|
|
<Button>
|
|
<Plus />
|
|
Thêm vai trò
|
|
</Button>
|
|
</RoleCreateModal>
|
|
</>
|
|
);
|
|
}, []);
|
|
|
|
// 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(() => ["roles", states], [states]);
|
|
|
|
const { data, isLoading, refetch } = useQuery({
|
|
queryKey,
|
|
queryFn: async () => {
|
|
if (!states) return [];
|
|
await delay(300); // Giả lập delay để thấy loading
|
|
const res = await rolesApi.index(states);
|
|
return res;
|
|
},
|
|
enabled: !!states,
|
|
});
|
|
|
|
const filterOptions: FilterOption[] = [
|
|
{
|
|
key: "id",
|
|
label: "ID",
|
|
type: "number" 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: "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="space-y-4">
|
|
{(data?.data || ([] as IRole[])).map((role: IRole) => (
|
|
<RoleItem
|
|
onDeleted={() => {
|
|
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
refetch();
|
|
}}
|
|
data={role}
|
|
key={role.role_key}
|
|
/>
|
|
))}
|
|
</div>
|
|
</DataContainer>
|
|
</div>
|
|
);
|
|
}
|