@bilalsino/react-tanstack-data-table
Version:
Reusable React data table component with sorting, filtering, and pagination
1 lines • 103 kB
Source Map (JSON)
{"version":3,"sources":["../src/lib/stores/tableStore/index.ts","../src/components/CustomTable/index.tsx","../src/lib/utils.ts","../src/components/Loading/Loading.tsx","../src/components/CustomTable/BulkActions.tsx","../src/components/CustomTable/Pagination.tsx","../src/components/ui/button.tsx","../src/components/ui/select.tsx","../src/components/FormComponents/FormSelect/FormSelect.tsx","../src/components/CustomTable/Filter.tsx","../src/components/ui/popover.tsx","../src/components/FormComponents/FormInput/FormInput.tsx","../src/components/ui/input.tsx","../src/components/FormComponents/FormCalendar/FormCalendar.tsx","../src/components/ui/calendar.tsx","../src/components/ui/checkbox.tsx","../src/components/CustomTable/DefaultColumns.tsx"],"sourcesContent":["import {\r\n ColumnFiltersState,\r\n ColumnPinningState,\r\n PaginationState,\r\n RowSelectionState,\r\n SortingState,\r\n} from \"@tanstack/react-table\";\r\nimport { create } from \"zustand\";\r\nimport { devtools } from \"zustand/middleware\";\r\n\r\nexport type TableFilter = {\r\n tableId: string;\r\n globalFilter: string;\r\n columnFilters: ColumnFiltersState;\r\n sorting: SortingState;\r\n pagination: PaginationState;\r\n columnPinning: ColumnPinningState;\r\n rowSelection: RowSelectionState;\r\n};\r\n\r\ninterface TableStore {\r\n tableData: TableFilter[];\r\n setTableData: (tableData: TableFilter) => void;\r\n removeTableData: (tableId: string) => void;\r\n}\r\n\r\nconst useTableStore = create<TableStore>()(\r\n devtools(\r\n (set) => ({\r\n tableData: [],\r\n setTableData: (tableData) =>\r\n set((state) => {\r\n const existingTableIndex = state.tableData.findIndex(\r\n (t) => t.tableId === tableData.tableId\r\n );\r\n if (existingTableIndex >= 0) {\r\n const newTableData = [...state.tableData];\r\n newTableData[existingTableIndex] = tableData;\r\n return { tableData: newTableData };\r\n }\r\n return { tableData: [...state.tableData, tableData] };\r\n }),\r\n removeTableData: (tableId) =>\r\n set((state) => ({\r\n tableData: state.tableData.filter((t) => t.tableId !== tableId),\r\n })),\r\n }),\r\n { name: \"table-storage\" }\r\n )\r\n);\r\n\r\nexport default useTableStore;\r\n","\"use client\";\r\n\r\nimport {\r\n ColumnFiltersState,\r\n ColumnPinningState,\r\n FilterFn,\r\n flexRender,\r\n getCoreRowModel,\r\n getFacetedMinMaxValues,\r\n getFacetedRowModel,\r\n getFacetedUniqueValues,\r\n getFilteredRowModel,\r\n getPaginationRowModel,\r\n getSortedRowModel,\r\n Header,\r\n PaginationState,\r\n Row,\r\n RowSelectionState,\r\n SortingState,\r\n useReactTable,\r\n} from \"@tanstack/react-table\";\r\nimport { useEffect, useRef, useState, useMemo } from \"react\";\r\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\r\nimport { AlertTriangle, ChevronUp, ChevronDown } from \"lucide-react\";\r\nimport { Table } from \"@/components/ui/table\";\r\nimport { Loading } from \"@/components/Loading\";\r\nimport { getCommonPinningStyles } from \"@/lib/utils\";\r\nimport BulkActions from \"./BulkActions\";\r\nimport { Pagination } from \"./Pagination\";\r\nimport { CustomTableProps } from \"@/lib/types/table.types\";\r\nimport useTableStore from \"@/lib/stores/tableStore\";\r\nimport \"@/styles/globals.css\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Filter } from \"./Filter\";\r\n\r\nconst CustomTable = <T extends Record<string, any>>({\r\n tableId,\r\n columns,\r\n rows,\r\n defaultPageSize = 10,\r\n manualPagination = false,\r\n customPagination = false,\r\n defaultPinnedColumns,\r\n cardComponent,\r\n bulkActions,\r\n manualSearch,\r\n rightTop,\r\n leftTop,\r\n viewMode = \"table\",\r\n pageOffset = 15.5,\r\n scrollable = true,\r\n isLoading = false,\r\n defaultSorting,\r\n maxHeight,\r\n minHeight,\r\n emptyContent,\r\n}: CustomTableProps<T>) => {\r\n const { setTableData, tableData } = useTableStore();\r\n\r\n const initialPagination = useMemo(() => {\r\n return (\r\n tableData.find((t) => t.tableId === tableId)?.pagination || {\r\n pageIndex: 0,\r\n pageSize: defaultPageSize,\r\n }\r\n );\r\n }, [tableId, tableData, defaultPageSize]);\r\n\r\n const [pagination, setPagination] =\r\n useState<PaginationState>(initialPagination);\r\n\r\n const initialRowsData = useMemo(\r\n () => ({\r\n ...rows,\r\n data: rows.data || [],\r\n rowCount: rows.rowCount || rows.data?.length || 0,\r\n pageCount:\r\n rows.pageCount ||\r\n Math.ceil(\r\n (rows.rowCount || rows.data?.length || 0) / initialPagination.pageSize\r\n ),\r\n }),\r\n [rows, initialPagination.pageSize]\r\n );\r\n\r\n const [rowsData, setRowsData] =\r\n useState<CustomTableProps<T>[\"rows\"]>(initialRowsData);\r\n\r\n const [columnPinning, setColumnPinning] = useState<ColumnPinningState>(\r\n defaultPinnedColumns\r\n ? {\r\n left: [\"select\", ...(defaultPinnedColumns?.left || [])],\r\n right: [...(defaultPinnedColumns?.right || [])],\r\n }\r\n : {\r\n left: [],\r\n right: [],\r\n }\r\n );\r\n const [sorting, setSorting] = useState<SortingState>(\r\n tableData.find((t) => t.tableId === tableId)?.sorting ||\r\n defaultSorting ||\r\n []\r\n );\r\n const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(\r\n tableData.find((t) => t.tableId === tableId)?.columnFilters || []\r\n );\r\n const [globalFilter, setGlobalFilter] = useState<any>(\r\n tableData.find((t) => t.tableId === tableId)?.globalFilter || \"\"\r\n );\r\n const [rowSelection, setRowSelection] = useState<RowSelectionState>(\r\n tableData.find((t) => t.tableId === tableId)?.rowSelection || {}\r\n );\r\n const weakIncludesStringFn: FilterFn<T> = (\r\n srows: Row<T>,\r\n columnId: string,\r\n filterValue: any\r\n ) => {\r\n const rowValue = srows.getValue(columnId);\r\n\r\n // Eğer rowValue null veya undefined ise eşleşme yok\r\n if (rowValue == null) return false;\r\n\r\n // String olarak karşılaştırma yap\r\n const rowValueString = String(rowValue).toLowerCase();\r\n const filterValueString = String(filterValue).toLowerCase();\r\n\r\n // Case-insensitive içerik eşleşmesi veya zayıf eşitlik\r\n return (\r\n rowValueString.includes(filterValueString) || rowValue === filterValue\r\n );\r\n };\r\n useEffect(() => {\r\n if (!rows.data) return;\r\n\r\n if (manualPagination) {\r\n // For manual pagination, only update if data actually changed\r\n setRowsData((prev) => {\r\n if (\r\n prev.data === rows.data &&\r\n prev.rowCount === rows.rowCount &&\r\n prev.pageCount === rows.pageCount\r\n ) {\r\n return prev;\r\n }\r\n return rows;\r\n });\r\n } else {\r\n const newRowCount = rows.rowCount || rows.data.length;\r\n const newPageCount =\r\n rows.pageCount || Math.ceil(newRowCount / pagination.pageSize);\r\n\r\n setRowsData((prev) => {\r\n if (\r\n prev.data === rows.data &&\r\n prev.rowCount === newRowCount &&\r\n prev.pageCount === newPageCount\r\n ) {\r\n return prev;\r\n }\r\n return {\r\n ...rows,\r\n data: rows.data,\r\n rowCount: newRowCount,\r\n pageCount: newPageCount,\r\n };\r\n });\r\n }\r\n }, [rows, pagination.pageSize, manualPagination]);\r\n\r\n useEffect(() => {\r\n setTableData({\r\n tableId,\r\n columnFilters,\r\n columnPinning,\r\n sorting,\r\n pagination,\r\n globalFilter,\r\n rowSelection,\r\n });\r\n }, [\r\n tableId,\r\n columnFilters,\r\n columnPinning,\r\n sorting,\r\n pagination,\r\n globalFilter,\r\n rowSelection,\r\n setTableData,\r\n ]);\r\n const table = useReactTable<T>({\r\n columns,\r\n data: rowsData.data,\r\n getCoreRowModel: getCoreRowModel(),\r\n getSortedRowModel: getSortedRowModel(),\r\n getFilteredRowModel: getFilteredRowModel(),\r\n getPaginationRowModel: getPaginationRowModel(),\r\n getFacetedRowModel: getFacetedRowModel(),\r\n getFacetedUniqueValues: getFacetedUniqueValues(),\r\n getFacetedMinMaxValues: getFacetedMinMaxValues(),\r\n state: {\r\n columnFilters,\r\n columnPinning,\r\n sorting,\r\n pagination,\r\n globalFilter,\r\n rowSelection,\r\n },\r\n rowCount: rowsData.rowCount,\r\n onColumnPinningChange: setColumnPinning,\r\n onSortingChange: setSorting,\r\n onPaginationChange: setPagination,\r\n onColumnFiltersChange: setColumnFilters,\r\n onGlobalFilterChange: setGlobalFilter,\r\n onRowSelectionChange: setRowSelection,\r\n globalFilterFn: weakIncludesStringFn,\r\n manualPagination,\r\n enableColumnResizing: true,\r\n columnResizeMode: \"onChange\",\r\n defaultColumn: {\r\n minSize: 100,\r\n size: 100,\r\n enableResizing: true,\r\n },\r\n pageCount: rowsData.pageCount,\r\n manualFiltering: false,\r\n });\r\n\r\n const tableContainerRef = useRef<any>(null);\r\n const rowVirtualizer = useVirtualizer({\r\n count: Math.max(\r\n pagination.pageSize < table.getFilteredRowModel().rows.length\r\n ? pagination.pageSize\r\n : table.getFilteredRowModel().rows.length,\r\n 0\r\n ),\r\n estimateSize: () => 52,\r\n getScrollElement: () => tableContainerRef.current,\r\n measureElement:\r\n typeof window !== \"undefined\" &&\r\n navigator.userAgent.indexOf(\"Firefox\") === -1\r\n ? (element) => element?.getBoundingClientRect().height\r\n : undefined,\r\n overscan: 5,\r\n });\r\n const selectedRows = rowsData.data.filter((_, index) => rowSelection[index]);\r\n const actions = {\r\n selectedRows,\r\n pagination,\r\n setRowSelection,\r\n setGlobalFilter,\r\n setPagination,\r\n table,\r\n };\r\n const renderBulkActions = () => {\r\n if (typeof bulkActions === \"function\") {\r\n return bulkActions(actions);\r\n }\r\n return bulkActions;\r\n };\r\n\r\n const renderCardComponent = (row: Row<T>) => {\r\n if (typeof cardComponent === \"function\") {\r\n return cardComponent({ row });\r\n }\r\n return cardComponent;\r\n };\r\n\r\n const renderRightTop = () => {\r\n if (typeof rightTop === \"function\") {\r\n return rightTop(actions);\r\n }\r\n return rightTop;\r\n };\r\n\r\n const renderLeftTop = () => {\r\n if (typeof leftTop === \"function\") {\r\n return leftTop(actions);\r\n }\r\n return leftTop;\r\n };\r\n\r\n const renderCustomPagination = () => {\r\n if (typeof customPagination === \"function\") {\r\n return customPagination(actions);\r\n }\r\n return customPagination;\r\n };\r\n const { rows: rowsModel } = table.getPaginationRowModel();\r\n\r\n const virtualItems = rowVirtualizer.getVirtualItems();\r\n const virtualRows = useMemo(() => {\r\n return virtualItems.map((virtualRow) => {\r\n const row = rowsModel[virtualRow.index] as Row<T>;\r\n if (!row) return null;\r\n return {\r\n virtualRow,\r\n row,\r\n cells: row.getVisibleCells().map((cell) => {\r\n const header = table\r\n .getHeaderGroups()\r\n .flatMap((headerGroup) => headerGroup.headers)\r\n .find((h) => h.id === cell.column.id);\r\n return { cell, header: header as Header<T, unknown> };\r\n }),\r\n };\r\n });\r\n }, [virtualItems, rowsModel, table]);\r\n\r\n useEffect(() => {\r\n if (!manualPagination) {\r\n const filteredRowCount =\r\n table.getState().columnFilters.length === 0\r\n ? rows.data.length\r\n : table.getFilteredRowModel().rows.length;\r\n const newPageCount = Math.ceil(filteredRowCount / pagination.pageSize);\r\n setRowsData((prev) => {\r\n return {\r\n ...prev,\r\n rowCount: filteredRowCount,\r\n pageCount: newPageCount,\r\n };\r\n });\r\n if (pagination.pageIndex >= newPageCount) {\r\n setPagination((prev) => ({\r\n ...prev,\r\n pageIndex: Math.max(0, newPageCount - 1),\r\n }));\r\n }\r\n } else {\r\n setRowsData((prev) => {\r\n if (\r\n prev.data === rows.data &&\r\n prev.rowCount === rows.rowCount &&\r\n prev.pageCount === rows.pageCount\r\n ) {\r\n return prev;\r\n }\r\n return rows;\r\n });\r\n }\r\n }, [\r\n columnFilters,\r\n globalFilter,\r\n pagination.pageSize,\r\n table,\r\n pagination.pageIndex,\r\n manualPagination,\r\n rows,\r\n ]);\r\n\r\n return (\r\n <div className=\"w-full\">\r\n <div className=\"rounded-2.5xl px-3 max-w-full grid relative h-full\">\r\n {bulkActions && <BulkActions>{renderBulkActions()}</BulkActions>}\r\n <div className=\"flex flex-col gap-3 transition-none overflow-auto relative\">\r\n {isLoading && <Loading />}\r\n <div\r\n className={`flex justify-between items-center ${\r\n !bulkActions && !manualSearch && !manualPagination ? \"p-3\" : \"\"\r\n }`}\r\n >\r\n <div className=\"flex items-center h-full flex-1\">\r\n {!manualSearch && (\r\n <input\r\n className=\"h-10 w-full max-w-72 rounded-lg px-3 border border-gray-300\"\r\n value={globalFilter}\r\n onChange={(e) =>\r\n table.setGlobalFilter(String(e.target.value))\r\n }\r\n placeholder=\"Ara\"\r\n />\r\n )}\r\n {leftTop && (\r\n <div className=\"flex-1 flex justify-start items-center gap-2\">\r\n {renderLeftTop()}\r\n </div>\r\n )}\r\n </div>\r\n {rightTop && (\r\n <div className=\"flex-1 flex justify-end items-center gap-2\">\r\n {renderRightTop()}\r\n </div>\r\n )}\r\n </div>\r\n <div\r\n ref={tableContainerRef}\r\n style={{\r\n minHeight: scrollable\r\n ? minHeight\r\n ? minHeight\r\n : \"70%\"\r\n : minHeight || \"auto\",\r\n maxHeight: scrollable\r\n ? `calc(100svh - ${pageOffset}rem)`\r\n : maxHeight &&\r\n Number(maxHeight) > rowVirtualizer.getTotalSize() + 48\r\n ? maxHeight\r\n : `calc(${rowVirtualizer.getTotalSize()}px + 64px)`,\r\n }}\r\n className=\"overflow-auto max-sm:flex h-screen\"\r\n >\r\n {viewMode === \"table\" && (\r\n <div\r\n className=\"caption-bottom text-sm flex-1 inline-table w-full relative\"\r\n style={{ height: `${rowVirtualizer.getTotalSize()}px` }}\r\n >\r\n <div className=\"sticky top-0 h-12 z-20 w-full bg-zinc-100 rounded-t-xl\">\r\n {table.getHeaderGroups().map((headerGroup) => (\r\n <div\r\n className=\"transition-colors hover:text-zinc-800 data-[state=selected]:bg-white flex group divide-x sticky top-0\"\r\n key={headerGroup.id}\r\n >\r\n {headerGroup.headers.map((header) => (\r\n <div\r\n key={header.id}\r\n className={cn(\r\n \"bg-gray-200 text-left align-middle font-medium text-base text-gray-700 flex items-center flex-shrink-0 last:rounded-r-lg first:rounded-l-lg last:flex-1 relative select-none min-h-12 group/head\",\r\n {\r\n \"cursor-pointer\": header.column.getCanSort(),\r\n \"flex-1\":\r\n table.getAllColumns().length - 1 ===\r\n header.index,\r\n }\r\n )}\r\n style={{\r\n width: header.getSize(),\r\n ...getCommonPinningStyles(header.column),\r\n }}\r\n >\r\n <div\r\n className=\"flex max-sm:flex-col md:gap-3 sm:items-center sm:justify-between whitespace-nowrap relative w-full px-3\"\r\n onClick={\r\n header.column.getCanSort()\r\n ? header.column.getToggleSortingHandler()\r\n : undefined\r\n }\r\n role=\"button\"\r\n tabIndex={0}\r\n >\r\n {header.isPlaceholder\r\n ? null\r\n : flexRender(\r\n header.column.columnDef.header,\r\n header.getContext()\r\n )}\r\n {(header.column.getCanFilter() ||\r\n header.column.getCanSort()) && (\r\n <div className=\"flex items-center gap-1 md:pr-5 last:pr-0\">\r\n {header.column.getCanFilter() ? (\r\n <Filter\r\n column={header.column}\r\n table={table}\r\n />\r\n ) : null}\r\n {header.column.getCanSort() && (\r\n <div className=\"flex flex-col text-zinc-800\">\r\n <ChevronDown\r\n className={cn(\r\n \"translate-y-2 translate-x-1 size-5 text-gray-800\",\r\n {\r\n \"text-green-600\":\r\n header.column.getIsSorted() ===\r\n \"asc\",\r\n }\r\n )}\r\n />\r\n <ChevronUp\r\n className={cn(\r\n \"-translate-y-2 -translate-x-1 size-5 text-gray-800\",\r\n {\r\n \"text-green-600\":\r\n header.column.getIsSorted() ===\r\n \"desc\",\r\n }\r\n )}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {header.column.getCanResize() &&\r\n header.index !==\r\n table.getAllColumns().length - 1 && (\r\n <div\r\n aria-hidden=\"true\"\r\n onMouseDown={header.getResizeHandler()}\r\n onTouchStart={header.getResizeHandler()}\r\n className={cn(\r\n \"absolute right-0 top-0 flex justify-end h-full w-2 cursor-col-resize select-none touch-none \"\r\n )}\r\n >\r\n <div\r\n className={cn(\r\n \"w-[1px] h-full bg-transparent peer-hover:bg-gray-200 group-hover/head:bg-gray-200 transition-all\",\r\n {\r\n \"bg-gray-200 hover:bg-gray-200\":\r\n header.column.getIsResizing(),\r\n }\r\n )}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n ))}\r\n </div>\r\n {!isLoading &&\r\n table.getFilteredRowModel().rows.length === 0 &&\r\n rowsData.data.length === 0 && (\r\n <>\r\n {emptyContent ? (\r\n emptyContent\r\n ) : (\r\n <div className=\"w-full bg-yellow-100 text-yellow-800 flex items-center p-2 gap-2\">\r\n <span className=\"flex items-center gap-2 sticky left-2\">\r\n <AlertTriangle />\r\n Sonuç bulunamadı\r\n </span>\r\n </div>\r\n )}\r\n </>\r\n )}\r\n\r\n {!isLoading && table.getFilteredRowModel().rows.length > 0 && (\r\n <div className=\"grid relative\">\r\n {virtualRows.map((virtualItem, index) => {\r\n if (!virtualItem) return null;\r\n const { virtualRow, row, cells } = virtualItem;\r\n return (\r\n <div\r\n className=\"will-change-transform transition-colors relative hover:text-zinc-800 data-[state=selected]:bg-white flex group\"\r\n key={row.id}\r\n style={{\r\n height: `${virtualRow.size}px`,\r\n transformStyle: \"preserve-3d\",\r\n backfaceVisibility: \"hidden\",\r\n transform: `translateY(${\r\n virtualRow.start - index * virtualRow.size\r\n }px)`,\r\n }}\r\n >\r\n {cells.map(({ cell, header }) => {\r\n return (\r\n <div\r\n key={cell.id}\r\n style={{\r\n width: cell.column.getSize(),\r\n ...getCommonPinningStyles(cell.column),\r\n }}\r\n className={cn(\r\n \"align-middle text-gray-800 flex flex-shrink-0 bg-gray-50 group-hover:bg-gray-100 group-hover:text-zinc-800 last:flex-1 text-left group/cell\",\r\n {\r\n \"flex-1\":\r\n table.getAllColumns().length - 1 ===\r\n cell.column.getIndex(),\r\n }\r\n )}\r\n >\r\n <div className=\"min-h-12 text-sm flex items-center break-all px-3 w-full\">\r\n {flexRender(\r\n cell.column.columnDef.cell,\r\n cell.getContext()\r\n )}\r\n </div>\r\n {cell.column.getCanResize() &&\r\n cell.column.getIndex() !==\r\n table.getAllColumns().length - 1 && (\r\n <div\r\n aria-hidden=\"true\"\r\n onMouseDown={header.getResizeHandler()}\r\n onTouchStart={header.getResizeHandler()}\r\n className={cn(\r\n \"absolute right-0 top-0 flex justify-end h-full w-2 cursor-col-resize select-none touch-none\"\r\n )}\r\n >\r\n <div\r\n className={cn(\r\n \"w-[1px] h-full bg-transparent peer-hover:bg-gray-800 group-hover/cell:bg-gray-800 transition-all\",\r\n {\r\n \"bg-gray-800 hover:bg-gray-800\":\r\n cell.column.getIsResizing(),\r\n }\r\n )}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n {viewMode === \"card\" && (\r\n <div className=\"p-3 w-full\">\r\n <div className=\"grid grid-cols-2 gap-5\">\r\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\r\n const row = rowsModel[virtualRow.index] as Row<T>;\r\n if (!row) return null;\r\n return renderCardComponent(row);\r\n })}\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n </div>\r\n {!manualPagination && !customPagination && table && (\r\n <Pagination\r\n rows={rowsData}\r\n table={table}\r\n pagination={pagination}\r\n setPagination={setPagination}\r\n />\r\n )}\r\n {manualPagination && customPagination && renderCustomPagination()}\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nexport { CustomTable };\r\n","import { Column } from \"@tanstack/react-table\";\r\nimport { type ClassValue, clsx } from \"clsx\";\r\nimport { CSSProperties } from \"react\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n return twMerge(clsx(inputs));\r\n}\r\n\r\nexport const getCommonPinningStyles = <T>(column: Column<T>): CSSProperties => {\r\n const isPinned = column.getIsPinned();\r\n const isLastLeftPinnedColumn =\r\n isPinned === \"left\" && column.getIsLastColumn(\"left\");\r\n const isFirstRightPinnedColumn =\r\n isPinned === \"right\" && column.getIsFirstColumn(\"right\");\r\n\r\n return {\r\n /* boxShadow: isLastLeftPinnedColumn\r\n ? \"-4px 0 4px -4px lightgray inset\"\r\n : isFirstRightPinnedColumn\r\n ? \"4px 0 4px -4px lightgray inset\"\r\n : undefined, */\r\n left: isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined,\r\n right: isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined,\r\n position: isPinned ? \"sticky\" : \"relative\",\r\n zIndex: isPinned ? 1 : 0,\r\n };\r\n};\r\n","'use client'\r\n\r\nimport { cn } from '@/lib/utils'\r\nimport { HTMLAttributes } from 'react'\r\n\r\ninterface LoadingProps extends HTMLAttributes<HTMLDivElement> {}\r\n\r\nexport const Loading = ({ ...props }: LoadingProps) => {\r\n return (\r\n <div\r\n className={cn(\r\n 'absolute top-0 left-0 w-full h-full flex items-center justify-center space-x-2 bg-white/50 backdrop-blur-sm z-30',\r\n props.className,\r\n )}\r\n {...props}\r\n >\r\n <div className='h-4 w-4 bg-zinc-800 rounded-full animate-bounce [animation-delay:-0.3s]' />\r\n <div className='h-4 w-4 bg-zinc-800 rounded-full animate-bounce [animation-delay:-0.15s]' />\r\n <div className='h-4 w-4 bg-zinc-800 rounded-full animate-bounce' />\r\n </div>\r\n )\r\n}\r\n","import { ReactNode } from 'react';\r\n\r\nconst BulkActions = ({ children }: { children: ReactNode }) => {\r\n return (\r\n <div className=\"sticky flex items-center justify-center top-16 -translate-y-1/2 z-30\">\r\n <div className=\"bg-white px-2 py-2 rounded-xl shadow-md flex items-center justify-center gap-2\">{children}</div>\r\n </div>\r\n );\r\n};\r\n\r\nexport default BulkActions;\r\n","import { PaginationState, Table } from \"@tanstack/react-table\";\r\nimport React, { Dispatch, SetStateAction, useEffect } from \"react\";\r\nimport { CustomTableProps } from \"@/lib/types/table.types\";\r\nimport { ChevronLeft, ChevronRight, List } from \"lucide-react\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { FormSelect } from \"@/components/FormComponents/FormSelect\";\r\n\r\ninterface PaginationProps<T extends Record<string, any>> {\r\n table?: Table<T>;\r\n rows: CustomTableProps<T>[\"rows\"];\r\n pagination: PaginationState;\r\n setPagination: Dispatch<SetStateAction<PaginationState>>;\r\n manualPagination?: boolean;\r\n loading?: boolean;\r\n hidePageSize?: boolean;\r\n showTotalCount?: boolean;\r\n}\r\n\r\nconst Pagination = <T extends Record<string, any>>({\r\n table,\r\n setPagination,\r\n pagination,\r\n rows,\r\n manualPagination,\r\n loading,\r\n hidePageSize,\r\n showTotalCount,\r\n}: PaginationProps<T>) => {\r\n const [lastValues, setLastValues] = React.useState<{\r\n pageSize: number;\r\n currentPage: number;\r\n totalPages: number;\r\n rowCount: number;\r\n }>({\r\n pageSize: 0,\r\n currentPage: 0,\r\n totalPages: 0,\r\n rowCount: 0,\r\n });\r\n const pageSize = table\r\n ? table.getState().pagination.pageSize\r\n : pagination.pageSize;\r\n const currentPage = table\r\n ? table.getState().pagination.pageIndex + 1\r\n : pagination.pageIndex + 1;\r\n const totalPages = table\r\n ? table.getPageCount()\r\n : Math.ceil((rows?.rowCount ?? 0) / pageSize);\r\n\r\n useEffect(() => {\r\n if (!loading && rows?.rowCount) {\r\n setLastValues({\r\n pageSize,\r\n currentPage,\r\n totalPages,\r\n rowCount: rows?.rowCount,\r\n });\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [loading, rows?.rowCount]);\r\n\r\n const renderPageNumbers = () => {\r\n const pageNumbers = [];\r\n const totalP = loading ? lastValues.totalPages : totalPages;\r\n const currentP = loading ? lastValues.currentPage : currentPage;\r\n\r\n if (totalP <= 7) {\r\n for (let i = 1; i <= totalP; i += 1) {\r\n pageNumbers.push(i);\r\n }\r\n } else {\r\n pageNumbers.push(1);\r\n\r\n if (currentP <= 4) {\r\n for (let i = 2; i <= 5; i += 1) {\r\n pageNumbers.push(i);\r\n }\r\n pageNumbers.push(\"...\");\r\n pageNumbers.push(totalP);\r\n } else if (currentP >= totalP - 3) {\r\n pageNumbers.push(\"...\");\r\n for (let i = totalP - 4; i <= totalP; i += 1) {\r\n pageNumbers.push(i);\r\n }\r\n } else {\r\n pageNumbers.push(\"...\");\r\n for (let i = currentP - 1; i <= currentP + 1; i += 1) {\r\n pageNumbers.push(i);\r\n }\r\n pageNumbers.push(\"...\");\r\n pageNumbers.push(totalP);\r\n }\r\n }\r\n\r\n return pageNumbers;\r\n };\r\n\r\n const generatePageSizes = (maxSize: number) => {\r\n const sizes: number[] = [];\r\n let current = 10;\r\n\r\n while (current <= maxSize) {\r\n if (current !== maxSize) {\r\n sizes.push(current);\r\n }\r\n\r\n if (current < 100) {\r\n current += 10;\r\n } else if (current < 1000) {\r\n current += 100;\r\n } else {\r\n current += 1000;\r\n }\r\n }\r\n return sizes;\r\n };\r\n\r\n const pageSizeOptions = generatePageSizes(rows.rowCount ?? 0).map((size) => ({\r\n value: size.toString(),\r\n label: size.toString(),\r\n }));\r\n if (!manualPagination || (rows.rowCount ?? 0) <= 1000) {\r\n pageSizeOptions.push({\r\n value: rows.rowCount?.toString() ?? \"0\",\r\n label: `Hepsini Göster (${rows.rowCount})`,\r\n });\r\n }\r\n\r\n useEffect(() => {\r\n if (\r\n manualPagination &&\r\n !loading &&\r\n pagination &&\r\n pagination?.pageIndex > totalPages - 1\r\n ) {\r\n setPagination({ ...pagination, pageIndex: 0 });\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [manualPagination, rows.pageCount, setPagination, totalPages, loading]);\r\n\r\n const getPageNumber = () => {\r\n if (manualPagination) {\r\n if (totalPages === 1) {\r\n return pageSizeOptions[pageSizeOptions.length - 1].value.toString();\r\n }\r\n const pageSizeOption = pageSizeOptions.find(\r\n (option) => option.value === pagination.pageSize.toString()\r\n );\r\n if (!pageSizeOption) {\r\n return pageSizeOptions[0].value.toString();\r\n }\r\n if (pagination.pageSize >= (rows?.rowCount ?? 0)) {\r\n return pagination.pageSize.toString();\r\n }\r\n return pagination.pageSize.toString();\r\n }\r\n if (pageSize >= rows.data.length) {\r\n return rows.data.length.toString();\r\n }\r\n return pageSize.toString();\r\n };\r\n\r\n return (\r\n <div className=\"flex items-center gap-2 select-none mt-auto sticky z-20 bottom-2 shadow border-t bg-gray-200 p-3 rounded-2xl\">\r\n <Button\r\n size=\"icon\"\r\n className=\"text-gray-800\"\r\n variant=\"ghost\"\r\n onClick={() =>\r\n table\r\n ? table.previousPage()\r\n : setPagination!((currentValue) => ({\r\n ...currentValue,\r\n pageIndex: currentValue.pageIndex - 1,\r\n }))\r\n }\r\n disabled={\r\n table ? !table.getCanPreviousPage() : pagination!.pageIndex === 0\r\n }\r\n >\r\n <ChevronLeft />\r\n </Button>\r\n\r\n <div className=\"flex items-center gap-1\">\r\n {renderPageNumbers().map((pageNum, idx) => (\r\n <React.Fragment key={idx}>\r\n {pageNum === \"...\" ? (\r\n <span className=\"px-2 h-8 w-8 flex items-center justify-center text-gray-800\">\r\n ...\r\n </span>\r\n ) : (\r\n <div>\r\n <Button\r\n variant={currentPage === pageNum ? \"default\" : \"ghost\"}\r\n className=\"min-w-[32px] h-8 flex items-center justify-center px-1\"\r\n onClick={() => {\r\n const newPage = (pageNum as number) - 1;\r\n if (table) {\r\n table.setPageIndex(newPage);\r\n } else {\r\n setPagination!((prev) => ({\r\n ...prev,\r\n pageIndex: newPage,\r\n }));\r\n }\r\n }}\r\n >\r\n {pageNum}\r\n </Button>\r\n </div>\r\n )}\r\n </React.Fragment>\r\n ))}\r\n </div>\r\n\r\n <Button\r\n size=\"icon\"\r\n className=\"text-gray-800\"\r\n variant=\"ghost\"\r\n onClick={() =>\r\n table\r\n ? table.nextPage()\r\n : setPagination!((currentValue) => ({\r\n ...currentValue,\r\n pageIndex: currentValue.pageIndex + 1,\r\n }))\r\n }\r\n disabled={\r\n table\r\n ? !table.getCanNextPage()\r\n : pagination!.pageIndex === totalPages - 1\r\n }\r\n >\r\n <ChevronRight />\r\n </Button>\r\n\r\n {!hidePageSize && (\r\n <FormSelect\r\n onChange={(e) => {\r\n if (table) {\r\n table.setPageSize(Number(e.target.value));\r\n } else {\r\n setPagination!({\r\n ...pagination!,\r\n pageSize: Number(e.target.value),\r\n });\r\n }\r\n }}\r\n value={getPageNumber()}\r\n options={pageSizeOptions}\r\n />\r\n )}\r\n\r\n {showTotalCount && (\r\n <div className=\"flex items-center gap-4 text-sm text-gray-700 bg-white p-2 rounded-md shadow-md\">\r\n <span className=\"font-semibold\">{`${currentPage} / ${\r\n loading ? lastValues.totalPages : totalPages\r\n }`}</span>\r\n <span className=\"text-gray-500 flex items-center\">\r\n <List className=\"mr-1\" />\r\n {`${\r\n table\r\n ? table.getFilteredRowModel().rows.length\r\n : rows.rowCount === 0 && loading\r\n ? lastValues.rowCount\r\n : rows.rowCount\r\n }`}\r\n </span>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\nexport { Pagination };\r\n","import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean\n }) {\n const Comp = asChild ? Slot : \"button\"\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n )\n}\n\nexport { Button, buttonVariants }\n","import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Select({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />;\n}\n\nfunction SelectGroup({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />;\n}\n\nfunction SelectValue({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />;\n}\n\nfunction SelectTrigger({\n className,\n size = \"default\",\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n size?: \"sm\" | \"default\";\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"flex h-10 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-zinc-600 focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon className=\"size-4 opacity-50\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n}\n\nfunction SelectContent({\n className,\n children,\n position = \"popper\",\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n \"relative z-[999999] max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-white text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n}\n\nfunction SelectLabel({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n );\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n {...props}\n />\n );\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n return (\n <SelectPrimitive.ScrollUpButton\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n {...props}\n >\n <ChevronUpIcon className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n );\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n return (\n <SelectPrimitive.ScrollDownButton\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n {...props}\n >\n <ChevronDownIcon className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n );\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n};\n","\"use client\";\r\n\r\nimport { ChangeEvent, ReactNode, SelectHTMLAttributes } from \"react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectGroup,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from \"@/components/ui/select\";\r\n\r\nexport interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\r\n label?: ReactNode;\r\n leftIcon?: ReactNode;\r\n rightIcon?: ReactNode;\r\n placeholder?: string;\r\n options: { value: string; label: string }[];\r\n}\r\n\r\nconst FormSelect = ({\r\n label,\r\n className,\r\n options,\r\n placeholder,\r\n ...props\r\n}: SelectProps) => {\r\n return (\r\n <div>\r\n {label && (\r\n <div className=\"text-sm font-medium text-zinc-800 mb-1 block\">\r\n {label}\r\n </div>\r\n )}\r\n <div className={cn(\"relative flex items-center\", className)}>\r\n <Select\r\n onValueChange={(value) =>\r\n props.onChange?.({\r\n target: { value },\r\n } as ChangeEvent<HTMLSelectElement>)\r\n }\r\n value={String(props.value ?? \"\")}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder={placeholder} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectGroup>\r\n {options?.map((item) => (\r\n <SelectItem key={item.value} value={item.value}>\r\n {item.label}\r\n </SelectItem>\r\n ))}\r\n </SelectGroup>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n </div>\r\n );\r\n};\r\n\r\nFormSelect.displayName = \"FormSelect\";\r\n\r\nexport { FormSelect };\r\n","import { cn } from \"@/lib/utils\";\r\nimport { Column, Table } from \"@tanstack/react-table\";\r\nimport { useState } from \"react\";\r\nimport { formatISO, isValid, parseISO } from \"date-fns\";\r\nimport {\r\n Popover,\r\n PopoverContent,\r\n PopoverTrigger,\r\n} from \"@/components/ui/popover\";\r\nimport { FormInput } from \"@/components/FormComponents/FormInput\";\r\nimport { FormSelect } from \"@/components/FormComponents/FormSelect\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport { Filter as FilterIcon } from \"lucide-react\";\r\nimport { FormCalendar } from \"@/components/FormComponents/FormCalendar\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\n\r\nconst parseDateString = (dateString: unknown): Date | undefined => {\r\n if (!dateString) return undefined;\r\n try {\r\n if (typeof dateString === \"string\") {\r\n const parsedDate = parseISO(dateString);\r\n return isValid(parsedDate) ? parsedDate : undefined;\r\n }\r\n return undefined;\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n } catch (error) {\r\n return undefined;\r\n }\r\n};\r\n\r\nexport const Filter = ({\r\n column,\r\n table,\r\n}: {\r\n column: Column<any, any>;\r\n table: Table<any>;\r\n}) => {\r\n const [open, setOpen] = useState(false);\r\n const uniqueValues = Array.from(\r\n new Set(\r\n table\r\n .getPreFilteredRowModel()\r\n .flatRows.map((row) => row.getValue(column.id))\r\n )\r\n );\r\n\r\n const columnFilterValue = column.getFilterValue();\r\n const [filteredValue, setFilteredValue] = useState<any>(columnFilterValue);\r\n const filterType = column.columnDef.meta?.filterType;\r\n\r\n const renderFilterInput = () => {\r\n if (filterType === \"date\") {\r\n return (\r\n <motion.div\r\n key=\"date-filter\"\r\n initial={{ opacity: 0, y: -10 }}\r\n animate={{ opacity: 1, y: 0 }}\r\n exit={{ opacity: 0, y: -10 }}\r\n >\r\n <FormCalendar\r\n className=\"w-full\"\r\n value={\r\n filteredValue !== undefined\r\n ? parseISO(filteredValue as string)\r\n