Skip to content

Commit 1e5dea2

Browse files
committed
fix: TModel type definition
1 parent 6eb4a22 commit 1e5dea2

File tree

7 files changed

+139
-116
lines changed

7 files changed

+139
-116
lines changed

resources/js/components/app/data-table/columns.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,35 @@ import { Action, Column } from './types';
66
/**
77
* It creates actions column for data table
88
*/
9-
export function createActionsColumn<T>(
10-
getActions: (row: T) => Action<T>[],
11-
): Column<T> {
9+
export function createActionsColumn<TModel>(
10+
getActions: (row: TModel) => Action<TModel>[],
11+
): Column<TModel> {
1212
return {
1313
field: '__actions__' as any,
1414
header: 'Actions',
15-
render: (row: T) => <ActionsCell row={row} getActions={getActions} />,
15+
render: (row: TModel) => (
16+
<ActionsCell row={row} getActions={getActions} />
17+
),
1618
};
1719
}
1820

1921
/**
2022
* It creates actions column for data table
2123
*/
22-
export function createBooleanColumn<T>({
24+
export function createBooleanColumn<TModel>({
2325
field,
2426
header,
2527
beforeRender,
2628
}: {
27-
field: Column<T>['field'];
28-
header: Column<T>['header'];
29-
beforeRender?: Column<T>['beforeRender'];
30-
}): Column<T> {
29+
field: Column<TModel>['field'];
30+
header: Column<TModel>['header'];
31+
beforeRender?: Column<TModel>['beforeRender'];
32+
}): Column<TModel> {
3133
return {
3234
field,
3335
header,
3436
beforeRender,
35-
render: (row: T) => {
37+
render: (row: TModel) => {
3638
let value = getFieldValue(row, field);
3739

3840
if (typeof value !== 'boolean') {

resources/js/components/app/data-table/data-table.tsx

Lines changed: 114 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import {
1010
} from '@/components/shadcn/table';
1111
import { cn } from '@/lib/utils';
1212
import { Link } from '@inertiajs/react';
13-
import { PlusIcon } from 'lucide-react';
13+
import { Loader2, PlusIcon } from 'lucide-react';
1414
import { getFieldValue, renderCellContent } from './datatable';
1515
import { DatatableProvider } from './datatable-context';
1616
import DatatablePagination from './partials/pagination';
1717
import { DatatableOptions, DataTableProps } from './types';
1818
import { useDatatable } from './use-datatable';
1919

20-
export function DataTable<T extends object>({
20+
export function DataTable<TModel extends object>({
2121
url,
2222
columns,
2323
currentPage,
2424
createUrl,
2525
uniqueKey,
26-
}: DataTableProps<T>) {
26+
}: DataTableProps<TModel>) {
2727
const options: DatatableOptions = {
2828
currentPage: currentPage || 1,
2929
pageSize: 10,
@@ -33,102 +33,123 @@ export function DataTable<T extends object>({
3333
hasPaginator: true,
3434
};
3535

36-
const { result, data, loading, refresh, nextPage, prevPage, goToPage } =
37-
useDatatable<T>(url, options);
36+
const { result, data, refresh, loading, nextPage, prevPage, goToPage } =
37+
useDatatable<TModel>(url, options);
38+
39+
const renderEmptyState = () => (
40+
<TableRow>
41+
<TableCell
42+
colSpan={columns.length}
43+
className="bg-background py-16 text-center text-muted-foreground"
44+
>
45+
<div className="flex flex-col items-center gap-3">
46+
<h3 className="text-xl font-semibold">No records found</h3>
47+
<p className="text-sm text-muted-foreground">
48+
Try adjusting your filters or add a new record.
49+
</p>
50+
{createUrl && (
51+
<Link
52+
href={createUrl}
53+
className={cn(
54+
buttonVariants({
55+
variant: 'secondary',
56+
size: 'sm',
57+
}),
58+
)}
59+
>
60+
<PlusIcon className="mr-1 size-4" />
61+
Add New
62+
</Link>
63+
)}
64+
</div>
65+
</TableCell>
66+
</TableRow>
67+
);
68+
69+
const renderLoadingState = () => (
70+
<TableRow>
71+
<TableCell
72+
colSpan={columns.length}
73+
className="bg-background py-16 text-center text-muted-foreground"
74+
>
75+
<div className="flex flex-col items-center gap-3">
76+
<Loader2 className="size-6 animate-spin text-primary" />
77+
<p>Fetching data...</p>
78+
</div>
79+
</TableCell>
80+
</TableRow>
81+
);
3882

3983
return (
4084
<DatatableProvider value={{ refresh }}>
41-
<Table>
42-
<TableHeader>
43-
<TableRow className="bg-slate-100 dark:bg-slate-950">
44-
{columns.map((column, index) => (
45-
<TableHead key={`${column.field}_${index}`}>
46-
{column.header}
47-
</TableHead>
48-
))}
49-
</TableRow>
50-
</TableHeader>
51-
{loading && data.length === 0 ? (
52-
<TableBody>
53-
<TableRow className="text-center">
54-
<TableCell
55-
colSpan={columns.length}
56-
className="py-16 text-center"
57-
>
58-
Fetching data...
59-
</TableCell>
60-
</TableRow>
61-
</TableBody>
62-
) : data.length === 0 ? (
63-
<TableBody>
64-
<TableRow className="text-center">
65-
<TableCell
66-
colSpan={columns.length}
67-
className="space-y-2 bg-background py-16 text-center hover:bg-background"
68-
>
69-
<h3 className="text-2xl font-semibold">
70-
Oops!
71-
</h3>
72-
<p>There is no data</p>
73-
{createUrl && (
74-
<Link
75-
href={createUrl}
76-
className={cn(
77-
buttonVariants({
78-
variant: 'secondary',
79-
size: 'sm',
80-
}),
81-
)}
85+
<div className="overflow-hidden rounded-lg border bg-card shadow-sm">
86+
<div className="overflow-x-auto">
87+
<Table>
88+
<TableHeader>
89+
<TableRow className="bg-muted/30">
90+
{columns.map((column, index) => (
91+
<TableHead
92+
key={`${column.field}_${index}`}
93+
className="px-4 py-3 text-left text-sm font-semibold whitespace-nowrap text-muted-foreground"
8294
>
83-
<PlusIcon className="size-4" />
84-
<span>Add New</span>
85-
</Link>
86-
)}
87-
</TableCell>
88-
</TableRow>
89-
</TableBody>
90-
) : (
91-
<TableBody>
92-
{data.map((row) => (
93-
<TableRow
94-
key={getFieldValue(row, uniqueKey ?? 'id')}
95-
>
96-
{columns.map((column, index) => {
97-
return (
98-
<TableCell
99-
key={`${column.field}_${index}`}
100-
className={cn('font-medium')}
101-
>
102-
{renderCellContent(row, column)}
103-
</TableCell>
104-
);
105-
})}
95+
{column.header}
96+
</TableHead>
97+
))}
10698
</TableRow>
107-
))}
108-
</TableBody>
109-
)}
99+
</TableHeader>
100+
101+
<TableBody>
102+
{loading && data.length === 0
103+
? renderLoadingState()
104+
: data.length === 0
105+
? renderEmptyState()
106+
: data.map((row, i) => (
107+
<TableRow
108+
key={
109+
getFieldValue(
110+
row,
111+
uniqueKey ?? 'id',
112+
) ?? i
113+
}
114+
className="border-b transition-colors last:border-0 hover:bg-muted/50"
115+
>
116+
{columns.map((column, index) => (
117+
<TableCell
118+
key={`${column.field}_${index}`}
119+
className="px-4 py-3 text-sm"
120+
>
121+
{renderCellContent(
122+
row,
123+
column,
124+
)}
125+
</TableCell>
126+
))}
127+
</TableRow>
128+
))}
129+
</TableBody>
110130

111-
<TableFooter>
112-
<TableRow className="bg-background hover:bg-background">
113-
<TableCell colSpan={columns.length}>
114-
<div className="flex items-center justify-between">
115-
<p className="text-sm">
116-
Showing {result.meta.from} -{' '}
117-
{result.meta.to} of {result.meta.total}
118-
</p>
119-
<div>
120-
<DatatablePagination
121-
meta={result.meta}
122-
onNext={nextPage}
123-
onGotoPage={goToPage}
124-
onPrev={prevPage}
125-
/>
126-
</div>
127-
</div>
128-
</TableCell>
129-
</TableRow>
130-
</TableFooter>
131-
</Table>
131+
<TableFooter>
132+
<TableRow className="bg-muted/20">
133+
<TableCell colSpan={columns.length}>
134+
<div className="flex flex-col items-center justify-between gap-3 px-2 text-sm sm:flex-row">
135+
<p className="text-muted-foreground">
136+
Showing {result.meta.from}
137+
{result.meta.to} of{' '}
138+
{result.meta.total} results
139+
</p>
140+
<DatatablePagination
141+
meta={result.meta}
142+
onNext={nextPage}
143+
onGotoPage={goToPage}
144+
onPrev={prevPage}
145+
/>
146+
</div>
147+
</TableCell>
148+
</TableRow>
149+
</TableFooter>
150+
</Table>
151+
</div>
152+
</div>
132153
</DatatableProvider>
133154
);
134155
}

resources/js/components/app/data-table/datatable.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { cva } from 'class-variance-authority';
2-
import { Column, DatatablePaginatedData } from './types';
2+
import { Column, LengthAwarePaginator } from './types';
33

44
export function defaultPaginatedData<TModel>(
55
url?: string | null,
6-
): DatatablePaginatedData<TModel> {
6+
): LengthAwarePaginator<TModel> {
77
return {
88
data: [],
99
links: {

resources/js/components/app/data-table/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ export type {
33
Column,
44
DataTableProps,
55
DatatableOptions,
6-
DatatablePaginatedData,
6+
LengthAwarePaginator,
77
} from './types';

resources/js/components/app/data-table/partials/action-cell.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { actionVariants } from '../datatable';
44
import { useDatatableContext } from '../datatable-context';
55
import { Action } from '../types';
66

7-
export default function ActionsCell<T>({
7+
export default function ActionsCell<TModel>({
88
row,
99
getActions,
1010
}: {
11-
row: T;
12-
getActions: (row: T) => Action<T>[];
11+
row: TModel;
12+
getActions: (row: TModel) => Action<TModel>[];
1313
}) {
1414
const { refresh } = useDatatableContext();
1515
const actions = getActions(row);

resources/js/components/app/data-table/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface Column<TModel> {
2323
header: string;
2424
sortable?: boolean;
2525
beforeRender?: (columnData: any) => any;
26-
render?: (row: T) => React.ReactNode;
26+
render?: (row: TModel) => React.ReactNode;
2727
}
2828

2929
export interface DatatableOptions {
@@ -35,7 +35,7 @@ export interface DatatableOptions {
3535
hasPaginator?: boolean;
3636
}
3737

38-
export interface DatatablePaginatedData<TModel> {
38+
export interface LengthAwarePaginator<TModel> {
3939
data?: TModel[];
4040
links: {
4141
first: string;

resources/js/components/app/data-table/use-datatable.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defaultPaginatedData } from '@/components/app/data-table/datatable';
22
import axios from 'axios';
33
import { useEffect, useState } from 'react';
4-
import { DatatableOptions, DatatablePaginatedData } from './types';
4+
import { DatatableOptions, LengthAwarePaginator } from './types';
55

66
const defaultOptions: DatatableOptions = {
77
currentPage: 1,
@@ -12,17 +12,17 @@ const defaultOptions: DatatableOptions = {
1212
hasPaginator: true,
1313
};
1414

15-
export function useDatatable<T>(
15+
export function useDatatable<TModel>(
1616
url?: string | null,
1717
options: DatatableOptions = defaultOptions,
1818
) {
1919
const { pageSize, sortBy, sortDirection, filters, currentPage } = options;
2020

21-
const [result, setResult] = useState<DatatablePaginatedData<T>>(
22-
defaultPaginatedData<T>(url),
21+
const [result, setResult] = useState<LengthAwarePaginator<TModel>>(
22+
defaultPaginatedData<TModel>(url),
2323
);
2424

25-
const [data, setData] = useState<T[]>([]);
25+
const [data, setData] = useState<TModel[]>([]);
2626
const [loading, setLoading] = useState<boolean>(false);
2727

2828
const [page, setPage] = useState<number>(currentPage);

0 commit comments

Comments
 (0)