updated table look
This commit is contained in:
parent
dda9b1bb36
commit
8022f5e725
|
|
@ -1,11 +1,5 @@
|
|||
'use client';
|
||||
|
||||
import { FC, useState } from 'react';
|
||||
import {
|
||||
useQuery,
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from '@tanstack/react-query';
|
||||
import { FC, useState, useEffect } from 'react';
|
||||
import {
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
|
|
@ -15,14 +9,18 @@ import {
|
|||
SortingState,
|
||||
createColumnHelper,
|
||||
ColumnDef,
|
||||
Row,
|
||||
Cell,
|
||||
Header,
|
||||
Table,
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
import * as z from "zod";
|
||||
import { apiPostFetcher } from "@/lib/fetcher";
|
||||
import { API_BASE_URL } from "@/config/config";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/mutual/ui/form";
|
||||
import { Input } from "@/components/mutual/ui/input";
|
||||
import { Button } from "@/components/mutual/ui/button";
|
||||
|
||||
interface DashboardPageProps {
|
||||
|
||||
searchParams: Record<string, any>;
|
||||
activePageUrl?: string;
|
||||
|
||||
|
|
@ -39,340 +37,219 @@ interface DashboardPageProps {
|
|||
updateOnline?: (data: any) => void;
|
||||
}
|
||||
|
||||
// Wrap the component with QueryClientProvider
|
||||
const queryClient = new QueryClient();
|
||||
const formSchema = z.object({
|
||||
page: z.number().min(1, "Page must be at least 1"),
|
||||
size: z.number().min(1, "Size must be at least 1"),
|
||||
orderField: z.array(z.string()),
|
||||
orderType: z.array(z.string()),
|
||||
query: z.any()
|
||||
});
|
||||
|
||||
const DPage: FC<DashboardPageProps> = ({
|
||||
searchParams,
|
||||
userData,
|
||||
userLoading,
|
||||
onlineData,
|
||||
activePageUrl
|
||||
}) => {
|
||||
// Sample data for the dashboard
|
||||
const statCardsData = [
|
||||
{
|
||||
title: 'Users',
|
||||
count: '36.5k',
|
||||
icon: 'bx bx-user',
|
||||
iconColor: 'text-blue-500',
|
||||
percentage: '4.65%',
|
||||
isPositive: true
|
||||
},
|
||||
{
|
||||
title: 'Companies',
|
||||
count: '4.5k',
|
||||
icon: 'bx bx-building',
|
||||
iconColor: 'text-yellow-500',
|
||||
percentage: '1.25%',
|
||||
isPositive: false
|
||||
},
|
||||
{
|
||||
title: 'Blogs',
|
||||
count: '12.5k',
|
||||
icon: 'bx bxl-blogger',
|
||||
iconColor: 'text-green-500',
|
||||
percentage: '2.15%',
|
||||
isPositive: true
|
||||
},
|
||||
{
|
||||
title: 'Revenue',
|
||||
count: '$35.5k',
|
||||
icon: 'bx bx-dollar',
|
||||
iconColor: 'text-pink-500',
|
||||
percentage: '3.75%',
|
||||
isPositive: true
|
||||
}
|
||||
];
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
const userRolesColumns = [
|
||||
{ header: 'Name', accessor: 'name' },
|
||||
{ header: 'Email', accessor: 'email' },
|
||||
{
|
||||
header: 'Role', accessor: 'role',
|
||||
cell: (value: string) => (
|
||||
<span className={`py-1 px-2 rounded-md text-xs ${value === 'Admin' ? 'bg-blue-500/10 text-blue-500' : value === 'Editor' ? 'bg-yellow-500/10 text-yellow-500' : 'bg-emerald-500/10 text-emerald-500'}`}>
|
||||
{value}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
];
|
||||
const DPage: FC<DashboardPageProps> = ({ searchParams, activePageUrl, userData, userLoading, userError, refreshUser, updateUser, onlineData, onlineLoading, onlineError, refreshOnline, updateOnline }) => {
|
||||
|
||||
const userRolesData = [
|
||||
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
||||
{ name: 'Jane Smith', email: 'jane@example.com', role: 'Editor' },
|
||||
{ name: 'Robert Johnson', email: 'robert@example.com', role: 'Customer' },
|
||||
{ name: 'Emily Davis', email: 'emily@example.com', role: 'Customer' },
|
||||
{ name: 'Michael Brown', email: 'michael@example.com', role: 'Editor' }
|
||||
];
|
||||
const form = useForm<FormValues>({ resolver: zodResolver(formSchema), defaultValues: { page: 1, size: 10, orderField: [], orderType: [], query: {} } });
|
||||
|
||||
const activitiesColumns = [
|
||||
{ header: 'Name', accessor: 'name' },
|
||||
{ header: 'Date', accessor: 'date' },
|
||||
{ header: 'Time', accessor: 'time' }
|
||||
];
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: form.getValues().size });
|
||||
|
||||
const activitiesData = [
|
||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
|
||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
|
||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
|
||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
|
||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }
|
||||
];
|
||||
useEffect(() => { setPagination({ pageIndex: 0, pageSize: form.getValues().size }) }, [form.getValues().size]);
|
||||
useEffect(() => { fetchTableData(form.getValues()) }, []);
|
||||
|
||||
const earningsData = [
|
||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
|
||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
|
||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
|
||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
|
||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' }
|
||||
];
|
||||
|
||||
return (
|
||||
<div className='p-6'>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TanStackTableExample />
|
||||
</QueryClientProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// TanStack Table Example component
|
||||
|
||||
type Person = {
|
||||
id: number
|
||||
firstName: string
|
||||
lastName: string
|
||||
age: number
|
||||
visits: number
|
||||
status: string
|
||||
progress: number
|
||||
}
|
||||
|
||||
const TanStackTableExample: FC = () => {
|
||||
// Column definitions using columnHelper
|
||||
const columnHelper = createColumnHelper<Person>();
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor('firstName', {
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => 'First Name',
|
||||
footer: (info) => info.column.id,
|
||||
}),
|
||||
columnHelper.accessor((row) => row.lastName, {
|
||||
id: 'lastName',
|
||||
cell: (info) => info.getValue(),
|
||||
header: () => 'Last Name',
|
||||
footer: (info) => info.column.id,
|
||||
}),
|
||||
columnHelper.accessor('age', {
|
||||
header: () => 'Age',
|
||||
cell: (info) => info.renderValue(),
|
||||
footer: (info) => info.column.id,
|
||||
}),
|
||||
columnHelper.accessor('visits', {
|
||||
header: () => 'Visits',
|
||||
footer: (info) => info.column.id,
|
||||
}),
|
||||
columnHelper.accessor('status', {
|
||||
header: 'Status',
|
||||
cell: (info) => (
|
||||
<span className={`py-1 px-2 rounded-md text-xs ${info.getValue() === 'Active' ? 'bg-green-500/10 text-green-500' :
|
||||
info.getValue() === 'Pending' ? 'bg-yellow-500/10 text-yellow-500' :
|
||||
'bg-red-500/10 text-red-500'
|
||||
}`}>
|
||||
{info.getValue()}
|
||||
</span>
|
||||
),
|
||||
footer: (info) => info.column.id,
|
||||
}),
|
||||
columnHelper.accessor('progress', {
|
||||
header: 'Profile Progress',
|
||||
cell: (info) => (
|
||||
<div className="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
|
||||
<div
|
||||
className="bg-blue-600 h-2.5 rounded-full"
|
||||
style={{ width: `${info.getValue()}%` }}
|
||||
></div>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
] as ColumnDef<Person>[];
|
||||
|
||||
// Fetch data using React Query
|
||||
const fetchPeople = async (): Promise<Person[]> => {
|
||||
// In a real app, this would be an API call
|
||||
// For this example, we'll return mock data
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
age: 28,
|
||||
visits: 10,
|
||||
status: 'Active',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
age: 32,
|
||||
visits: 5,
|
||||
status: 'Pending',
|
||||
progress: 45,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
firstName: 'Robert',
|
||||
lastName: 'Johnson',
|
||||
age: 45,
|
||||
visits: 20,
|
||||
status: 'Inactive',
|
||||
progress: 30,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
firstName: 'Emily',
|
||||
lastName: 'Davis',
|
||||
age: 27,
|
||||
visits: 15,
|
||||
status: 'Active',
|
||||
progress: 95,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
firstName: 'Michael',
|
||||
lastName: 'Brown',
|
||||
age: 39,
|
||||
visits: 8,
|
||||
status: 'Pending',
|
||||
progress: 60,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
firstName: 'Sarah',
|
||||
lastName: 'Wilson',
|
||||
age: 34,
|
||||
visits: 12,
|
||||
status: 'Active',
|
||||
progress: 75,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
firstName: 'David',
|
||||
lastName: 'Miller',
|
||||
age: 41,
|
||||
visits: 7,
|
||||
status: 'Inactive',
|
||||
progress: 25,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
firstName: 'Jennifer',
|
||||
lastName: 'Taylor',
|
||||
age: 29,
|
||||
visits: 18,
|
||||
status: 'Active',
|
||||
progress: 88,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
firstName: 'James',
|
||||
lastName: 'Anderson',
|
||||
age: 36,
|
||||
visits: 9,
|
||||
status: 'Pending',
|
||||
progress: 52,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
firstName: 'Lisa',
|
||||
lastName: 'Thomas',
|
||||
age: 31,
|
||||
visits: 14,
|
||||
status: 'Active',
|
||||
progress: 70,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
firstName: 'Richard',
|
||||
lastName: 'Jackson',
|
||||
age: 47,
|
||||
visits: 6,
|
||||
status: 'Inactive',
|
||||
progress: 15,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
firstName: 'Mary',
|
||||
lastName: 'White',
|
||||
age: 25,
|
||||
visits: 22,
|
||||
status: 'Active',
|
||||
progress: 92,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
firstName: 'Thomas',
|
||||
lastName: 'Harris',
|
||||
age: 38,
|
||||
visits: 11,
|
||||
status: 'Pending',
|
||||
progress: 48,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
firstName: 'Patricia',
|
||||
lastName: 'Martin',
|
||||
age: 33,
|
||||
visits: 16,
|
||||
status: 'Active',
|
||||
progress: 83,
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
firstName: 'Charles',
|
||||
lastName: 'Thompson',
|
||||
age: 42,
|
||||
visits: 4,
|
||||
status: 'Inactive',
|
||||
progress: 22,
|
||||
},
|
||||
];
|
||||
const fetchTableData = async (values: FormValues) => {
|
||||
setIsLoading(true); setError(null);
|
||||
try {
|
||||
const result = await apiPostFetcher({ url: `${API_BASE_URL}/test`, body: values, isNoCache: true });
|
||||
if (result?.data?.data) {
|
||||
setTableData(result.data.data); setPagination({ pageIndex: values.page - 1, pageSize: values.size });
|
||||
} else { setTableData([]); if (result?.data?.message) { setError(result.data.message) } }
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error); setTableData([]); setError('Failed to fetch data');
|
||||
} finally { setIsLoading(false) }
|
||||
};
|
||||
|
||||
const { data = [], isLoading, error } = useQuery({
|
||||
queryKey: ['people'],
|
||||
queryFn: fetchPeople,
|
||||
});
|
||||
const handleSortingChange = (columnId: string) => {
|
||||
const currentSort = sorting[0];
|
||||
let newSorting: SortingState = [];
|
||||
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
if (currentSort?.id === columnId) {
|
||||
if (currentSort.desc) { newSorting = [] } else { newSorting = [{ id: columnId, desc: true }] }
|
||||
} else { newSorting = [{ id: columnId, desc: false }] }
|
||||
|
||||
setSorting(newSorting);
|
||||
|
||||
const orderFields = newSorting.length > 0 ? [newSorting[0].id] : [];
|
||||
const orderTypes = newSorting.length > 0 ? [newSorting[0].desc ? 'desc' : 'asc'] : [];
|
||||
|
||||
form.setValue('orderField', orderFields);
|
||||
form.setValue('orderType', orderTypes);
|
||||
fetchTableData({ ...form.getValues(), orderField: orderFields, orderType: orderTypes });
|
||||
};
|
||||
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
const columns = [
|
||||
columnHelper.accessor('uu_id', { cell: info => info.getValue(), header: () => <span>UUID</span>, footer: info => info.column.id }),
|
||||
columnHelper.accessor('process_name', { cell: info => info.getValue(), header: () => <span>Name</span>, footer: info => info.column.id }),
|
||||
columnHelper.accessor('bank_date', { header: () => <span>Bank Date</span>, cell: info => info.getValue(), footer: info => info.column.id }),
|
||||
columnHelper.accessor('currency_value', { header: () => <span>Currency Value</span>, cell: info => String(info.getValue()), footer: info => info.column.id }),
|
||||
] as ColumnDef<any>[];
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
data: tableData,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
state: { sorting, pagination },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: Math.ceil(tableData.length / pagination.pageSize) || 1,
|
||||
});
|
||||
|
||||
if (isLoading) return <div className="text-center py-4">Loading data...</div>;
|
||||
if (error) return <div className="text-center py-4 text-red-500">Error loading data</div>;
|
||||
if (error) return <div className="text-center py-4 text-red-500">{error}</div>;
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
||||
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-800">People Data</h2>
|
||||
<p className="text-sm text-gray-500">TanStack React Query Table Example</p>
|
||||
<h2 className="text-lg font-semibold text-gray-800">Data Table</h2>
|
||||
<p className="text-sm text-gray-500">TanStack Table with API Data</p>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit((values) => fetchTableData(values as FormValues))} className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="page"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Page</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="Page" {...field} onChange={e => field.onChange(Number(e.target.value))} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Size</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Size"
|
||||
{...field}
|
||||
onChange={e => {
|
||||
const newSize = Number(e.target.value);
|
||||
field.onChange(newSize);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="col-span-1 md:col-span-2 flex justify-end">
|
||||
<Button type="submit" disabled={isLoading}>Fetch Data</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div className="flex-1 flex justify-between sm:hidden">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const currentPage = form.getValues().page;
|
||||
if (currentPage > 1) {
|
||||
const newValues = { ...form.getValues(), page: currentPage - 1 };
|
||||
form.setValue('page', currentPage - 1);
|
||||
fetchTableData(newValues);
|
||||
}
|
||||
}}
|
||||
disabled={form.getValues().page <= 1 || isLoading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const currentPage = form.getValues().page;
|
||||
const newValues = { ...form.getValues(), page: currentPage + 1 };
|
||||
form.setValue('page', currentPage + 1);
|
||||
fetchTableData(newValues);
|
||||
}}
|
||||
disabled={isLoading || tableData.length < form.getValues().size}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Page <span className="font-medium">{form.getValues().page}</span> ·
|
||||
Size <span className="font-medium">{form.getValues().size}</span> ·
|
||||
Total <span className="font-medium">{tableData.length}</span> items
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.setValue('page', 1);
|
||||
fetchTableData({ ...form.getValues(), page: 1 });
|
||||
}}
|
||||
disabled={form.getValues().page <= 1 || isLoading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
First
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const currentPage = form.getValues().page;
|
||||
if (currentPage > 1) {
|
||||
const newValues = { ...form.getValues(), page: currentPage - 1 };
|
||||
form.setValue('page', currentPage - 1);
|
||||
fetchTableData(newValues);
|
||||
}
|
||||
}}
|
||||
disabled={form.getValues().page <= 1 || isLoading}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const currentPage = form.getValues().page;
|
||||
const newValues = { ...form.getValues(), page: currentPage + 1 };
|
||||
form.setValue('page', currentPage + 1);
|
||||
fetchTableData(newValues);
|
||||
}}
|
||||
disabled={isLoading || tableData.length < form.getValues().size}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto ">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
|
|
@ -381,19 +258,11 @@ const TanStackTableExample: FC = () => {
|
|||
<th
|
||||
key={header.id}
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
onClick={() => handleSortingChange(header.column.id)}
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
<span>
|
||||
{{
|
||||
asc: ' 🔼',
|
||||
desc: ' 🔽',
|
||||
}[header.column.getIsSorted() as string] ?? null}
|
||||
</span>
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
<span>{{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? null} </span>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
|
|
@ -414,76 +283,6 @@ const TanStackTableExample: FC = () => {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div className="flex-1 flex justify-between sm:hidden">
|
||||
<button
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Showing{' '}
|
||||
<span className="font-medium">{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}</span>{' '}
|
||||
to{' '}
|
||||
<span className="font-medium">
|
||||
{Math.min(
|
||||
(table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize,
|
||||
table.getRowCount()
|
||||
)}
|
||||
</span>{' '}
|
||||
of <span className="font-medium">{table.getRowCount()}</span> results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
||||
<button
|
||||
onClick={() => table.setPageIndex(0)}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
<span className="sr-only">First</span>
|
||||
⏮️
|
||||
</button>
|
||||
<button
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
<span className="sr-only">Previous</span>
|
||||
◀️
|
||||
</button>
|
||||
<button
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
<span className="sr-only">Next</span>
|
||||
▶️
|
||||
</button>
|
||||
<button
|
||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||
disabled={!table.getCanNextPage()}
|
||||
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50"
|
||||
>
|
||||
<span className="sr-only">Last</span>
|
||||
⏭️
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,443 @@
|
|||
'use client';
|
||||
import React from "react";
|
||||
import {
|
||||
useReactTable,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
getPaginationRowModel,
|
||||
flexRender,
|
||||
createColumnHelper,
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from "@/components/mutual/ui/form";
|
||||
import { Button } from "@/components/mutual/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/mutual/ui/select";
|
||||
import { API_BASE_URL } from "@/config/config";
|
||||
import { useTableData } from "@/hooks/useTableData";
|
||||
import { LanguageTypes } from "@/validations/mutual/language/validations";
|
||||
import {
|
||||
Translations,
|
||||
TableHeaderProps,
|
||||
LoadingSpinnerProps,
|
||||
ErrorDisplayProps,
|
||||
MobilePaginationControlsProps,
|
||||
DashboardPageProps,
|
||||
TableDataItem,
|
||||
} from "@/validations/mutual/table/validations";
|
||||
import LoadingContent from "@/components/mutual/loader/component";
|
||||
|
||||
interface DataTableProps {
|
||||
table: ReturnType<typeof useReactTable>;
|
||||
tableData: TableDataItem[];
|
||||
isLoading: boolean;
|
||||
handleSortingChange: (columnId: string) => void;
|
||||
getSortingIcon: (columnId: string) => React.ReactNode;
|
||||
flexRender: typeof flexRender;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
interface TableFormProps {
|
||||
form: UseFormReturn<any>;
|
||||
handleFormSubmit: (e: React.FormEvent) => void;
|
||||
handleSelectChange: (value: string, field: { onChange: (value: number) => void }) => void;
|
||||
renderPageOptions: () => { key: string | number; value: string; label: string }[];
|
||||
pageSizeOptions: number[];
|
||||
apiPagination: {
|
||||
size: number;
|
||||
page: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
pageCount: number;
|
||||
};
|
||||
handleFirstPage: () => void;
|
||||
handlePreviousPage: () => void;
|
||||
handleNextPage: () => void;
|
||||
isPreviousDisabled: () => boolean;
|
||||
isNextDisabled: () => boolean;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
const translations: Record<LanguageTypes, Translations> = {
|
||||
en: {
|
||||
dataTable: 'Data Table',
|
||||
tableWithApiData: 'Table with API Data',
|
||||
loading: 'Loading...',
|
||||
noDataAvailable: 'No data available',
|
||||
page: 'Page',
|
||||
size: 'Size',
|
||||
total: 'Total',
|
||||
items: 'items',
|
||||
first: 'First',
|
||||
previous: 'Previous',
|
||||
next: 'Next',
|
||||
selectPage: 'Select page',
|
||||
selectSize: 'Select size'
|
||||
},
|
||||
tr: {
|
||||
dataTable: 'Veri Tablosu',
|
||||
tableWithApiData: 'API Verili Tablo',
|
||||
loading: 'Yükleniyor...',
|
||||
noDataAvailable: 'Veri bulunamadı',
|
||||
page: 'Sayfa',
|
||||
size: 'Boyut',
|
||||
total: 'Toplam',
|
||||
items: 'öğe',
|
||||
first: 'İlk',
|
||||
previous: 'Önceki',
|
||||
next: 'Sonraki',
|
||||
selectPage: 'Sayfa seç',
|
||||
selectSize: 'Boyut seç'
|
||||
}
|
||||
};
|
||||
|
||||
const DataTable: React.FC<DataTableProps> = React.memo(({
|
||||
table,
|
||||
tableData,
|
||||
isLoading,
|
||||
handleSortingChange,
|
||||
getSortingIcon,
|
||||
flexRender,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<div className="overflow-x-auto relative">
|
||||
{/* Semi-transparent loading overlay that preserves interactivity */}
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-white bg-opacity-60 flex items-center justify-center z-10">
|
||||
{/* We don't put anything here as we already have the loading indicator in the header */}
|
||||
</div>
|
||||
)}
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<caption className="sr-only">{t.dataTable}</caption>
|
||||
<thead className="bg-gray-50">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th
|
||||
key={header.id}
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
||||
onClick={() => handleSortingChange(header.column.id)}
|
||||
aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined}
|
||||
scope="col"
|
||||
>
|
||||
<div className="flex items-center space-x-1">
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
<span>{getSortingIcon(header.column.id)}</span>
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{tableData.length === 0 && !isLoading ? (
|
||||
<tr>
|
||||
<td colSpan={table.getAllColumns().length} className="px-6 py-4 text-center text-gray-500">
|
||||
{t.noDataAvailable}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<tr key={row.id} className="hover:bg-gray-50">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td key={cell.id} className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const TableForm: React.FC<TableFormProps> = ({
|
||||
form,
|
||||
handleFormSubmit,
|
||||
handleSelectChange,
|
||||
renderPageOptions,
|
||||
pageSizeOptions,
|
||||
apiPagination,
|
||||
handleFirstPage,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
isPreviousDisabled,
|
||||
isNextDisabled,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleFormSubmit} className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div className="md:col-span-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="page"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t.page}</FormLabel>
|
||||
<Select
|
||||
value={field.value.toString()}
|
||||
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t.selectPage} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{renderPageOptions().map((option: { key: string | number; value: string; label: string }) => (
|
||||
<SelectItem key={option.key} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-1">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t.size}</FormLabel>
|
||||
<Select
|
||||
value={field.value.toString()}
|
||||
onValueChange={(value: string) => handleSelectChange(value, field)}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t.selectSize} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{pageSizeOptions.map((size: number) => (
|
||||
<SelectItem key={size} value={size.toString()}>
|
||||
{size}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-1 flex items-end">
|
||||
<p className="text-sm text-gray-700">
|
||||
{t.page}: <span className="font-medium">{apiPagination.page}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.totalPages}</span> ·
|
||||
{t.size}: <span className="font-medium">{apiPagination.pageCount}</span><span>{" / "}</span> <span className="font-medium">{apiPagination.size}</span> ·
|
||||
{t.total}: <span className="font-medium">{apiPagination.totalCount}</span> {t.items}
|
||||
</p>
|
||||
</div>
|
||||
<div className="md:col-span-1 flex items-end justify-end space-x-2">
|
||||
<Button
|
||||
onClick={handleFirstPage}
|
||||
disabled={isPreviousDisabled()}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="Go to first page"
|
||||
>{t.first}</Button>
|
||||
<Button
|
||||
onClick={handlePreviousPage}
|
||||
disabled={isPreviousDisabled()}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="Go to previous page"
|
||||
>{t.previous}</Button>
|
||||
<Button
|
||||
onClick={handleNextPage}
|
||||
disabled={isNextDisabled()}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="Go to next page"
|
||||
>{t.next}</Button>
|
||||
{/* <Button type="submit" disabled={isLoading} size="sm">Fetch</Button> */}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MobilePaginationControls: React.FC<MobilePaginationControlsProps> = ({
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
isPreviousDisabled,
|
||||
isNextDisabled,
|
||||
t
|
||||
}) => {
|
||||
return (
|
||||
<div className="px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:hidden">
|
||||
<div className="flex-1 flex justify-between">
|
||||
<Button
|
||||
onClick={handlePreviousPage}
|
||||
disabled={isPreviousDisabled()}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="Go to previous page"
|
||||
>{t.previous}</Button>
|
||||
<Button
|
||||
onClick={handleNextPage}
|
||||
disabled={isNextDisabled()}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2"
|
||||
aria-label="Go to next page"
|
||||
>{t.next}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TableHeader: React.FC<TableHeaderProps> = ({ title, description, isLoading, error, t }) => {
|
||||
return (
|
||||
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-800">{title}</h2>
|
||||
<p className="text-sm text-gray-500">{description}</p>
|
||||
</div>
|
||||
{/* {isLoading && <LoadingContent height="h-16" size="w-36 h-48" plane="h-full w-full" />} */}
|
||||
{error && <ErrorDisplay message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ message }) => {
|
||||
return <div className="text-red-500">{message}</div>;
|
||||
};
|
||||
|
||||
const TableCardComponentImproved: React.FC<DashboardPageProps> = React.memo((props) => {
|
||||
// Initialize translation with English as default
|
||||
const language = props.onlineData?.lang as LanguageTypes || 'en';
|
||||
const t = translations[language];
|
||||
|
||||
const {
|
||||
form,
|
||||
tableData,
|
||||
sorting,
|
||||
isLoading,
|
||||
error,
|
||||
pagination,
|
||||
apiPagination,
|
||||
setSorting,
|
||||
handleSortingChange,
|
||||
handleSelectChange,
|
||||
handlePageChange,
|
||||
handleFirstPage,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
getSortingIcon,
|
||||
handleFormSubmit,
|
||||
// Disabled states
|
||||
isPreviousDisabled,
|
||||
isNextDisabled,
|
||||
pageSizeOptions,
|
||||
renderPageOptions,
|
||||
} = useTableData({ apiUrl: `${API_BASE_URL}/test` });
|
||||
|
||||
const columnHelper = createColumnHelper<TableDataItem>();
|
||||
const columns = React.useMemo(() => [
|
||||
columnHelper.accessor('uu_id', {
|
||||
cell: info => info.getValue(),
|
||||
header: () => <span>UUID</span>,
|
||||
footer: info => info.column.id
|
||||
}),
|
||||
columnHelper.accessor('process_name', {
|
||||
cell: info => info.getValue(),
|
||||
header: () => <span>Name</span>,
|
||||
footer: info => info.column.id
|
||||
}),
|
||||
columnHelper.accessor('bank_date', {
|
||||
header: () => <span>Bank Date</span>,
|
||||
cell: info => info.getValue(),
|
||||
footer: info => info.column.id
|
||||
}),
|
||||
columnHelper.accessor('currency_value', {
|
||||
header: () => <span>Currency Value</span>,
|
||||
cell: info => String(info.getValue()),
|
||||
footer: info => info.column.id
|
||||
}),
|
||||
], [columnHelper]) as ColumnDef<TableDataItem>[];
|
||||
const table = useReactTable({
|
||||
data: tableData,
|
||||
columns,
|
||||
state: { sorting, pagination },
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: apiPagination.totalPages || 1,
|
||||
});
|
||||
|
||||
return (
|
||||
isLoading ? <LoadingContent height="h-48" size="w-36 h-36" plane="h-full w-full" /> :
|
||||
<div className="bg-white rounded-lg shadow-md overflow-hidden mb-20">
|
||||
|
||||
<div className="flex justify-between items-center p-4">
|
||||
<TableHeader
|
||||
title={t.dataTable}
|
||||
description={t.tableWithApiData}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
t={t}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TableForm
|
||||
form={form}
|
||||
handleFormSubmit={handleFormSubmit}
|
||||
handleSelectChange={handleSelectChange}
|
||||
renderPageOptions={renderPageOptions}
|
||||
pageSizeOptions={pageSizeOptions}
|
||||
apiPagination={apiPagination}
|
||||
handleFirstPage={handleFirstPage}
|
||||
handlePreviousPage={handlePreviousPage}
|
||||
handleNextPage={handleNextPage}
|
||||
isPreviousDisabled={isPreviousDisabled}
|
||||
isNextDisabled={isNextDisabled}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
{/* Mobile pagination controls - only visible on small screens */}
|
||||
<MobilePaginationControls
|
||||
handlePreviousPage={handlePreviousPage}
|
||||
handleNextPage={handleNextPage}
|
||||
isPreviousDisabled={isPreviousDisabled}
|
||||
isNextDisabled={isNextDisabled}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<DataTable
|
||||
table={table}
|
||||
tableData={tableData}
|
||||
isLoading={isLoading}
|
||||
handleSortingChange={handleSortingChange}
|
||||
getSortingIcon={getSortingIcon}
|
||||
flexRender={flexRender}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TableCardComponentImproved;
|
||||
|
|
@ -67,6 +67,24 @@ async function initFirstSelection(firstSelection: any, userType: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function setLoginCookies(cookieStore: any, accessToken: string, redisKeyAccess: string, usersSelection: string) {
|
||||
cookieStore.set({
|
||||
name: "eys-zzz",
|
||||
value: accessToken,
|
||||
...cookieObject,
|
||||
});
|
||||
cookieStore.set({
|
||||
name: "eys-yyy",
|
||||
value: redisKeyAccess,
|
||||
...cookieObject,
|
||||
});
|
||||
cookieStore.set({
|
||||
name: "eys-sel",
|
||||
value: usersSelection,
|
||||
...cookieObject,
|
||||
});
|
||||
}
|
||||
|
||||
async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
|
||||
const cookieStore = await cookies();
|
||||
try {
|
||||
|
|
@ -97,22 +115,7 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
|
|||
const redisKeyAccess = await nextCrypto.encrypt(redisKey);
|
||||
const usersSelection = await nextCrypto.encrypt(JSON.stringify({ selected: firstSelection, userType, redisKey }));
|
||||
|
||||
cookieStore.set({
|
||||
name: "eys-zzz",
|
||||
value: accessToken,
|
||||
...cookieObject,
|
||||
});
|
||||
cookieStore.set({
|
||||
name: "eys-yyy",
|
||||
value: redisKeyAccess,
|
||||
...cookieObject,
|
||||
});
|
||||
cookieStore.set({
|
||||
name: "eys-sel",
|
||||
value: usersSelection,
|
||||
...cookieObject,
|
||||
});
|
||||
|
||||
await setLoginCookies(cookieStore, accessToken, redisKeyAccess, usersSelection);
|
||||
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,338 @@
|
|||
import { useState, useEffect, useRef } from "react";
|
||||
import { UseFormReturn, Path, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import * as z from "zod";
|
||||
import { SortingState } from "@tanstack/react-table";
|
||||
import { apiPostFetcher } from "@/lib/fetcher";
|
||||
|
||||
// Define interfaces for API responses
|
||||
export interface ApiPagination {
|
||||
size: number;
|
||||
page: number;
|
||||
allCount: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
pageCount: number;
|
||||
orderField: string[];
|
||||
orderType: string[];
|
||||
next: boolean;
|
||||
back: boolean;
|
||||
}
|
||||
|
||||
export interface FetcherDataResponse<T> {
|
||||
success: boolean;
|
||||
data: {
|
||||
data: T[];
|
||||
pagination?: ApiPagination;
|
||||
message?: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface TablePaginationState {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
// Define the default form schema using zod
|
||||
export const defaultFormSchema = z.object({
|
||||
page: z.number().min(1),
|
||||
size: z.number().min(1),
|
||||
orderField: z.array(z.string()),
|
||||
orderType: z.array(z.string()),
|
||||
query: z.record(z.any()),
|
||||
});
|
||||
|
||||
// Default form values
|
||||
export const defaultFormValues = {
|
||||
page: 1,
|
||||
size: 10,
|
||||
orderField: [] as string[],
|
||||
orderType: [] as string[],
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Type for the form values
|
||||
export type TableFormValues = z.infer<typeof defaultFormSchema>;
|
||||
|
||||
export interface UseTableDataProps {
|
||||
apiUrl: string;
|
||||
defaultPagination?: {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
mapFormToRequestBody?: (values: TableFormValues) => any;
|
||||
customFormSchema?: z.ZodType<any, any>;
|
||||
customFormValues?: Record<string, any>;
|
||||
pageField?: keyof TableFormValues;
|
||||
sizeField?: keyof TableFormValues;
|
||||
orderFieldField?: keyof TableFormValues;
|
||||
orderTypeField?: keyof TableFormValues;
|
||||
queryField?: keyof TableFormValues;
|
||||
form?: UseFormReturn<TableFormValues>;
|
||||
}
|
||||
|
||||
export function useTableData({
|
||||
apiUrl,
|
||||
defaultPagination = { page: 1, size: 10 },
|
||||
mapFormToRequestBody,
|
||||
customFormSchema = defaultFormSchema,
|
||||
customFormValues = defaultFormValues,
|
||||
pageField = "page" as keyof TableFormValues,
|
||||
sizeField = "size" as keyof TableFormValues,
|
||||
orderFieldField = "orderField" as keyof TableFormValues,
|
||||
orderTypeField = "orderType" as keyof TableFormValues,
|
||||
queryField = "query" as keyof TableFormValues,
|
||||
form: externalForm,
|
||||
}: UseTableDataProps) {
|
||||
// Initialize the form with react-hook-form or use the provided one
|
||||
const form =
|
||||
externalForm ||
|
||||
useForm<TableFormValues>({
|
||||
resolver: zodResolver(customFormSchema),
|
||||
defaultValues: customFormValues as any,
|
||||
});
|
||||
// Table data state
|
||||
const [tableData, setTableData] = useState<any[]>([]);
|
||||
const [sorting, setSorting] = useState<SortingState>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [inUse, setInUse] = useState<boolean>(false);
|
||||
const inputTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Pagination states
|
||||
const [pagination, setPagination] = useState<TablePaginationState>({
|
||||
pageIndex: defaultPagination.page - 1,
|
||||
pageSize: defaultPagination.size,
|
||||
});
|
||||
const [apiPagination, setApiPagination] = useState<ApiPagination>({
|
||||
size: 10,
|
||||
page: 1,
|
||||
allCount: 0,
|
||||
totalCount: 0,
|
||||
totalPages: 1,
|
||||
pageCount: 10,
|
||||
orderField: [],
|
||||
orderType: [],
|
||||
next: false,
|
||||
back: false,
|
||||
});
|
||||
console.log("apiPagination", apiPagination);
|
||||
|
||||
// Watch for form value changes
|
||||
const page = form.watch(pageField as Path<TableFormValues>);
|
||||
const size = form.watch(sizeField as Path<TableFormValues>);
|
||||
const orderField = form.watch(orderFieldField as Path<TableFormValues>);
|
||||
const orderType = form.watch(orderTypeField as Path<TableFormValues>);
|
||||
const query = form.watch(queryField as Path<TableFormValues>);
|
||||
|
||||
// Update table pagination when size changes
|
||||
useEffect(() => {
|
||||
if (typeof size === "number") {
|
||||
setPagination((prev) => ({ ...prev, pageSize: size }));
|
||||
}
|
||||
}, [size]);
|
||||
|
||||
// Fetch data when any pagination parameter changes
|
||||
useEffect(() => {
|
||||
fetchTableData();
|
||||
}, [
|
||||
page,
|
||||
size,
|
||||
orderField ? JSON.stringify(orderField) : null,
|
||||
orderType ? JSON.stringify(orderType) : null,
|
||||
query ? JSON.stringify(query) : null,
|
||||
]);
|
||||
|
||||
const fetchTableData = async () => {
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
|
||||
try {
|
||||
// Get current form values
|
||||
const values = form.getValues();
|
||||
|
||||
// Prepare request body
|
||||
const requestBody = mapFormToRequestBody
|
||||
? mapFormToRequestBody(values)
|
||||
: {
|
||||
page: values[pageField],
|
||||
size: values[sizeField],
|
||||
orderField: values[orderFieldField],
|
||||
orderType: values[orderTypeField],
|
||||
query: values[queryField],
|
||||
};
|
||||
|
||||
const response = await apiPostFetcher<any>({
|
||||
url: apiUrl,
|
||||
isNoCache: true,
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
if (response.success && response.data) {
|
||||
const dataArray = Array.isArray(response.data.data)
|
||||
? response.data.data
|
||||
: [];
|
||||
setTableData(dataArray);
|
||||
setPagination({
|
||||
pageIndex: Number(values[pageField]) - 1,
|
||||
pageSize: Number(values[sizeField]),
|
||||
});
|
||||
|
||||
if (response.data.pagination) {
|
||||
setApiPagination(response.data.pagination);
|
||||
}
|
||||
} else {
|
||||
setError("Failed to fetch data");
|
||||
setTableData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setTableData([]);
|
||||
setError("Failed to fetch data");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSortingChange = (columnId: string) => {
|
||||
const sortingColumn = sorting.find((col) => col.id === columnId);
|
||||
let newSorting: SortingState = [];
|
||||
if (!sortingColumn) {
|
||||
newSorting = [{ id: columnId, desc: false }];
|
||||
} else if (!sortingColumn.desc) {
|
||||
newSorting = [{ id: columnId, desc: true }];
|
||||
}
|
||||
setSorting(newSorting);
|
||||
const orderFields = newSorting.length > 0 ? [newSorting[0].id] : [];
|
||||
const orderTypes =
|
||||
newSorting.length > 0 ? [newSorting[0].desc ? "desc" : "asc"] : [];
|
||||
form.setValue(orderFieldField as Path<TableFormValues>, orderFields as any);
|
||||
form.setValue(orderTypeField as Path<TableFormValues>, orderTypes as any);
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
// Dropdown options
|
||||
const pageSizeOptions = [10, 20, 50, 100];
|
||||
|
||||
// Function to render page options for the dropdown
|
||||
const renderPageOptions = () => {
|
||||
return Array.from(
|
||||
{ length: apiPagination.totalPages || 5 },
|
||||
(_, i) => i + 1
|
||||
).map((page) => ({
|
||||
key: page,
|
||||
value: page.toString(),
|
||||
label: page.toString(),
|
||||
}));
|
||||
};
|
||||
|
||||
// UI event handlers
|
||||
const handleSelectChange = (
|
||||
value: string,
|
||||
field: { onChange: (value: number) => void }
|
||||
) => {
|
||||
const numericValue = Number(value);
|
||||
field.onChange(numericValue);
|
||||
// Fetch data immediately when dropdown selection changes
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
const handleNumberInputChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
field: { onChange: (value: number) => void }
|
||||
) => {
|
||||
const value = Number(e.target.value);
|
||||
field.onChange(value);
|
||||
|
||||
// Set inUse to true to indicate user is typing
|
||||
setInUse(true);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (inputTimeoutRef.current) {
|
||||
clearTimeout(inputTimeoutRef.current);
|
||||
inputTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
// Set a new timeout
|
||||
inputTimeoutRef.current = setTimeout(() => {
|
||||
// After 1 second of inactivity, set inUse to false and fetch data
|
||||
setInUse(false);
|
||||
fetchTableData();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleFormSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Immediate fetch without debounce when form is submitted
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
const handlePageChange = (newPage: number) => {
|
||||
form.setValue(pageField as Path<TableFormValues>, newPage);
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
const handleFirstPage = () => {
|
||||
form.setValue(pageField as Path<TableFormValues>, 1);
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
const handlePreviousPage = () => {
|
||||
const currentPage = Number(form.getValues()[pageField]);
|
||||
if (currentPage > 1) {
|
||||
form.setValue(pageField as Path<TableFormValues>, currentPage - 1);
|
||||
fetchTableData();
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
const currentPage = Number(form.getValues()[pageField]);
|
||||
form.setValue(pageField as Path<TableFormValues>, currentPage + 1);
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
// Sorting indicator for UI
|
||||
const getSortingIcon = (columnId: string) => {
|
||||
const sortingColumn = sorting.find((col) => col.id === columnId);
|
||||
if (!sortingColumn) return null;
|
||||
return sortingColumn.desc ? " 🔽" : " 🔼";
|
||||
};
|
||||
|
||||
// Check if previous button should be disabled
|
||||
const isPreviousDisabled = () => {
|
||||
return !apiPagination.back || page <= 1;
|
||||
};
|
||||
|
||||
// Check if next button should be disabled
|
||||
const isNextDisabled = () => {
|
||||
return !apiPagination.next || page >= apiPagination.totalPages;
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
tableData,
|
||||
sorting,
|
||||
isLoading,
|
||||
error,
|
||||
pagination,
|
||||
apiPagination,
|
||||
setSorting,
|
||||
handleSortingChange,
|
||||
// UI handlers
|
||||
handleSelectChange,
|
||||
handlePageChange,
|
||||
handleFirstPage,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
getSortingIcon,
|
||||
handleFormSubmit,
|
||||
// Disabled states
|
||||
isPreviousDisabled,
|
||||
isNextDisabled,
|
||||
// Input state
|
||||
inUse,
|
||||
// Dropdown options
|
||||
pageSizeOptions,
|
||||
renderPageOptions,
|
||||
};
|
||||
}
|
||||
|
|
@ -39,9 +39,9 @@ const ClientLayout: FC<ClientLayoutProps> = ({ activePageUrl, searchParams }) =>
|
|||
<ContentComponent activePageUrl={activePageUrl} mode={mode} searchParams={searchParams}
|
||||
userData={userData} userLoading={userLoading} userError={userError} refreshUser={refreshUser} updateUser={updateUser}
|
||||
onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} />
|
||||
<FooterComponent activePageUrl={activePageUrl} searchParams={searchParams}
|
||||
{/* <FooterComponent activePageUrl={activePageUrl} searchParams={searchParams}
|
||||
configData={configData} configLoading={configLoading} configError={configError} refreshConfig={refreshConfig} updateConfig={updateConfig}
|
||||
onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} />
|
||||
onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} /> */}
|
||||
</div>
|
||||
</ClientProviders>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
|
||||
import { DPage } from "@/components/custom/content/DPage";
|
||||
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
|
||||
|
||||
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
|
||||
"/dashboard": { DashboardPage: DPage },
|
||||
"/dashboard": { DashboardPage: TableCardComponentImproved },
|
||||
"/build": { DashboardPage: DPage },
|
||||
"/build/create": { DashboardPage: DPage },
|
||||
"/build/update": { DashboardPage: DPage },
|
||||
};
|
||||
|
||||
export { pageIndexMulti };
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
interface Translations {
|
||||
dataTable: string;
|
||||
tableWithApiData: string;
|
||||
loading: string;
|
||||
noDataAvailable: string;
|
||||
page: string;
|
||||
size: string;
|
||||
total: string;
|
||||
items: string;
|
||||
first: string;
|
||||
previous: string;
|
||||
next: string;
|
||||
selectPage: string;
|
||||
selectSize: string;
|
||||
}
|
||||
|
||||
interface TableHeaderProps {
|
||||
title: string;
|
||||
description: string;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
interface ErrorDisplayProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface MobilePaginationControlsProps {
|
||||
handlePreviousPage: () => void;
|
||||
handleNextPage: () => void;
|
||||
isPreviousDisabled: () => boolean;
|
||||
isNextDisabled: () => boolean;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
interface DashboardPageProps {
|
||||
searchParams: Record<string, any>;
|
||||
activePageUrl?: string;
|
||||
|
||||
userData?: any;
|
||||
userLoading?: boolean;
|
||||
userError?: any;
|
||||
refreshUser?: () => void;
|
||||
updateUser?: (data: any) => void;
|
||||
|
||||
onlineData?: any;
|
||||
onlineLoading?: boolean;
|
||||
onlineError?: any;
|
||||
refreshOnline?: () => void;
|
||||
updateOnline?: (data: any) => void;
|
||||
}
|
||||
|
||||
interface TableDataItem {
|
||||
uu_id: string;
|
||||
process_name: string;
|
||||
bank_date: string;
|
||||
currency_value: number | string;
|
||||
[key: string]: any; // For any additional fields
|
||||
}
|
||||
|
||||
export type {
|
||||
Translations,
|
||||
TableHeaderProps,
|
||||
LoadingSpinnerProps,
|
||||
ErrorDisplayProps,
|
||||
MobilePaginationControlsProps,
|
||||
DashboardPageProps,
|
||||
TableDataItem,
|
||||
};
|
||||
Loading…
Reference in New Issue