From 93c4b75998ac720e9152003a1426a4a23fbe2a3b Mon Sep 17 00:00:00 2001 From: Umar Adilov <99314948+adilovcode@users.noreply.github.com> Date: Fri, 2 May 2025 00:49:05 +0500 Subject: [PATCH] integrate transaction table --- src/app/api/auth/login/route.ts | 16 +-- src/app/api/bonus/transactions/route.ts | 63 +++++++++ src/app/api/corporate/info/route.ts | 23 ++++ .../middlewares/error-handler.middleware.ts | 5 +- src/entities/bonus/api/bonus.api.ts | 20 ++- .../model/types/bonus-client-info.type.ts | 26 ++++ src/entities/corporate/api/corporate.api.ts | 17 +++ .../model/types/corporate-client-info.type.ts | 9 ++ .../(dashboard)/corporate-dashboard/index.tsx | 126 +++++++++++------- src/widgets/transactions-table.tsx | 101 ++++---------- 10 files changed, 264 insertions(+), 142 deletions(-) create mode 100644 src/app/api/bonus/transactions/route.ts create mode 100644 src/app/api/corporate/info/route.ts create mode 100644 src/entities/corporate/api/corporate.api.ts create mode 100644 src/entities/corporate/model/types/corporate-client-info.type.ts diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 7dbe591..8772735 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -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 }), - { - path: '/', - maxAge: 2 * 60 * 60, - }, - ); + response.cookies.set(`${validatedBody.type}__token`, oriyoResponse.data, { + path: '/', + maxAge: 2 * 60 * 60, + }); return response; } catch (error) { diff --git a/src/app/api/bonus/transactions/route.ts b/src/app/api/bonus/transactions/route.ts new file mode 100644 index 0000000..310e78a --- /dev/null +++ b/src/app/api/bonus/transactions/route.ts @@ -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, + ); + + 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); diff --git a/src/app/api/corporate/info/route.ts b/src/app/api/corporate/info/route.ts new file mode 100644 index 0000000..b7e23ff --- /dev/null +++ b/src/app/api/corporate/info/route.ts @@ -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); diff --git a/src/app/api/middlewares/error-handler.middleware.ts b/src/app/api/middlewares/error-handler.middleware.ts index cc02041..d7fa719 100644 --- a/src/app/api/middlewares/error-handler.middleware.ts +++ b/src/app/api/middlewares/error-handler.middleware.ts @@ -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 }); diff --git a/src/entities/bonus/api/bonus.api.ts b/src/entities/bonus/api/bonus.api.ts index b8d31bf..b83c712 100644 --- a/src/entities/bonus/api/bonus.api.ts +++ b/src/entities/bonus/api/bonus.api.ts @@ -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; diff --git a/src/entities/bonus/model/types/bonus-client-info.type.ts b/src/entities/bonus/model/types/bonus-client-info.type.ts index dd7138f..bc50a6c 100644 --- a/src/entities/bonus/model/types/bonus-client-info.type.ts +++ b/src/entities/bonus/model/types/bonus-client-info.type.ts @@ -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; +} diff --git a/src/entities/corporate/api/corporate.api.ts b/src/entities/corporate/api/corporate.api.ts new file mode 100644 index 0000000..32426ac --- /dev/null +++ b/src/entities/corporate/api/corporate.api.ts @@ -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({ + query: () => { + return { + url: '/corporate/info', + }; + }, + }), + }), +}); + +export const { useFetchMyCorporateInfoQuery } = corporateApi; diff --git a/src/entities/corporate/model/types/corporate-client-info.type.ts b/src/entities/corporate/model/types/corporate-client-info.type.ts new file mode 100644 index 0000000..3a069ae --- /dev/null +++ b/src/entities/corporate/model/types/corporate-client-info.type.ts @@ -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; +} diff --git a/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx b/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx index 02ddedd..6f0bc94 100644 --- a/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx +++ b/src/pages-templates/(dashboard)/corporate-dashboard/index.tsx @@ -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 (
@@ -110,7 +114,11 @@ export function CorporateDashboard() {
{/* Company Card */} - + @@ -118,55 +126,67 @@ export function CorporateDashboard() { -
-
-
-

- {t('corporate.companyCard.companyNameLabel')} -

-

{companyData.companyName}

+ {!data ? ( + <>Loading + ) : ( +
+
+
+

+ {t('corporate.companyCard.companyNameLabel')} +

+

+ {data.group_name} +

+
+
+

+ {t('corporate.companyCard.cardsCountLabel')} +

+

{data.total_cards}

+
+
+

+ {t('corporate.companyCard.registrationDateLabel')} +

+ +

+ {new Date(data.created_at).toLocaleDateString( + 'en-GB', + )} +

+
-
-

- {t('corporate.companyCard.cardsCountLabel')} -

-

{companyData.numberOfCards}

-
-
-

- {t('corporate.companyCard.registrationDateLabel')} -

-

- {companyData.registrationDate} -

+
+
+

+ {t('corporate.companyCard.fundLabel')} +

+

+ {data.fund.toLocaleString()} {t('corporate.currency')} +

+
+
+

+ {t('corporate.companyCard.overdraftLabel')} +

+

+ {data.overdraft.toLocaleString()}{' '} + {t('corporate.currency')} +

+
-
-
-

- {t('corporate.companyCard.fundLabel')} -

-

- {companyData.fund.toLocaleString()}{' '} - {t('corporate.currency')} -

-
-
-

- {t('corporate.companyCard.overdraftLabel')} -

-

- {companyData.overdraft.toLocaleString()}{' '} - {t('corporate.currency')} -

-
-
-
+ )} {/* Fund Card */} - + @@ -177,14 +197,18 @@ export function CorporateDashboard() { -
-

- {companyData.totalFund.toLocaleString()} -

-

- {t('corporate.fundCard.currency')} -

-
+ {!data ? ( + <>Loading + ) : ( +
+

+ {data.fund_total?.toLocaleString()} +

+

+ {t('corporate.fundCard.currency')} +

+
+ )}
diff --git a/src/widgets/transactions-table.tsx b/src/widgets/transactions-table.tsx index 03fe3ca..993ac00 100644 --- a/src/widgets/transactions-table.tsx +++ b/src/widgets/transactions-table.tsx @@ -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( - subMonths(new Date(), 1), - ); - const [endDate, setEndDate] = useState(new Date()); - const [filteredTransactions, setFilteredTransactions] = - useState(transactions); + const [startDate, setStartDate] = useState(subMonths(new Date(), 1)); + const [endDate, setEndDate] = useState(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 (
@@ -200,22 +147,22 @@ export const TransactionsTable = () => { - {filteredTransactions.length > 0 ? ( - filteredTransactions.map((transaction) => ( + {data.transactions.length > 0 ? ( + data.transactions.map((transaction) => ( - {format(transaction.date, 'dd.MM.yyyy')} + {format(new Date(transaction.date_create), 'dd.MM.yyyy')} {transaction.station} - {transaction.product} + {transaction.product_name} - {transaction.quantity} + {transaction.price_real} - {transaction.cost.toFixed(2)} {t('corporate.currency')} + {transaction.amount} {t('corporate.currency')} - {transaction.total.toFixed(2)} {t('corporate.currency')} + {transaction.sum_real} {t('corporate.currency')} ))