integrate transaction table
This commit is contained in:
parent
891fb64339
commit
93c4b75998
@ -23,22 +23,18 @@ const routeHandler = async (req: NextRequest) => {
|
||||
},
|
||||
});
|
||||
|
||||
const { token, card_id } = JSON.parse(oriyoResponse.data);
|
||||
const parsedResponse = JSON.parse(oriyoResponse.data);
|
||||
|
||||
if (!token) {
|
||||
if (!parsedResponse.token) {
|
||||
return NextResponse.json({ error: 'Credentials error' }, { status: 401 });
|
||||
}
|
||||
|
||||
const response = NextResponse.json({ success: true });
|
||||
|
||||
response.cookies.set(
|
||||
`${validatedBody.type}__token`,
|
||||
JSON.stringify({ token, card_id }),
|
||||
{
|
||||
response.cookies.set(`${validatedBody.type}__token`, oriyoResponse.data, {
|
||||
path: '/',
|
||||
maxAge: 2 * 60 * 60,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
||||
63
src/app/api/bonus/transactions/route.ts
Normal file
63
src/app/api/bonus/transactions/route.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import oriyoClient from '@/app/api-utlities/utilities/oriyo.client';
|
||||
|
||||
import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
|
||||
|
||||
const validatedSchema = z.object({
|
||||
start_date: z.string().optional(),
|
||||
end_date: z.string().optional(),
|
||||
limit: z.coerce.number(),
|
||||
page: z.coerce.number(),
|
||||
});
|
||||
|
||||
const routeHandler = async (req: NextRequest) => {
|
||||
const bonusTokenData = req.cookies.get('bonus__token');
|
||||
|
||||
if (!bonusTokenData) {
|
||||
return NextResponse.json(
|
||||
{ error: 'User does not have access' },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
const params = Array.from(req.nextUrl.searchParams.entries()).reduce(
|
||||
(pr, cr) => {
|
||||
pr[cr[0]] = cr[1];
|
||||
|
||||
return pr;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
const validatedRequest = validatedSchema.parse(params);
|
||||
|
||||
const { card_id, token } = JSON.parse(bonusTokenData.value);
|
||||
|
||||
const oriyoResponse = await oriyoClient.get('/client/transactions', {
|
||||
params: {
|
||||
card_id,
|
||||
token,
|
||||
limit: validatedRequest.limit,
|
||||
page: validatedRequest.page,
|
||||
type: 'bonus',
|
||||
sort: 'id',
|
||||
direction: 'desc',
|
||||
start_date: validatedRequest.start_date,
|
||||
end_date: validatedRequest.end_date,
|
||||
},
|
||||
});
|
||||
|
||||
const parsedResponse = JSON.parse(oriyoResponse.data);
|
||||
|
||||
if (parsedResponse.error) {
|
||||
return NextResponse.json({ message: 'Fetch error' }, { status: 400 });
|
||||
}
|
||||
|
||||
return new Response(oriyoResponse.data, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
export const GET = validationErrorHandler(routeHandler);
|
||||
23
src/app/api/corporate/info/route.ts
Normal file
23
src/app/api/corporate/info/route.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { omit } from 'lodash';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
|
||||
|
||||
const routeHandler = async (req: NextRequest) => {
|
||||
const bonusTokenData = req.cookies.get('corporate__token');
|
||||
|
||||
if (!bonusTokenData) {
|
||||
return NextResponse.json(
|
||||
{ error: 'User does not have access' },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
const parsedData = JSON.parse(bonusTokenData.value);
|
||||
|
||||
return new Response(JSON.stringify(omit(parsedData, 'token')), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
export const GET = validationErrorHandler(routeHandler);
|
||||
@ -2,9 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
export const validationErrorHandler =
|
||||
(handler: Function) => async (req: NextRequest) => {
|
||||
(handler: Function) =>
|
||||
async (req: NextRequest, ...args: any[]) => {
|
||||
try {
|
||||
return await handler(req);
|
||||
return await handler(req, ...args);
|
||||
} catch (error) {
|
||||
if (error instanceof ZodError)
|
||||
return NextResponse.json({ message: error.format() }, { status: 400 });
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { baseAPI } from '@/shared/api/base-api';
|
||||
|
||||
import { ClientInfo } from '../model/types/bonus-client-info.type';
|
||||
import {
|
||||
ClientInfo,
|
||||
TransactionRequest,
|
||||
TransactionResponse,
|
||||
} from '../model/types/bonus-client-info.type';
|
||||
|
||||
export const bonusApi = baseAPI.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
@ -11,7 +15,19 @@ export const bonusApi = baseAPI.injectEndpoints({
|
||||
};
|
||||
},
|
||||
}),
|
||||
fetchBonusTransactions: builder.query<
|
||||
TransactionResponse,
|
||||
TransactionRequest
|
||||
>({
|
||||
query: (request) => {
|
||||
return {
|
||||
url: '/bonus/transactions',
|
||||
params: request,
|
||||
};
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useFetchMyBonusInfoQuery } = bonusApi;
|
||||
export const { useFetchMyBonusInfoQuery, useFetchBonusTransactionsQuery } =
|
||||
bonusApi;
|
||||
|
||||
@ -6,3 +6,29 @@ export interface ClientInfo {
|
||||
end_date: string;
|
||||
bonuses: string;
|
||||
}
|
||||
|
||||
export interface TransactionResponse {
|
||||
transactions: Transaction[];
|
||||
card_id: string;
|
||||
current_page: number;
|
||||
limit: number;
|
||||
total_records: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
id: number;
|
||||
date_create: string;
|
||||
station: string;
|
||||
product_name: string;
|
||||
amount: string;
|
||||
price_real: string;
|
||||
sum_real: string;
|
||||
}
|
||||
|
||||
export interface TransactionRequest {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
page: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
17
src/entities/corporate/api/corporate.api.ts
Normal file
17
src/entities/corporate/api/corporate.api.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { baseAPI } from '@/shared/api/base-api';
|
||||
|
||||
import { CorporateInfoResponse } from '../model/types/corporate-client-info.type';
|
||||
|
||||
export const corporateApi = baseAPI.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
fetchMyCorporateInfo: builder.query<CorporateInfoResponse, any>({
|
||||
query: () => {
|
||||
return {
|
||||
url: '/corporate/info',
|
||||
};
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useFetchMyCorporateInfoQuery } = corporateApi;
|
||||
@ -0,0 +1,9 @@
|
||||
export interface CorporateInfoResponse {
|
||||
created_at: string;
|
||||
fund: string;
|
||||
fund_total: string;
|
||||
group_id: number;
|
||||
group_name: string;
|
||||
overdraft: string;
|
||||
total_cards: number;
|
||||
}
|
||||
@ -4,6 +4,8 @@ import { subMonths } from 'date-fns';
|
||||
import { Building2, LogOut, Wallet } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useFetchMyCorporateInfoQuery } from '@/entities/corporate/api/corporate.api';
|
||||
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
import {
|
||||
@ -96,6 +98,8 @@ export function CorporateDashboard() {
|
||||
|
||||
const { t } = useTextController();
|
||||
|
||||
const { data, isLoading } = useFetchMyCorporateInfoQuery({});
|
||||
|
||||
return (
|
||||
<div className='flex min-h-screen flex-col px-2.5'>
|
||||
<main className='flex-1 py-10'>
|
||||
@ -110,7 +114,11 @@ export function CorporateDashboard() {
|
||||
|
||||
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
|
||||
{/* Company Card */}
|
||||
<Card data-aos='zoom-in' data-aos-mirror="true" className='md:col-span-2'>
|
||||
<Card
|
||||
data-aos='zoom-in'
|
||||
data-aos-mirror='true'
|
||||
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' />
|
||||
@ -118,26 +126,34 @@ export function CorporateDashboard() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!data ? (
|
||||
<>Loading</>
|
||||
) : (
|
||||
<div className='grid gap-6 md:grid-cols-2'>
|
||||
<div>
|
||||
<div className='mb-4 space-y-1'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('corporate.companyCard.companyNameLabel')}
|
||||
</p>
|
||||
<p className='font-medium'>{companyData.companyName}</p>
|
||||
<p className='truncate font-medium'>
|
||||
{data.group_name}
|
||||
</p>
|
||||
</div>
|
||||
<div className='mb-4 space-y-1'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('corporate.companyCard.cardsCountLabel')}
|
||||
</p>
|
||||
<p className='font-medium'>{companyData.numberOfCards}</p>
|
||||
<p className='font-medium'>{data.total_cards}</p>
|
||||
</div>
|
||||
<div className='space-y-1'>
|
||||
<p className='text-sm text-gray-500'>
|
||||
{t('corporate.companyCard.registrationDateLabel')}
|
||||
</p>
|
||||
|
||||
<p className='font-medium'>
|
||||
{companyData.registrationDate}
|
||||
{new Date(data.created_at).toLocaleDateString(
|
||||
'en-GB',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -147,8 +163,7 @@ export function CorporateDashboard() {
|
||||
{t('corporate.companyCard.fundLabel')}
|
||||
</p>
|
||||
<p className='font-medium'>
|
||||
{companyData.fund.toLocaleString()}{' '}
|
||||
{t('corporate.currency')}
|
||||
{data.fund.toLocaleString()} {t('corporate.currency')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='mb-4 space-y-1'>
|
||||
@ -156,17 +171,22 @@ export function CorporateDashboard() {
|
||||
{t('corporate.companyCard.overdraftLabel')}
|
||||
</p>
|
||||
<p className='font-medium'>
|
||||
{companyData.overdraft.toLocaleString()}{' '}
|
||||
{data.overdraft.toLocaleString()}{' '}
|
||||
{t('corporate.currency')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Fund Card */}
|
||||
<Card data-aos='zoom-in' data-aos-mirror="true" className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
|
||||
<Card
|
||||
data-aos='zoom-in'
|
||||
data-aos-mirror='true'
|
||||
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' />
|
||||
@ -177,14 +197,18 @@ export function CorporateDashboard() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!data ? (
|
||||
<>Loading</>
|
||||
) : (
|
||||
<div className='text-center'>
|
||||
<p className='mb-2 text-4xl font-bold'>
|
||||
{companyData.totalFund.toLocaleString()}
|
||||
{data.fund_total?.toLocaleString()}
|
||||
</p>
|
||||
<p className='text-white/80'>
|
||||
{t('corporate.fundCard.currency')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
import { format, subMonths } from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import { CalendarIcon } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useFetchBonusTransactionsQuery } from '@/entities/bonus/api/bonus.api';
|
||||
|
||||
import { useTextController } from '@/shared/language/hooks/use-text-controller';
|
||||
import { Button } from '@/shared/shadcn-ui/button';
|
||||
@ -23,85 +25,30 @@ import {
|
||||
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<Date | undefined>(
|
||||
subMonths(new Date(), 1),
|
||||
);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(new Date());
|
||||
const [filteredTransactions, setFilteredTransactions] =
|
||||
useState(transactions);
|
||||
const [startDate, setStartDate] = useState<Date>(subMonths(new Date(), 1));
|
||||
const [endDate, setEndDate] = useState<Date>(new Date());
|
||||
|
||||
const { data, refetch } = useFetchBonusTransactionsQuery({
|
||||
limit: 100,
|
||||
page: 1,
|
||||
start_date: format(startDate, 'yyyy-MM-dd'),
|
||||
end_date: format(endDate, 'yyyy-MM-dd'),
|
||||
});
|
||||
|
||||
// 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);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const { t } = useTextController();
|
||||
|
||||
useEffect(() => {}, [startDate, endDate]);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'>
|
||||
@ -200,22 +147,22 @@ export const TransactionsTable = () => {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredTransactions.length > 0 ? (
|
||||
filteredTransactions.map((transaction) => (
|
||||
{data.transactions.length > 0 ? (
|
||||
data.transactions.map((transaction) => (
|
||||
<TableRow key={transaction.id}>
|
||||
<TableCell>
|
||||
{format(transaction.date, 'dd.MM.yyyy')}
|
||||
{format(new Date(transaction.date_create), 'dd.MM.yyyy')}
|
||||
</TableCell>
|
||||
<TableCell>{transaction.station}</TableCell>
|
||||
<TableCell>{transaction.product}</TableCell>
|
||||
<TableCell>{transaction.product_name}</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
{transaction.quantity}
|
||||
{transaction.price_real}
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
{transaction.cost.toFixed(2)} {t('corporate.currency')}
|
||||
{transaction.amount} {t('corporate.currency')}
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-medium'>
|
||||
{transaction.total.toFixed(2)} {t('corporate.currency')}
|
||||
{transaction.sum_real} {t('corporate.currency')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user