feat: make corporate-dashboard page
This commit is contained in:
parent
a7fce902c3
commit
b3fc5e765d
@ -0,0 +1,308 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { format, subMonths } from 'date-fns';
|
||||||
|
import { ru } from 'date-fns/locale';
|
||||||
|
import { Building2, CalendarIcon, LogOut, Wallet } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
|
import { Calendar } from '@/shared/shadcn-ui/calendar';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/shared/shadcn-ui/card';
|
||||||
|
import { Label } from '@/shared/shadcn-ui/label';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/shared/shadcn-ui/popover';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/shared/shadcn-ui/table';
|
||||||
|
|
||||||
|
// import { CardsList } from '@/widgets/cards-list';
|
||||||
|
|
||||||
|
// Sample company data
|
||||||
|
const companyData = {
|
||||||
|
companyName: 'ООО «ТаджикТранс»',
|
||||||
|
numberOfCards: 12,
|
||||||
|
fund: 25000,
|
||||||
|
overdraft: 5000,
|
||||||
|
totalFund: 30000,
|
||||||
|
registrationDate: '10.03.2019',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sample transaction data
|
||||||
|
const generateTransactions = () => {
|
||||||
|
const stations = [
|
||||||
|
'АЗС Душанбе-Центр',
|
||||||
|
'АЗС Душанбе-Запад',
|
||||||
|
'АЗС Душанбе-Восток',
|
||||||
|
'АЗС Худжанд',
|
||||||
|
'АЗС Куляб',
|
||||||
|
];
|
||||||
|
|
||||||
|
const products = [
|
||||||
|
{ name: 'ДТ', price: 8.5 },
|
||||||
|
{ name: 'АИ-92', price: 9.2 },
|
||||||
|
{ name: 'АИ-95', price: 10.5 },
|
||||||
|
{ name: 'Z-100 Power', price: 11.8 },
|
||||||
|
{ name: 'Пропан', price: 6.3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const transactions = [];
|
||||||
|
|
||||||
|
// Generate 50 random transactions over the last 6 months
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
const date = subMonths(new Date(), Math.random() * 6);
|
||||||
|
const station = stations[Math.floor(Math.random() * stations.length)];
|
||||||
|
const product = products[Math.floor(Math.random() * products.length)];
|
||||||
|
const quantity = Math.floor(Math.random() * 40) + 10; // 10-50 liters
|
||||||
|
const cost = product.price;
|
||||||
|
const total = quantity * cost;
|
||||||
|
|
||||||
|
transactions.push({
|
||||||
|
id: i + 1,
|
||||||
|
date,
|
||||||
|
station,
|
||||||
|
product: product.name,
|
||||||
|
quantity,
|
||||||
|
cost,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by date (newest first)
|
||||||
|
return transactions.sort((a, b) => b.date.getTime() - a.date.getTime());
|
||||||
|
};
|
||||||
|
|
||||||
|
const transactions = generateTransactions();
|
||||||
|
|
||||||
|
export default function CorporateDashboard() {
|
||||||
|
const [startDate, setStartDate] = useState<Date | undefined>(
|
||||||
|
subMonths(new Date(), 1),
|
||||||
|
);
|
||||||
|
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
|
||||||
|
const [filteredTransactions, setFilteredTransactions] =
|
||||||
|
useState(transactions);
|
||||||
|
|
||||||
|
// Filter transactions by date range
|
||||||
|
const filterTransactions = () => {
|
||||||
|
if (!startDate || !endDate) return;
|
||||||
|
|
||||||
|
const filtered = transactions.filter((transaction) => {
|
||||||
|
const transactionDate = new Date(transaction.date);
|
||||||
|
return transactionDate >= startDate && transactionDate <= endDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
setFilteredTransactions(filtered);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex min-h-screen flex-col'>
|
||||||
|
<main className='flex-1 py-10'>
|
||||||
|
<div className='container mx-auto max-w-6xl'>
|
||||||
|
<div className='mb-8 flex items-center justify-between'>
|
||||||
|
<h1 className='text-3xl font-bold'>Корпоративный кабинет</h1>
|
||||||
|
<Button variant='outline' className='gap-2'>
|
||||||
|
<LogOut className='h-4 w-4' />
|
||||||
|
Выйти
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
|
||||||
|
{/* Company Card */}
|
||||||
|
<Card className='md:col-span-2'>
|
||||||
|
<CardHeader className='pb-2'>
|
||||||
|
<CardTitle className='flex items-center gap-2'>
|
||||||
|
<Building2 className='h-5 w-5 text-red-600' />
|
||||||
|
Информация о компании
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className='grid gap-6 md:grid-cols-2'>
|
||||||
|
<div>
|
||||||
|
<div className='mb-4 space-y-1'>
|
||||||
|
<p className='text-sm text-gray-500'>Название компании</p>
|
||||||
|
<p className='font-medium'>{companyData.companyName}</p>
|
||||||
|
</div>
|
||||||
|
<div className='mb-4 space-y-1'>
|
||||||
|
<p className='text-sm text-gray-500'>Количество карт</p>
|
||||||
|
<p className='font-medium'>{companyData.numberOfCards}</p>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-1'>
|
||||||
|
<p className='text-sm text-gray-500'>Дата регистрации</p>
|
||||||
|
<p className='font-medium'>
|
||||||
|
{companyData.registrationDate}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='mb-4 space-y-1'>
|
||||||
|
<p className='text-sm text-gray-500'>Фонд</p>
|
||||||
|
<p className='font-medium'>
|
||||||
|
{companyData.fund.toLocaleString()} сомони
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='mb-4 space-y-1'>
|
||||||
|
<p className='text-sm text-gray-500'>Овердрафт</p>
|
||||||
|
<p className='font-medium'>
|
||||||
|
{companyData.overdraft.toLocaleString()} сомони
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Fund Card */}
|
||||||
|
<Card className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className='flex items-center gap-2'>
|
||||||
|
<Wallet className='h-5 w-5' />
|
||||||
|
Общий фонд
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className='text-white/80'>
|
||||||
|
Доступные средства
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className='text-center'>
|
||||||
|
<p className='mb-2 text-4xl font-bold'>
|
||||||
|
{companyData.totalFund.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p className='text-white/80'>сомони</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <CardsList totalCards={companyData.numberOfCards} /> */}
|
||||||
|
|
||||||
|
{/* Transactions */}
|
||||||
|
<div className='space-y-6'>
|
||||||
|
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
|
||||||
|
<h2 className='text-2xl font-bold'>История операций</h2>
|
||||||
|
|
||||||
|
<div className='flex w-full flex-col gap-4 md:w-auto md:flex-row'>
|
||||||
|
<div className='grid grid-cols-2 gap-2'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Label htmlFor='start-date'>От</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>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Label htmlFor='end-date'>До</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 })
|
||||||
|
: 'Выберите дату'}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className='w-auto p-0'>
|
||||||
|
<Calendar
|
||||||
|
mode='single'
|
||||||
|
selected={endDate}
|
||||||
|
onSelect={setEndDate}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className='mt-auto bg-red-600 hover:bg-red-700'
|
||||||
|
onClick={filterTransactions}
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='rounded-md border'>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Дата</TableHead>
|
||||||
|
<TableHead>Станция</TableHead>
|
||||||
|
<TableHead>Продукт</TableHead>
|
||||||
|
<TableHead className='text-right'>Кол-во (л)</TableHead>
|
||||||
|
<TableHead className='text-right'>Стоимость</TableHead>
|
||||||
|
<TableHead className='text-right'>Сумма</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{filteredTransactions.length > 0 ? (
|
||||||
|
filteredTransactions.map((transaction) => (
|
||||||
|
<TableRow key={transaction.id}>
|
||||||
|
<TableCell>
|
||||||
|
{format(transaction.date, 'dd.MM.yyyy')}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{transaction.station}</TableCell>
|
||||||
|
<TableCell>{transaction.product}</TableCell>
|
||||||
|
<TableCell className='text-right'>
|
||||||
|
{transaction.quantity}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className='text-right'>
|
||||||
|
{transaction.cost.toFixed(2)} сомони
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className='text-right font-medium'>
|
||||||
|
{transaction.total.toFixed(2)} сомони
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={6}
|
||||||
|
className='py-6 text-center text-gray-500'
|
||||||
|
>
|
||||||
|
Нет операций за выбранный период
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
src/widgets/cards-list.tsx
Normal file
76
src/widgets/cards-list.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { BarChart, CreditCard, Download, FileText } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Button } from '@/shared/shadcn-ui/button';
|
||||||
|
import { Card, CardContent } from '@/shared/shadcn-ui/card';
|
||||||
|
|
||||||
|
interface CardsListProps {
|
||||||
|
totalCards: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CardsList = ({ totalCards }: CardsListProps) => {
|
||||||
|
return (
|
||||||
|
<div className='mb-10'>
|
||||||
|
<div className='mb-4 flex items-center justify-between'>
|
||||||
|
<h2 className='text-2xl font-bold'>Топливные карты</h2>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<Button variant='outline' className='gap-2'>
|
||||||
|
<Download className='h-4 w-4' />
|
||||||
|
Экспорт данных
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline' className='gap-2'>
|
||||||
|
<FileText className='h-4 w-4' />
|
||||||
|
Отчеты
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline' className='gap-2'>
|
||||||
|
<BarChart className='h-4 w-4' />
|
||||||
|
Аналитика
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
|
||||||
|
{Array.from({ length: 4 }).map((_, index) => (
|
||||||
|
<Card key={index} className='overflow-hidden'>
|
||||||
|
<CardContent className='p-0'>
|
||||||
|
<div className='bg-gradient-to-r from-gray-800 to-gray-900 p-4 text-white'>
|
||||||
|
<div className='flex items-start justify-between'>
|
||||||
|
<div>
|
||||||
|
<p className='mb-1 text-xs text-gray-300'>
|
||||||
|
Карта #{index + 1}
|
||||||
|
</p>
|
||||||
|
<p className='font-medium'>**** **** **** {1000 + index}</p>
|
||||||
|
</div>
|
||||||
|
<CreditCard className='h-6 w-6 text-gray-300' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='p-4'>
|
||||||
|
<div className='mb-2 flex items-center justify-between'>
|
||||||
|
<p className='text-sm text-gray-500'>Лимит:</p>
|
||||||
|
<p className='font-medium'>
|
||||||
|
{(5000 * (index + 1)).toLocaleString()} сомони
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<p className='text-sm text-gray-500'>Статус:</p>
|
||||||
|
<span className='inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800'>
|
||||||
|
Активна
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{/* Show more cards button */}
|
||||||
|
{totalCards > 4 && (
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
className='h-full min-h-[120px] border-dashed'
|
||||||
|
>
|
||||||
|
Показать все карты ({totalCards})
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user