1import { useEffect, useMemo } from "react";
2import { DataTableViewOptions } from "./components";
3import { useQuery } from "@tanstack/react-query";
4import { RowSelectionState, ColumnDef } from "@tanstack/react-table";
5import { EntityTableContext } from "./contexts";
6import { TableSearch, DataTablePagination, EntityDataTable } from "./components";
7import {
8 type UseRowSelectionReturn,
9 usePrimaryFiltering,
10 usePagination,
11 type FetchEntityReturn,
12 useEntityTable,
13 useVisibility,
14 useSorting,
15 type SelectableQueryKey,
16 type SelectableEntityQueryKeyType,
17 useFilters,
18} from "./hooks";
19
20export interface SelectableEntityTableProps<T extends { id: number }> {
21 fetchEntity: ({ queryKey }: SelectableEntityQueryKeyType) => FetchEntityReturn<T>;
22 columns: ColumnDef<T>[];
23 primaryFilter: string;
24 parentEntityKey: string;
25 parentEntityId: string | number;
26 entityKey: string;
27 rowSelection: UseRowSelectionReturn;
28 entitySelection: {
29 selectedEntity: number[];
30 setSelectedEntity: (s: number[]) => void;
31 }
32 existingEntityIds: number[];
33}
34
35export function SelectableEntityTable<T extends { id: number }>({
36 fetchEntity,
37 columns,
38 primaryFilter,
39 rowSelection,
40 entitySelection,
41 entityKey,
42 parentEntityKey,
43 parentEntityId,
44 existingEntityIds,
45}: SelectableEntityTableProps<T>) {
46 const columnPrimaryFilter = usePrimaryFiltering({ column: primaryFilter });
47 const filters = useFilters();
48 const sorting = useSorting();
49 const visibility = useVisibility();
50 const { selectedRow, setSelectedRow } = rowSelection;
51 const { setSelectedEntity } = entitySelection;
52 const { onPaginationChange, pageIndex, pageSize } = usePagination();
53 const query: SelectableQueryKey = [
54 parentEntityKey,
55 parentEntityId,
56 entityKey,
57 {
58 page: pageIndex,
59 size: pageSize,
60 },
61 columnPrimaryFilter.columnFilters,
62 {
63 sortBy: sorting.sorting[0]?.id || "created_at",
64 desc: sorting.sorting[0]?.desc || true,
65 },
66 { filters: filters.columnsFilter }
67 ];
68
69 const { data, isFetching } = useQuery({
70 queryFn: fetchEntity,
71 queryKey: query,
72 initialData: { entities: [], pageCount: 1 },
73 });
74
75 const table = useEntityTable({
76 data,
77 columns,
78 pageSize,
79 pageIndex,
80 rowSelection,
81 visibility,
82 sorting,
83 onPaginationChange,
84 });
85
86 useEffect(() => {
87 setSelectedRow((selected) => {
88 const updatedSelected: RowSelectionState = { ...selected };
89 for (const [i, fetchedEntity] of data.entities.entries()) {
90 if (existingEntityIds.includes(fetchedEntity.id)) {
91 updatedSelected[i] = true;
92 } else {
93 updatedSelected[i] = false;
94 }
95 }
96 return updatedSelected;
97 });
98
99 }, [data, setSelectedRow, existingEntityIds]);
100
101 useEffect(() => {
102 const selectedRowId = Object.keys(selectedRow).filter(key => selectedRow[key] === true).map(Number);
103
104 setSelectedEntity(
105 selectedRowId
106 .map(id => data.entities[id]?.id)
107 .filter(id => id !== null && id !== undefined)
108 );
109 }, [data, setSelectedEntity, setSelectedRow, existingEntityIds, selectedRow]);
110
111
112 const contextValue = useMemo(
113 () => ({ table, data: data.entities, primaryFilter: columnPrimaryFilter, filters, isLoading: isFetching }),
114 [table, data.entities, filters, columnPrimaryFilter, isFetching],
115 );
116
117 return (
118 <EntityTableContext.Provider value={contextValue}>
119 <div className="flex flex-col">
120 <div className="flex flex-col md:flex-row-reverse items-center py-4 gap-2 w-full">
121 <div className="flex flex-row items-center w-full">
122 <DataTableViewOptions table={table} />
123 </div>
124 <TableSearch />
125 </div>
126 <div className="w-full rounded-md border">
127 <EntityDataTable columns={columns} />
128 <DataTablePagination table={table} />
129 </div>
130 </div>
131 </EntityTableContext.Provider>
132 );
133}