444 lines
14 KiB
TypeScript
444 lines
14 KiB
TypeScript
'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;
|