diff --git a/ServicesWeb/customer/src/components/custom/content/DPage.tsx b/ServicesWeb/customer/src/components/custom/content/DPage.tsx index 6165563..d37ff06 100644 --- a/ServicesWeb/customer/src/components/custom/content/DPage.tsx +++ b/ServicesWeb/customer/src/components/custom/content/DPage.tsx @@ -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; 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 = ({ - 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; - const userRolesColumns = [ - { header: 'Name', accessor: 'name' }, - { header: 'Email', accessor: 'email' }, - { - header: 'Role', accessor: 'role', - cell: (value: string) => ( - - {value} - - ) - } - ]; +const DPage: FC = ({ 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({ 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([]); + const [sorting, setSorting] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(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 ( -
- - - -
- ); -}; - - -// 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(); - - 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) => ( - - {info.getValue()} - - ), - footer: (info) => info.column.id, - }), - columnHelper.accessor('progress', { - header: 'Profile Progress', - cell: (info) => ( -
-
-
- ), - }), - ] as ColumnDef[]; - - // Fetch data using React Query - const fetchPeople = async (): Promise => { - // 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([]); + 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(); + const columns = [ + columnHelper.accessor('uu_id', { cell: info => info.getValue(), header: () => UUID, footer: info => info.column.id }), + columnHelper.accessor('process_name', { cell: info => info.getValue(), header: () => Name, footer: info => info.column.id }), + columnHelper.accessor('bank_date', { header: () => Bank Date, cell: info => info.getValue(), footer: info => info.column.id }), + columnHelper.accessor('currency_value', { header: () => Currency Value, cell: info => String(info.getValue()), footer: info => info.column.id }), + ] as ColumnDef[]; 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
Loading data...
; - if (error) return
Error loading data
; + if (error) return
{error}
; return ( -
+
+
-

People Data

-

TanStack React Query Table Example

+

Data Table

+

TanStack Table with API Data

-
+
+
+ fetchTableData(values as FormValues))} className="grid grid-cols-1 md:grid-cols-2 gap-4"> + ( + + Page + + field.onChange(Number(e.target.value))} /> + + + + )} + /> + ( + + Size + + { + const newSize = Number(e.target.value); + field.onChange(newSize); + }} + /> + + + + )} + /> +
+ +
+ + +
+ +
+
+ + +
+
+
+

+ Page {form.getValues().page} · + Size {form.getValues().size} · + Total {tableData.length} items +

+
+
+ + + +
+
+
+ +
{table.getHeaderGroups().map((headerGroup) => ( @@ -381,19 +258,11 @@ const TanStackTableExample: FC = () => { ))} @@ -414,76 +283,6 @@ const TanStackTableExample: FC = () => {
handleSortingChange(header.column.id)} >
- {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} - + {flexRender(header.column.columnDef.header, header.getContext())} + {{ asc: ' 🔼', desc: ' 🔽' }[header.column.getIsSorted() as string] ?? null}
-
-
- - -
-
-
-

- Showing{' '} - {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}{' '} - to{' '} - - {Math.min( - (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, - table.getRowCount() - )} - {' '} - of {table.getRowCount()} results -

-
-
- -
-
-
); }; diff --git a/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx new file mode 100644 index 0000000..44221c2 --- /dev/null +++ b/ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx @@ -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; + tableData: TableDataItem[]; + isLoading: boolean; + handleSortingChange: (columnId: string) => void; + getSortingIcon: (columnId: string) => React.ReactNode; + flexRender: typeof flexRender; + t: Translations; +} + +interface TableFormProps { + form: UseFormReturn; + 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 = { + 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 = React.memo(({ + table, + tableData, + isLoading, + handleSortingChange, + getSortingIcon, + flexRender, + t +}) => { + return ( +
+ {/* Semi-transparent loading overlay that preserves interactivity */} + {isLoading && ( +
+ {/* We don't put anything here as we already have the loading indicator in the header */} +
+ )} + + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {tableData.length === 0 && !isLoading ? ( + + + + ) : ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + )) + )} + +
{t.dataTable}
handleSortingChange(header.column.id)} + aria-sort={header.column.getIsSorted() ? (header.column.getIsSorted() === 'desc' ? 'descending' : 'ascending') : undefined} + scope="col" + > +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {getSortingIcon(header.column.id)} +
+
+ {t.noDataAvailable} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ ); +}); + +const TableForm: React.FC = ({ + form, + handleFormSubmit, + handleSelectChange, + renderPageOptions, + pageSizeOptions, + apiPagination, + handleFirstPage, + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ +
+ ( + + {t.page} + + + + )} + /> +
+
+ ( + + {t.size} + + + + )} + /> +
+
+

+ {t.page}: {apiPagination.page}{" / "} {apiPagination.totalPages} · + {t.size}: {apiPagination.pageCount}{" / "} {apiPagination.size} · + {t.total}: {apiPagination.totalCount} {t.items} +

