updated table look
This commit is contained in:
parent
dda9b1bb36
commit
8022f5e725
|
|
@ -1,11 +1,5 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
import { FC, useState, useEffect } from 'react';
|
||||||
import { FC, useState } from 'react';
|
|
||||||
import {
|
|
||||||
useQuery,
|
|
||||||
QueryClient,
|
|
||||||
QueryClientProvider,
|
|
||||||
} from '@tanstack/react-query';
|
|
||||||
import {
|
import {
|
||||||
flexRender,
|
flexRender,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
|
|
@ -15,14 +9,18 @@ import {
|
||||||
SortingState,
|
SortingState,
|
||||||
createColumnHelper,
|
createColumnHelper,
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
Row,
|
|
||||||
Cell,
|
|
||||||
Header,
|
|
||||||
Table,
|
|
||||||
} from '@tanstack/react-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 {
|
interface DashboardPageProps {
|
||||||
|
|
||||||
searchParams: Record<string, any>;
|
searchParams: Record<string, any>;
|
||||||
activePageUrl?: string;
|
activePageUrl?: string;
|
||||||
|
|
||||||
|
|
@ -39,340 +37,219 @@ interface DashboardPageProps {
|
||||||
updateOnline?: (data: any) => void;
|
updateOnline?: (data: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the component with QueryClientProvider
|
const formSchema = z.object({
|
||||||
const queryClient = new QueryClient();
|
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> = ({
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const userRolesColumns = [
|
const DPage: FC<DashboardPageProps> = ({ searchParams, activePageUrl, userData, userLoading, userError, refreshUser, updateUser, onlineData, onlineLoading, onlineError, refreshOnline, updateOnline }) => {
|
||||||
{ 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 userRolesData = [
|
const form = useForm<FormValues>({ resolver: zodResolver(formSchema), defaultValues: { page: 1, size: 10, orderField: [], orderType: [], query: {} } });
|
||||||
{ 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 activitiesColumns = [
|
const [tableData, setTableData] = useState<any[]>([]);
|
||||||
{ header: 'Name', accessor: 'name' },
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
{ header: 'Date', accessor: 'date' },
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
{ header: 'Time', accessor: 'time' }
|
const [error, setError] = useState<string | null>(null);
|
||||||
];
|
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: form.getValues().size });
|
||||||
|
|
||||||
const activitiesData = [
|
useEffect(() => { setPagination({ pageIndex: 0, pageSize: form.getValues().size }) }, [form.getValues().size]);
|
||||||
{ name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' },
|
useEffect(() => { fetchTableData(form.getValues()) }, []);
|
||||||
{ 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' }
|
|
||||||
];
|
|
||||||
|
|
||||||
const earningsData = [
|
const fetchTableData = async (values: FormValues) => {
|
||||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
|
setIsLoading(true); setError(null);
|
||||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
|
try {
|
||||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' },
|
const result = await apiPostFetcher({ url: `${API_BASE_URL}/test`, body: values, isNoCache: true });
|
||||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' },
|
if (result?.data?.data) {
|
||||||
{ service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' }
|
setTableData(result.data.data); setPagination({ pageIndex: values.page - 1, pageSize: values.size });
|
||||||
];
|
} else { setTableData([]); if (result?.data?.message) { setError(result.data.message) } }
|
||||||
|
} catch (error) {
|
||||||
return (
|
console.error('Error fetching data:', error); setTableData([]); setError('Failed to fetch data');
|
||||||
<div className='p-6'>
|
} finally { setIsLoading(false) }
|
||||||
<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 { data = [], isLoading, error } = useQuery({
|
const handleSortingChange = (columnId: string) => {
|
||||||
queryKey: ['people'],
|
const currentSort = sorting[0];
|
||||||
queryFn: fetchPeople,
|
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({
|
const table = useReactTable({
|
||||||
data,
|
data: tableData,
|
||||||
columns,
|
columns,
|
||||||
state: {
|
state: { sorting, pagination },
|
||||||
sorting,
|
|
||||||
},
|
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
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 (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 (
|
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">
|
<div className="p-4 border-b border-gray-200">
|
||||||
<h2 className="text-lg font-semibold text-gray-800">People Data</h2>
|
<h2 className="text-lg font-semibold text-gray-800">Data Table</h2>
|
||||||
<p className="text-sm text-gray-500">TanStack React Query Table Example</p>
|
<p className="text-sm text-gray-500">TanStack Table with API Data</p>
|
||||||
</div>
|
</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">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
|
@ -381,19 +258,11 @@ const TanStackTableExample: FC = () => {
|
||||||
<th
|
<th
|
||||||
key={header.id}
|
key={header.id}
|
||||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer"
|
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">
|
<div className="flex items-center space-x-1">
|
||||||
{flexRender(
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
header.column.columnDef.header,
|
<span>{{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? null} </span>
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
asc: ' 🔼',
|
|
||||||
desc: ' 🔽',
|
|
||||||
}[header.column.getIsSorted() as string] ?? null}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|
@ -414,76 +283,6 @@ const TanStackTableExample: FC = () => {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
</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) {
|
async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
try {
|
try {
|
||||||
|
|
@ -97,22 +115,7 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) {
|
||||||
const redisKeyAccess = await nextCrypto.encrypt(redisKey);
|
const redisKeyAccess = await nextCrypto.encrypt(redisKey);
|
||||||
const usersSelection = await nextCrypto.encrypt(JSON.stringify({ selected: firstSelection, userType, redisKey }));
|
const usersSelection = await nextCrypto.encrypt(JSON.stringify({ selected: firstSelection, userType, redisKey }));
|
||||||
|
|
||||||
cookieStore.set({
|
await setLoginCookies(cookieStore, accessToken, redisKeyAccess, usersSelection);
|
||||||
name: "eys-zzz",
|
|
||||||
value: accessToken,
|
|
||||||
...cookieObject,
|
|
||||||
});
|
|
||||||
cookieStore.set({
|
|
||||||
name: "eys-yyy",
|
|
||||||
value: redisKeyAccess,
|
|
||||||
...cookieObject,
|
|
||||||
});
|
|
||||||
cookieStore.set({
|
|
||||||
name: "eys-sel",
|
|
||||||
value: usersSelection,
|
|
||||||
...cookieObject,
|
|
||||||
});
|
|
||||||
|
|
||||||
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
|
await initRedis(loginRespone, firstSelection, accessToken, redisKey);
|
||||||
|
|
||||||
try {
|
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}
|
<ContentComponent activePageUrl={activePageUrl} mode={mode} searchParams={searchParams}
|
||||||
userData={userData} userLoading={userLoading} userError={userError} refreshUser={refreshUser} updateUser={updateUser}
|
userData={userData} userLoading={userLoading} userError={userError} refreshUser={refreshUser} updateUser={updateUser}
|
||||||
onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} />
|
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}
|
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>
|
</div>
|
||||||
</ClientProviders>
|
</ClientProviders>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
|
// import { DashboardPage } from "@/components/custom/content/DashboardPage";
|
||||||
import { DPage } from "@/components/custom/content/DPage";
|
import { DPage } from "@/components/custom/content/DPage";
|
||||||
|
import TableCardComponentImproved from "@/components/custom/content/TableCardComponentImproved";
|
||||||
|
|
||||||
const pageIndexMulti: Record<string, Record<string, React.FC<any>>> = {
|
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 };
|
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