diff --git a/package.json b/package.json
index 6ffdbf6..75dd634 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.11",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-navigation-menu": "^1.2.10",
+ "@radix-ui/react-popover": "^1.1.11",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-tabs": "^1.1.8",
@@ -24,12 +25,14 @@
"aos": "^2.3.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"lucide-react": "^0.501.0",
"next": "15.3.1",
"next-themes": "^0.4.6",
"react": "^19.0.0",
+ "react-day-picker": "8.10.1",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.1",
"react-redux": "^9.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6f6c3d0..430bf8d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@radix-ui/react-navigation-menu':
specifier: ^1.2.10
version: 1.2.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-popover':
+ specifier: ^1.1.11
+ version: 1.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-select':
specifier: ^2.2.2
version: 2.2.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -50,6 +53,9 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.1.0
embla-carousel-autoplay:
specifier: ^8.6.0
version: 8.6.0(embla-carousel@8.6.0)
@@ -68,6 +74,9 @@ importers:
react:
specifier: ^19.0.0
version: 19.1.0
+ react-day-picker:
+ specifier: 8.10.1
+ version: 8.10.1(date-fns@4.1.0)(react@19.1.0)
react-dom:
specifier: ^19.0.0
version: 19.1.0(react@19.1.0)
@@ -681,6 +690,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-popover@1.1.11':
+ resolution: {integrity: sha512-yFMfZkVA5G3GJnBgb2PxrrcLKm1ZLWXrbYVgdyTl//0TYEIHS9LJbnyz7WWcZ0qCq7hIlJZpRtxeSeIG5T5oJw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-popper@1.2.4':
resolution: {integrity: sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==}
peerDependencies:
@@ -1370,6 +1392,9 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -2261,6 +2286,12 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ react-day-picker@8.10.1:
+ resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
+ peerDependencies:
+ date-fns: ^2.28.0 || ^3.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
peerDependencies:
@@ -3121,6 +3152,29 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-popover@1.1.11(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ aria-hidden: 1.2.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-popper@1.2.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -3814,6 +3868,8 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
+ date-fns@4.1.0: {}
+
debug@3.2.7:
dependencies:
ms: 2.1.3
@@ -4782,6 +4838,11 @@ snapshots:
queue-microtask@1.2.3: {}
+ react-day-picker@8.10.1(date-fns@4.1.0)(react@19.1.0):
+ dependencies:
+ date-fns: 4.1.0
+ react: 19.1.0
+
react-dom@19.1.0(react@19.1.0):
dependencies:
react: 19.1.0
diff --git a/src/app/(dashboard)/corporate-dashboard/page.tsx b/src/app/(dashboard)/corporate-dashboard/page.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/(dashboard)/customer-dashboard/page.tsx b/src/app/(dashboard)/customer-dashboard/page.tsx
new file mode 100644
index 0000000..aa2f7ea
--- /dev/null
+++ b/src/app/(dashboard)/customer-dashboard/page.tsx
@@ -0,0 +1,110 @@
+import { ArrowUpRight, Clock, CreditCard, LogOut, User } from 'lucide-react';
+
+import { Button } from '@/shared/shadcn-ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/shared/shadcn-ui/card';
+
+import { TransactionsTable } from '@/widgets/transactions-table';
+
+// Sample customer data
+const customerData = {
+ firstName: 'Алишер',
+ lastName: 'Рахмонов',
+ passportNumber: 'A12345678',
+ bonusPoints: 1250,
+ cardNumber: '5678-9012-3456-7890',
+ expiryDate: '12/2025',
+ registrationDate: '15.06.2020',
+};
+
+export default function CustomerDashboard() {
+ return (
+
+
+
+
+
Личный кабинет
+
+
+
+
+ {/* Bonus Card */}
+
+
+
+
+ Бонусная карта
+
+
+ Ваши накопленные бонусы
+
+
+
+
+
+ {customerData.bonusPoints}
+
+
бонусных баллов
+
+
+
+
+ Действует до: 31.12.2023
+
+
+
+
+
+ {/* Customer Card */}
+
+
+
+
+ Информация о клиенте
+
+
+
+
+
+
+
ФИО
+
+ {customerData.firstName} {customerData.lastName}
+
+
+
+
Дата регистрации
+
+ {customerData.registrationDate}
+
+
+
+
+
+
Номер карты
+
{customerData.cardNumber}
+
+
+
Срок действия
+
{customerData.expiryDate}
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/shared/shadcn-ui/calendar.tsx b/src/shared/shadcn-ui/calendar.tsx
new file mode 100644
index 0000000..2f4b84e
--- /dev/null
+++ b/src/shared/shadcn-ui/calendar.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import * as React from 'react';
+import { DayPicker } from 'react-day-picker';
+
+import { cn } from '@/shared/lib/utils';
+import { buttonVariants } from '@/shared/shadcn-ui/button';
+
+export type CalendarProps = React.ComponentProps;
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ (
+
+ ),
+ IconRight: ({ className, ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+Calendar.displayName = 'Calendar';
+
+export { Calendar };
diff --git a/src/shared/shadcn-ui/popover.tsx b/src/shared/shadcn-ui/popover.tsx
new file mode 100644
index 0000000..7fdf57d
--- /dev/null
+++ b/src/shared/shadcn-ui/popover.tsx
@@ -0,0 +1,30 @@
+'use client';
+
+import { cn } from '@/shared/lib/utils';
+import * as PopoverPrimitive from '@radix-ui/react-popover';
+import * as React from 'react';
+
+const Popover = PopoverPrimitive.Root;
+
+const PopoverTrigger = PopoverPrimitive.Trigger;
+
+const PopoverContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
+
+
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
+
+export { Popover, PopoverTrigger, PopoverContent };
diff --git a/src/shared/shadcn-ui/table.tsx b/src/shared/shadcn-ui/table.tsx
new file mode 100644
index 0000000..d9998b3
--- /dev/null
+++ b/src/shared/shadcn-ui/table.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react';
+
+import { cn } from '@/shared/lib/utils';
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Table.displayName = 'Table';
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableHeader.displayName = 'TableHeader';
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableBody.displayName = 'TableBody';
+
+const TableFooter = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+ tr]:last:border-b-0',
+ className,
+ )}
+ {...props}
+ />
+));
+TableFooter.displayName = 'TableFooter';
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableRow.displayName = 'TableRow';
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+));
+TableHead.displayName = 'TableHead';
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+));
+TableCell.displayName = 'TableCell';
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+TableCaption.displayName = 'TableCaption';
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};
diff --git a/src/widgets/transactions-table.tsx b/src/widgets/transactions-table.tsx
new file mode 100644
index 0000000..8cee4b8
--- /dev/null
+++ b/src/widgets/transactions-table.tsx
@@ -0,0 +1,216 @@
+'use client';
+
+import { format, subMonths } from 'date-fns';
+import { ru } from 'date-fns/locale';
+import { CalendarIcon } from 'lucide-react';
+import { useState } from 'react';
+
+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,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/shared/shadcn-ui/table';
+
+// Sample customer data
+const customerData = {
+ firstName: 'Алишер',
+ lastName: 'Рахмонов',
+ passportNumber: 'A12345678',
+ bonusPoints: 1250,
+ cardNumber: '5678-9012-3456-7890',
+ expiryDate: '12/2025',
+ registrationDate: '15.06.2020',
+};
+
+// 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 const TransactionsTable = () => {
+ const [startDate, setStartDate] = useState(
+ subMonths(new Date(), 1),
+ );
+ const [endDate, setEndDate] = useState(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 (
+
+
+
История операций
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Дата
+ Станция
+ Продукт
+ Кол-во (л)
+ Стоимость
+ Сумма
+
+
+
+ {filteredTransactions.length > 0 ? (
+ filteredTransactions.map((transaction) => (
+
+
+ {format(transaction.date, 'dd.MM.yyyy')}
+
+ {transaction.station}
+ {transaction.product}
+
+ {transaction.quantity}
+
+
+ {transaction.cost.toFixed(2)} сомони
+
+
+ {transaction.total.toFixed(2)} сомони
+
+
+ ))
+ ) : (
+
+
+ Нет операций за выбранный период
+
+
+ )}
+
+
+
+
+ );
+};