Compare commits
No commits in common. "88f9fdd25cd1b8459061fc3e84c76e476067fb23" and "bf134a99d5538f117b83627b077a1faa98474bf3" have entirely different histories.
88f9fdd25c
...
bf134a99d5
@ -31,7 +31,6 @@
|
||||
"clsx": "^2.1.1",
|
||||
"cookies-next": "^5.1.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"embla-carousel-autoplay": "^8.6.0",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"json-to-graphql-query": "^2.3.0",
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -71,9 +71,6 @@ importers:
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
date-fns-tz:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0(date-fns@4.1.0)
|
||||
embla-carousel-autoplay:
|
||||
specifier: ^8.6.0
|
||||
version: 8.6.0(embla-carousel@8.6.0)
|
||||
@ -1573,11 +1570,6 @@ packages:
|
||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
date-fns-tz@3.2.0:
|
||||
resolution: {integrity: sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==}
|
||||
peerDependencies:
|
||||
date-fns: ^3.0.0 || ^4.0.0
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
@ -4236,10 +4228,6 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
date-fns-tz@3.2.0(date-fns@4.1.0):
|
||||
dependencies:
|
||||
date-fns: 4.1.0
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@3.2.7:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { deleteCookie } from 'cookies-next';
|
||||
import { format } from 'date-fns';
|
||||
import { Building2, LogOut, Wallet } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@ -11,7 +12,6 @@ import {
|
||||
import { CorporateTransactionRequest } from '@/entities/corporate/model/types/corporate-transactions.type';
|
||||
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { formatDate } from '@/shared/lib/format-date';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import {
|
||||
Card,
|
||||
@ -195,7 +195,7 @@ export function CorporateDashboard() {
|
||||
renderRow={(transaction, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
{formatDate(
|
||||
{format(
|
||||
new Date(transaction.date_create),
|
||||
'dd.MM.yyyy HH:mm',
|
||||
)}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { deleteCookie } from 'cookies-next';
|
||||
import { format } from 'date-fns';
|
||||
import { ArrowUpRight, Clock, CreditCard, LogOut, User } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@ -12,7 +13,6 @@ import { BonusTransactionRequest } from '@/entities/bonus/model/types/bonus-tran
|
||||
|
||||
import Loader from '@/shared/components/loader';
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { formatDate } from '@/shared/lib/format-date';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import {
|
||||
Card,
|
||||
@ -190,7 +190,10 @@ export function CustomerDashboard() {
|
||||
renderRow={(transaction) => (
|
||||
<TableRow key={transaction.id}>
|
||||
<TableCell>
|
||||
{formatDate(transaction.date_create, 'dd.MM.yyyy HH:mm')}
|
||||
{format(
|
||||
new Date(transaction.date_create),
|
||||
'dd.MM.yyyy HH:mm',
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{transaction.station}</TableCell>
|
||||
<TableCell>{transaction.product_name}</TableCell>
|
||||
|
||||
@ -156,7 +156,7 @@ export default function LoginPage() {
|
||||
<p>
|
||||
{t('auth.loginIssues')}{' '}
|
||||
<Link
|
||||
href={`mailto:${t('auth.loginForm.contactUs.mail')}`}
|
||||
href='mailto:info@oriyo.tj'
|
||||
className='text-red-600 hover:underline'
|
||||
>
|
||||
{t('auth.contactLink')}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { formatInTimeZone } from 'date-fns-tz';
|
||||
|
||||
export const formatDate = (
|
||||
date: Date | string,
|
||||
formatStr: string = 'dd.MM.yyyy HH:mm',
|
||||
) => {
|
||||
const utcDate = new Date(date);
|
||||
return formatInTimeZone(utcDate, 'UTC', formatStr);
|
||||
};
|
||||
273
src/widgets/transactions-table.tsx
Normal file
273
src/widgets/transactions-table.tsx
Normal file
@ -0,0 +1,273 @@
|
||||
'use client';
|
||||
|
||||
import { format } from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import { CalendarIcon, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import TableLoadingOverlay from '@/shared/components/table-loading-overlay';
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import { Calendar } from '@/shared/shadcn-ui/calendar';
|
||||
import { Label } from '@/shared/shadcn-ui/label';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/shared/shadcn-ui/popover';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/shared/shadcn-ui/table';
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from './pagination';
|
||||
|
||||
export interface TransactionRequest {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface TransactionsTableProps<T> {
|
||||
data: {
|
||||
transactions: T[];
|
||||
total_pages: number;
|
||||
total_records: number;
|
||||
};
|
||||
isLoading: boolean;
|
||||
onChange: (request: TransactionRequest) => void;
|
||||
renderHeaders: () => React.ReactNode;
|
||||
renderRow: (transaction: T, index: number) => React.ReactNode;
|
||||
itemsPerPageOptions?: number[];
|
||||
}
|
||||
|
||||
export const TransactionsTable = <T,>({
|
||||
data,
|
||||
isLoading,
|
||||
onChange,
|
||||
renderHeaders,
|
||||
renderRow,
|
||||
itemsPerPageOptions = [5, 10, 20, 50],
|
||||
}: TransactionsTableProps<T>) => {
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(
|
||||
new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(itemsPerPageOptions[0]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
if (page < 1 || page > data.total_pages) return;
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5; // Maximum number of visible pages
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||
|
||||
let startPage = Math.max(1, currentPage - halfVisible);
|
||||
let endPage = Math.min(data.total_pages, currentPage + halfVisible);
|
||||
|
||||
if (currentPage <= halfVisible) {
|
||||
endPage = Math.min(data.total_pages, maxVisiblePages);
|
||||
} else if (currentPage + halfVisible >= data.total_pages) {
|
||||
startPage = Math.max(1, data.total_pages - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
const filterTransactions = () => {
|
||||
if (!startDate || !endDate) return;
|
||||
setCurrentPage(1); // Reset to the first page when applying filters
|
||||
};
|
||||
|
||||
const { t } = useTextController();
|
||||
|
||||
useEffect(() => {
|
||||
onChange({
|
||||
limit: itemsPerPage,
|
||||
page: currentPage,
|
||||
...(startDate ? { start_date: format(startDate, 'yyyy-MM-dd') } : {}),
|
||||
...(endDate ? { end_date: format(endDate, 'yyyy-MM-dd') } : {}),
|
||||
});
|
||||
}, [startDate, endDate, itemsPerPage, currentPage]);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className='relative space-y-6'>
|
||||
<TableLoadingOverlay isLoading={isLoading} />
|
||||
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
{t('corporate.transactions.title')}
|
||||
</h2>
|
||||
|
||||
<div className='flex w-full flex-col gap-4 md:w-auto md:flex-row'>
|
||||
<div className='grid gap-2 sm:grid-cols-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label htmlFor='start-date'>
|
||||
{t('corporate.transactions.dateFrom')}
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='w-full justify-start text-left font-normal'
|
||||
>
|
||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||
{startDate
|
||||
? format(startDate, 'PP', { locale: ru })
|
||||
: 'Выберите дату'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-auto p-0'>
|
||||
<Calendar
|
||||
mode='single'
|
||||
selected={startDate}
|
||||
onSelect={setStartDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
<Button variant='ghost' onClick={() => setStartDate(undefined)}>
|
||||
<X className='mr-2 h-4 w-4' />
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label htmlFor='end-date'>
|
||||
{t('corporate.transactions.dateTo')}
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='w-full justify-start text-left font-normal'
|
||||
>
|
||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||
{endDate
|
||||
? format(endDate, 'PP', { locale: ru })
|
||||
: t('corporate.transactions.selectDate')}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-auto p-0'>
|
||||
<Calendar
|
||||
mode='single'
|
||||
selected={endDate}
|
||||
onSelect={setEndDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
<Button variant='ghost' onClick={() => setEndDate(undefined)}>
|
||||
<X className='mr-2 h-4 w-4' />
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='relative rounded-md border'>
|
||||
<Table>
|
||||
<TableHeader>{renderHeaders()}</TableHeader>
|
||||
<TableBody>
|
||||
{data.transactions.length > 0 ? (
|
||||
data.transactions.map((transaction, index) =>
|
||||
renderRow(transaction, index),
|
||||
)
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
className='py-6 text-center text-gray-500'
|
||||
>
|
||||
{t('corporate.transactions.no-data')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{data.transactions.length > 0 && (
|
||||
<div className='flex flex-col items-center justify-between gap-4 sm:flex-row'>
|
||||
<div className='text-sm text-gray-500'>
|
||||
Показано {data.transactions.length} из {data.total_records} операций
|
||||
</div>
|
||||
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
className={
|
||||
currentPage === 1 ? 'pointer-events-none opacity-50' : ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{getPageNumbers().map((page, index) => (
|
||||
<PaginationItem key={index}>
|
||||
<PaginationLink
|
||||
isActive={currentPage === page}
|
||||
onClick={() => handlePageChange(page as number)}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
className={
|
||||
currentPage === data.total_pages
|
||||
? 'pointer-events-none opacity-50'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-sm text-gray-500'>
|
||||
{t('transactions.entries')}
|
||||
</span>
|
||||
<select
|
||||
className='rounded border p-1 text-sm'
|
||||
value={itemsPerPage}
|
||||
onChange={(e) => {
|
||||
setItemsPerPage(Number(e.target.value));
|
||||
setCurrentPage(1); // Reset to first page when changing items per page
|
||||
}}
|
||||
>
|
||||
{itemsPerPageOptions.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export { TransactionsTable } from "./ui/transactions-table";
|
||||
@ -1,121 +0,0 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from '../../pagination';
|
||||
|
||||
interface TablePaginationProps {
|
||||
currentPage: number;
|
||||
itemsPerPage: number;
|
||||
totalPages: number;
|
||||
totalOperations?: number;
|
||||
transactionsQuantity: number;
|
||||
itemsPerPageOptions: number[];
|
||||
onPageChange: (page: number) => void;
|
||||
onItemsPerPageChange: (e: ChangeEvent<HTMLSelectElement>) => void;
|
||||
}
|
||||
|
||||
export const TransactionsTablePagination = ({
|
||||
currentPage,
|
||||
itemsPerPage,
|
||||
totalPages,
|
||||
totalOperations = 0,
|
||||
itemsPerPageOptions,
|
||||
transactionsQuantity,
|
||||
onPageChange,
|
||||
onItemsPerPageChange,
|
||||
}: TablePaginationProps) => {
|
||||
const { t } = useTextController();
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5; // Maximum number of visible pages
|
||||
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||
|
||||
let startPage = Math.max(1, currentPage - halfVisible);
|
||||
let endPage = Math.min(totalPages, currentPage + halfVisible);
|
||||
|
||||
if (currentPage <= halfVisible) {
|
||||
endPage = Math.min(totalPages, maxVisiblePages);
|
||||
} else if (currentPage + halfVisible >= totalPages) {
|
||||
startPage = Math.max(1, totalPages - maxVisiblePages + 1);
|
||||
}
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{transactionsQuantity > 0 && (
|
||||
<div className='flex flex-col items-center justify-between gap-4 sm:flex-row'>
|
||||
<div className='text-sm text-gray-500'>
|
||||
Показано {transactionsQuantity} из {totalOperations} операций
|
||||
</div>
|
||||
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
className={
|
||||
currentPage === 1 ? 'pointer-events-none opacity-50' : ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
|
||||
{getPageNumbers().map((page, index) => (
|
||||
<PaginationItem key={index}>
|
||||
<PaginationLink
|
||||
isActive={currentPage === page}
|
||||
onClick={() => onPageChange(page as number)}
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
className={
|
||||
currentPage === totalPages
|
||||
? 'pointer-events-none opacity-50'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-sm text-gray-500'>
|
||||
{t('transactions.entries')}
|
||||
</span>
|
||||
<select
|
||||
className='rounded border p-1 text-sm'
|
||||
value={itemsPerPage}
|
||||
onChange={onItemsPerPageChange}
|
||||
>
|
||||
{itemsPerPageOptions.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,102 +0,0 @@
|
||||
import { format } from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import { CalendarIcon, X } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import { Calendar } from '@/shared/shadcn-ui/calendar';
|
||||
import { Label } from '@/shared/shadcn-ui/label';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/shared/shadcn-ui/popover';
|
||||
|
||||
interface TransactionsTableHeaderProps {
|
||||
startDate: Date | undefined;
|
||||
setStartDate: Dispatch<SetStateAction<Date | undefined>>;
|
||||
endDate: Date | undefined;
|
||||
setEndDate: Dispatch<SetStateAction<Date | undefined>>;
|
||||
}
|
||||
|
||||
export const TransactionsTableHeader = ({
|
||||
startDate,
|
||||
setStartDate,
|
||||
endDate,
|
||||
setEndDate,
|
||||
}: TransactionsTableHeaderProps) => {
|
||||
const { t } = useTextController();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
|
||||
<h2 className='text-2xl font-bold'>
|
||||
{t('corporate.transactions.title')}
|
||||
</h2>
|
||||
|
||||
<div className='flex w-full flex-col gap-4 md:w-auto md:flex-row'>
|
||||
<div className='grid gap-2 sm:grid-cols-2'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label htmlFor='start-date'>
|
||||
{t('corporate.transactions.dateFrom')}
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='w-full justify-start text-left font-normal'
|
||||
>
|
||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||
{startDate
|
||||
? format(startDate, 'PP', { locale: ru })
|
||||
: 'Выберите дату'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-auto p-0'>
|
||||
<Calendar
|
||||
mode='single'
|
||||
selected={startDate}
|
||||
onSelect={setStartDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
<Button variant='ghost' onClick={() => setStartDate(undefined)}>
|
||||
<X className='mr-2 h-4 w-4' />
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<Label htmlFor='end-date'>
|
||||
{t('corporate.transactions.dateTo')}
|
||||
</Label>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
className='w-full justify-start text-left font-normal'
|
||||
>
|
||||
<CalendarIcon className='mr-2 h-4 w-4' />
|
||||
{endDate
|
||||
? format(endDate, 'PP', { locale: ru })
|
||||
: t('corporate.transactions.selectDate')}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-auto p-0'>
|
||||
<Calendar
|
||||
mode='single'
|
||||
selected={endDate}
|
||||
onSelect={setEndDate}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
<Button variant='ghost' onClick={() => setEndDate(undefined)}>
|
||||
<X className='mr-2 h-4 w-4' />
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,123 +0,0 @@
|
||||
import { format } from 'date-fns';
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
|
||||
import TableLoadingOverlay from '@/shared/components/table-loading-overlay';
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/shared/shadcn-ui/table';
|
||||
|
||||
import { TransactionsTablePagination } from './table-pagination';
|
||||
import { TransactionsTableHeader } from './transactions-table-header';
|
||||
|
||||
export interface TransactionRequest {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface TransactionsTableProps<T> {
|
||||
data: {
|
||||
transactions: T[];
|
||||
total_pages: number;
|
||||
total_records: number;
|
||||
};
|
||||
isLoading: boolean;
|
||||
onChange: (request: TransactionRequest) => void;
|
||||
renderHeaders: () => React.ReactNode;
|
||||
renderRow: (transaction: T, index: number) => React.ReactNode;
|
||||
itemsPerPageOptions?: number[];
|
||||
}
|
||||
|
||||
export const TransactionsTable = <T,>({
|
||||
data,
|
||||
isLoading,
|
||||
onChange,
|
||||
renderHeaders,
|
||||
renderRow,
|
||||
itemsPerPageOptions = [5, 10, 20, 50],
|
||||
}: TransactionsTableProps<T>) => {
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(
|
||||
new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||
);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(itemsPerPageOptions[0]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
if (page < 1 || page > data.total_pages) return;
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleItemsPerPageChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setItemsPerPage(Number(e.target.value));
|
||||
setCurrentPage(1); // Reset to first page when changing items per page
|
||||
};
|
||||
|
||||
const filterTransactions = () => {
|
||||
if (!startDate || !endDate) return;
|
||||
setCurrentPage(1); // Reset to the first page when applying filters
|
||||
};
|
||||
|
||||
const { t } = useTextController();
|
||||
|
||||
useEffect(() => {
|
||||
onChange({
|
||||
limit: itemsPerPage,
|
||||
page: currentPage,
|
||||
...(startDate ? { start_date: format(startDate, 'yyyy-MM-dd') } : {}),
|
||||
...(endDate ? { end_date: format(endDate, 'yyyy-MM-dd') } : {}),
|
||||
});
|
||||
}, [startDate, endDate, itemsPerPage, currentPage]);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className='relative space-y-6'>
|
||||
<TableLoadingOverlay isLoading={isLoading} />
|
||||
<TransactionsTableHeader
|
||||
startDate={startDate}
|
||||
setStartDate={setStartDate}
|
||||
endDate={endDate}
|
||||
setEndDate={setEndDate}
|
||||
/>
|
||||
|
||||
<div className='relative rounded-md border'>
|
||||
<Table>
|
||||
<TableHeader>{renderHeaders()}</TableHeader>
|
||||
<TableBody>
|
||||
{data.transactions.length > 0 ? (
|
||||
data.transactions.map((transaction, index) =>
|
||||
renderRow(transaction, index),
|
||||
)
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
className='py-6 text-center text-gray-500'
|
||||
>
|
||||
{t('corporate.transactions.no-data')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<TransactionsTablePagination
|
||||
currentPage={currentPage}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
totalPages={data.total_pages}
|
||||
transactionsQuantity={data.transactions.length}
|
||||
totalOperations={data.total_records}
|
||||
onPageChange={handlePageChange}
|
||||
onItemsPerPageChange={handleItemsPerPageChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Briefcase } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Jobs } from '@/app/api-utlities/@types/index';
|
||||
|
||||
@ -17,6 +16,7 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/shared/shadcn-ui/tabs';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface VacanciesSectionProps {
|
||||
jobs: Jobs;
|
||||
@ -131,7 +131,7 @@ const Vacancy = ({ jobTitle, location, tags }: VacancyProps) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Link href={`mailto:${t('home.vacancies.vacancy.applyToMail')}`}>
|
||||
<Link href='mailto:info@oriyo.tj'>
|
||||
<Button variant='outline' size='sm'>
|
||||
{t('common.buttons.apply')}
|
||||
</Button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user