production-evyos-systems-an.../ServicesWeb/customer/src/components/custom/content/TableCardComponentImproved.tsx

444 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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;