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>
 | 
						|
  );
 | 
						|
}
 |