+
+
+ + + + {/* */} +
+
+ +
+ ); +}; + +const MobilePaginationControls: React.FC = ({ + handlePreviousPage, + handleNextPage, + isPreviousDisabled, + isNextDisabled, + t +}) => { + return ( +
+
+ + +
+
+ ); +}; + +const TableHeader: React.FC = ({ title, description, isLoading, error, t }) => { + return ( + +
+
+
+

{title}

+

{description}

+
+ {/* {isLoading && } */} + {error && } +
+
+ ); +}; + +const ErrorDisplay: React.FC = ({ message }) => { + return
{message}
; +}; + +const TableCardComponentImproved: React.FC = 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(); + const columns = React.useMemo(() => [ + columnHelper.accessor('uu_id', { + cell: info => info.getValue(), + header: () => UUID, + footer: info => info.column.id + }), + columnHelper.accessor('process_name', { + cell: info => info.getValue(), + header: () => Name, + footer: info => info.column.id + }), + columnHelper.accessor('bank_date', { + header: () => Bank Date, + cell: info => info.getValue(), + footer: info => info.column.id + }), + columnHelper.accessor('currency_value', { + header: () => Currency Value, + cell: info => String(info.getValue()), + footer: info => info.column.id + }), + ], [columnHelper]) as ColumnDef[]; + 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 ? : +
+ +
+ +
+ + + + {/* Mobile pagination controls - only visible on small screens */} + + + + +
+ ); +}); + +export default TableCardComponentImproved; diff --git a/ServicesWeb/customer/src/fetchers/custom/login/login.tsx b/ServicesWeb/customer/src/fetchers/custom/login/login.tsx index c5409e0..b45e4de 100644 --- a/ServicesWeb/customer/src/fetchers/custom/login/login.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/login/login.tsx @@ -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 { diff --git a/ServicesWeb/customer/src/hooks/useTableData.ts b/ServicesWeb/customer/src/hooks/useTableData.ts new file mode 100644 index 0000000..721ea3a --- /dev/null +++ b/ServicesWeb/customer/src/hooks/useTableData.ts @@ -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 { + 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; + +export interface UseTableDataProps { + apiUrl: string; + defaultPagination?: { + page: number; + size: number; + }; + mapFormToRequestBody?: (values: TableFormValues) => any; + customFormSchema?: z.ZodType; + customFormValues?: Record; + pageField?: keyof TableFormValues; + sizeField?: keyof TableFormValues; + orderFieldField?: keyof TableFormValues; + orderTypeField?: keyof TableFormValues; + queryField?: keyof TableFormValues; + form?: UseFormReturn; +} + +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({ + resolver: zodResolver(customFormSchema), + defaultValues: customFormValues as any, + }); + // Table data state + const [tableData, setTableData] = useState([]); + const [sorting, setSorting] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [inUse, setInUse] = useState(false); + const inputTimeoutRef = useRef(null); + + // Pagination states + const [pagination, setPagination] = useState({ + pageIndex: defaultPagination.page - 1, + pageSize: defaultPagination.size, + }); + const [apiPagination, setApiPagination] = useState({ + 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); + const size = form.watch(sizeField as Path); + const orderField = form.watch(orderFieldField as Path); + const orderType = form.watch(orderTypeField as Path); + const query = form.watch(queryField as Path); + + // 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({ + 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, orderFields as any); + form.setValue(orderTypeField as Path, 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, + 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, newPage); + fetchTableData(); + }; + + const handleFirstPage = () => { + form.setValue(pageField as Path, 1); + fetchTableData(); + }; + + const handlePreviousPage = () => { + const currentPage = Number(form.getValues()[pageField]); + if (currentPage > 1) { + form.setValue(pageField as Path, currentPage - 1); + fetchTableData(); + } + }; + + const handleNextPage = () => { + const currentPage = Number(form.getValues()[pageField]); + form.setValue(pageField as Path, 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, + }; +} diff --git a/ServicesWeb/customer/src/layouts/dashboard/client.tsx b/ServicesWeb/customer/src/layouts/dashboard/client.tsx index e919db1..612de5f 100644 --- a/ServicesWeb/customer/src/layouts/dashboard/client.tsx +++ b/ServicesWeb/customer/src/layouts/dashboard/client.tsx @@ -39,9 +39,9 @@ const ClientLayout: FC = ({ activePageUrl, searchParams }) => - + onlineData={onlineData} onlineLoading={onlineLoading} onlineError={onlineError} refreshOnline={refreshOnline} updateOnline={updateOnline} /> */}
); diff --git a/ServicesWeb/customer/src/pages/multi/index.ts b/ServicesWeb/customer/src/pages/multi/index.ts index edc7542..174abac 100644 --- a/ServicesWeb/customer/src/pages/multi/index.ts +++ b/ServicesWeb/customer/src/pages/multi/index.ts @@ -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>> = { - "/dashboard": { DashboardPage: DPage }, + "/dashboard": { DashboardPage: TableCardComponentImproved }, + "/build": { DashboardPage: DPage }, + "/build/create": { DashboardPage: DPage }, + "/build/update": { DashboardPage: DPage }, }; export { pageIndexMulti }; diff --git a/ServicesWeb/customer/src/validations/mutual/table/validations.ts b/ServicesWeb/customer/src/validations/mutual/table/validations.ts new file mode 100644 index 0000000..838bb6e --- /dev/null +++ b/ServicesWeb/customer/src/validations/mutual/table/validations.ts @@ -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; + 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, +};