Compare commits

...

4 Commits

Author SHA1 Message Date
khadiatullo
c4872b7323 Merge branch 'dev' of https://devgit.oriyo.tj/adilovcode/oriyo_next into add-pages 2025-05-02 02:45:58 +03:00
Umar Adilov
93c4b75998 integrate transaction table 2025-05-02 00:49:05 +05:00
Umar Adilov
891fb64339 Added taylor api middleware 2025-05-01 23:27:00 +05:00
Umar Adilov
6deb48239e Fixed issue with SSR 2025-05-01 23:25:37 +05:00
27 changed files with 444 additions and 315 deletions

View File

@ -7,11 +7,11 @@ import { makeStore } from '@/shared/store';
export default async function About() { export default async function About() {
const store = makeStore(); const store = makeStore();
const { data, isLoading } = await store.dispatch( const { data } = await store.dispatch(
mainPageApi.endpoints.fetchAboutUsPageContent.initiate(), mainPageApi.endpoints.fetchAboutUsPageContent.initiate(),
); );
if (isLoading || !data) return null; if (!data) return null;
return <AboutPage content={data} />; return <AboutPage content={data} />;
} }

View File

@ -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 }); return NextResponse.json({ error: 'Credentials error' }, { status: 401 });
} }
const response = NextResponse.json({ success: true }); const response = NextResponse.json({ success: true });
response.cookies.set( response.cookies.set(`${validatedBody.type}__token`, oriyoResponse.data, {
`${validatedBody.type}__token`,
JSON.stringify({ token, card_id }),
{
path: '/', path: '/',
maxAge: 2 * 60 * 60, maxAge: 2 * 60 * 60,
}, });
);
return response; return response;
} catch (error) { } catch (error) {

View File

@ -4,7 +4,7 @@ import oriyoClient from '@/app/api-utlities/utilities/oriyo.client';
import { validationErrorHandler } from '../../middlewares/error-handler.middleware'; import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
export const routeHandler = async (req: NextRequest) => { const routeHandler = async (req: NextRequest) => {
const bonusTokenData = req.cookies.get('bonus__token'); const bonusTokenData = req.cookies.get('bonus__token');
if (!bonusTokenData) { if (!bonusTokenData) {

View 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);

View 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);

View File

@ -2,9 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
export const validationErrorHandler = export const validationErrorHandler =
(handler: Function) => async (req: NextRequest, res: NextResponse) => { (handler: Function) =>
async (req: NextRequest, ...args: any[]) => {
try { try {
return await handler(req, res); return await handler(req, ...args);
} catch (error) { } catch (error) {
if (error instanceof ZodError) if (error instanceof ZodError)
return NextResponse.json({ message: error.format() }, { status: 400 }); return NextResponse.json({ message: error.format() }, { status: 400 });

View File

@ -1,28 +0,0 @@
import {
presentHistoryItems,
presentReviews,
presentStations,
presentTeamMembers,
} from '@/app/api-utlities/presenters';
import { aboutUsPageRequest } from '@/app/api-utlities/requests/about-us-page.request';
import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
const routeHandler = async () => {
const response = await requestTaylor(aboutUsPageRequest);
return new Response(
JSON.stringify({
team: presentTeamMembers(response.data._komanda),
history: presentHistoryItems(response.data._istoriya),
stations: presentStations(response.data._azs),
reviews: presentReviews(response.data._otzyvy),
}),
{
headers: { 'Content-Type': 'application/json' },
},
);
};
export const GET = validationErrorHandler(routeHandler);

View File

@ -1,28 +0,0 @@
import {
presentDiscounts,
presentJobs,
presentPartners,
presentStations,
} from '@/app/api-utlities/presenters';
import { mainPageRequest } from '@/app/api-utlities/requests/main-page.request';
import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
import { validationErrorHandler } from '../../middlewares/error-handler.middleware';
const routeHandler = async (request: Request) => {
const response = await requestTaylor(mainPageRequest);
return new Response(
JSON.stringify({
partners: presentPartners(response.data._partners),
jobs: presentJobs(response.data._vacancies),
discounts: presentDiscounts(response.data._akcii),
stations: presentStations(response.data._azs),
}),
{
headers: { 'Content-Type': 'application/json' },
},
);
};
export const GET = validationErrorHandler(routeHandler);

View File

@ -1,14 +0,0 @@
import { presentTexts } from '@/app/api-utlities/presenters';
import { textsRequest } from '@/app/api-utlities/requests/common';
import { requestTaylor } from '@/app/api-utlities/utilities/taylor.client';
export async function GET(request: Request) {
const response = await requestTaylor(textsRequest);
return new Response(
JSON.stringify(presentTexts(response.data._kontentSajta)),
{
headers: { 'Content-Type': 'application/json' },
},
);
}

View File

@ -37,7 +37,7 @@ export default async function RootLayout({
className='scroll-smooth' className='scroll-smooth'
style={{ scrollBehavior: 'smooth' }} style={{ scrollBehavior: 'smooth' }}
> >
<body className={`${inter.className} antialiased min-w-2xs`}> <body className={`${inter.className} min-w-2xs antialiased`}>
<Providers textItems={response.data as TextItem[]}> <Providers textItems={response.data as TextItem[]}>
<Header /> <Header />
{children} {children}

View File

@ -15,7 +15,7 @@ import { VacanciesSection } from '@/widgets/vacancies-section';
export default async function Home() { export default async function Home() {
const store = makeStore(); const store = makeStore();
const { data, isLoading } = await store.dispatch( const { data, isLoading, error } = await store.dispatch(
mainPageApi.endpoints.fetchMainPageContent.initiate(), mainPageApi.endpoints.fetchMainPageContent.initiate(),
); );

View File

@ -1,6 +1,10 @@
import { baseAPI } from '@/shared/api/base-api'; 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({ export const bonusApi = baseAPI.injectEndpoints({
endpoints: (builder) => ({ 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;

View File

@ -6,3 +6,29 @@ export interface ClientInfo {
end_date: string; end_date: string;
bonuses: 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;
}

View 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;

View File

@ -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;
}

View File

@ -10,7 +10,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Stations } from '@/app/api-utlities/@types/main'; import { Stations } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Badge } from '@/shared/shadcn-ui/badge'; import { Badge } from '@/shared/shadcn-ui/badge';

View File

@ -1,19 +1,59 @@
import { jsonToGraphQLQuery } from 'json-to-graphql-query';
import { AboutUsPageData } from '@/app/api-utlities/@types/about-us'; import { AboutUsPageData } from '@/app/api-utlities/@types/about-us';
import { MainPageData } from '@/app/api-utlities/@types/main'; import { MainPageData } from '@/app/api-utlities/@types/main';
import {
presentDiscounts,
presentHistoryItems,
presentJobs,
presentPartners,
presentReviews,
presentStations,
presentTeamMembers,
} from '@/app/api-utlities/presenters';
import { aboutUsPageRequest } from '@/app/api-utlities/requests/about-us-page.request';
import { mainPageRequest } from '@/app/api-utlities/requests/main-page.request';
import { baseAPI } from '@/shared/api/base-api'; import { taylorAPI } from '@/shared/api/taylor-api';
export const mainPageApi = baseAPI.injectEndpoints({ export const mainPageApi = taylorAPI.injectEndpoints({
endpoints: (builder) => ({ endpoints: (builder) => ({
fetchMainPageContent: builder.query<MainPageData, void>({ fetchMainPageContent: builder.query<MainPageData, void>({
query: () => '/pages/main', query: () => ({
url: '',
method: 'POST',
body: {
query: jsonToGraphQLQuery({ query: mainPageRequest }),
},
}), }),
fetchAboutUsPageContent: builder.query<AboutUsPageData, void>({ transformResponse: (response: any) => {
query: () => '/pages/about-us', return {
partners: presentPartners(response.data._partners),
jobs: presentJobs(response.data._vacancies),
discounts: presentDiscounts(response.data._akcii),
stations: presentStations(response.data._azs),
};
},
}),
fetchAboutUsPageContent: builder.mutation<AboutUsPageData, void>({
query: () => ({
url: '',
method: 'POST',
body: {
query: jsonToGraphQLQuery({ query: aboutUsPageRequest }),
},
}),
transformResponse: (response: any) => {
return {
team: presentTeamMembers(response.data._komanda),
history: presentHistoryItems(response.data._istoriya),
stations: presentStations(response.data._azs),
reviews: presentReviews(response.data._otzyvy),
};
},
}), }),
}), }),
}); });
export const { useFetchMainPageContentQuery, useFetchAboutUsPageContentQuery } =
mainPageApi;

View File

@ -4,6 +4,8 @@ import { subMonths } from 'date-fns';
import { Building2, LogOut, Wallet } from 'lucide-react'; import { Building2, LogOut, Wallet } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { useFetchMyCorporateInfoQuery } from '@/entities/corporate/api/corporate.api';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';
import { import {
@ -96,6 +98,8 @@ export function CorporateDashboard() {
const { t } = useTextController(); const { t } = useTextController();
const { data, isLoading } = useFetchMyCorporateInfoQuery({});
return ( return (
<div className='flex min-h-screen flex-col px-2.5'> <div className='flex min-h-screen flex-col px-2.5'>
<main className='flex-1 py-10'> <main className='flex-1 py-10'>
@ -110,7 +114,7 @@ export function CorporateDashboard() {
<div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'> <div className='mb-10 grid gap-3 md:grid-cols-3 md:gap-6'>
{/* Company Card */} {/* Company Card */}
<Card data-aos='zoom-in' data-aos-mirror="true" className='md:col-span-2 order-2 md:order-1'> <Card data-aos='zoom-in' data-aos-mirror="true" className='md:col-span-2'>
<CardHeader className='pb-2'> <CardHeader className='pb-2'>
<CardTitle className='flex items-center gap-2'> <CardTitle className='flex items-center gap-2'>
<Building2 className='h-5 w-5 text-red-600' /> <Building2 className='h-5 w-5 text-red-600' />
@ -118,26 +122,34 @@ export function CorporateDashboard() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{!data ? (
<>Loading</>
) : (
<div className='grid gap-6 md:grid-cols-2'> <div className='grid gap-6 md:grid-cols-2'>
<div> <div>
<div className='mb-4 space-y-1'> <div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'> <p className='text-sm text-gray-500'>
{t('corporate.companyCard.companyNameLabel')} {t('corporate.companyCard.companyNameLabel')}
</p> </p>
<p className='font-medium'>{companyData.companyName}</p> <p className='truncate font-medium'>
{data.group_name}
</p>
</div> </div>
<div className='mb-4 space-y-1'> <div className='mb-4 space-y-1'>
<p className='text-sm text-gray-500'> <p className='text-sm text-gray-500'>
{t('corporate.companyCard.cardsCountLabel')} {t('corporate.companyCard.cardsCountLabel')}
</p> </p>
<p className='font-medium'>{companyData.numberOfCards}</p> <p className='font-medium'>{data.total_cards}</p>
</div> </div>
<div className='space-y-1'> <div className='space-y-1'>
<p className='text-sm text-gray-500'> <p className='text-sm text-gray-500'>
{t('corporate.companyCard.registrationDateLabel')} {t('corporate.companyCard.registrationDateLabel')}
</p> </p>
<p className='font-medium'> <p className='font-medium'>
{companyData.registrationDate} {new Date(data.created_at).toLocaleDateString(
'en-GB',
)}
</p> </p>
</div> </div>
</div> </div>
@ -147,8 +159,7 @@ export function CorporateDashboard() {
{t('corporate.companyCard.fundLabel')} {t('corporate.companyCard.fundLabel')}
</p> </p>
<p className='font-medium'> <p className='font-medium'>
{companyData.fund.toLocaleString()}{' '} {data.fund.toLocaleString()} {t('corporate.currency')}
{t('corporate.currency')}
</p> </p>
</div> </div>
<div className='mb-4 space-y-1'> <div className='mb-4 space-y-1'>
@ -156,17 +167,18 @@ export function CorporateDashboard() {
{t('corporate.companyCard.overdraftLabel')} {t('corporate.companyCard.overdraftLabel')}
</p> </p>
<p className='font-medium'> <p className='font-medium'>
{companyData.overdraft.toLocaleString()}{' '} {data.overdraft.toLocaleString()}{' '}
{t('corporate.currency')} {t('corporate.currency')}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
)}
</CardContent> </CardContent>
</Card> </Card>
{/* Fund 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 order-1 md:order-2'> <Card data-aos='zoom-in' data-aos-mirror="true" className='bg-gradient-to-br from-red-600 to-red-800 text-white'>
<CardHeader> <CardHeader>
<CardTitle className='flex items-center gap-2'> <CardTitle className='flex items-center gap-2'>
<Wallet className='h-5 w-5' /> <Wallet className='h-5 w-5' />
@ -177,14 +189,18 @@ export function CorporateDashboard() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{!data ? (
<>Loading</>
) : (
<div className='text-center'> <div className='text-center'>
<p className='mb-2 text-4xl font-bold'> <p className='mb-2 text-4xl font-bold'>
{companyData.totalFund.toLocaleString()} {data.fund_total?.toLocaleString()}
</p> </p>
<p className='text-white/80'> <p className='text-white/80'>
{t('corporate.fundCard.currency')} {t('corporate.fundCard.currency')}
</p> </p>
</div> </div>
)}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@ -4,6 +4,7 @@ import { deleteCookie, getCookie } from 'cookies-next';
import { Building2, Fuel, User } from 'lucide-react'; import { Building2, Fuel, User } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
import { LoginForm } from '@/features/auth/login-form'; import { LoginForm } from '@/features/auth/login-form';
@ -16,13 +17,13 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '@/shared/shadcn-ui/card'; } from '@/shared/shadcn-ui/card';
import Container from '@/shared/shadcn-ui/conteiner';
import { import {
Tabs, Tabs,
TabsContent, TabsContent,
TabsList, TabsList,
TabsTrigger, TabsTrigger,
} from '@/shared/shadcn-ui/tabs'; } from '@/shared/shadcn-ui/tabs';
import Container from '@/shared/shadcn-ui/conteiner';
const tabs = [ const tabs = [
{ {
@ -41,32 +42,18 @@ const tabs = [
}, },
]; ];
export default function LoginPage() { function LoginPageTabs() {
const { t } = useTextController(); const { t } = useTextController();
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const defaultTab = searchParams.get('tab') || 'bonus'; const defaultTab = searchParams.get('tab') || 'bonus';
const handleTabChange = (tabType: string) => { const handleTabChange = (tabType: string) => {
router.push(`?tab=${tabType}`, undefined, { shallow: true }); router.push(`?tab=${tabType}`, undefined);
}; };
return ( return (
<Container>
<div className='flex min-h-screen flex-col items-center justify-center'>
<main className='flex-1'>
<div className='container max-w-6xl py-16'>
<div className='mb-12 flex flex-col items-center text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Fuel className='h-6 w-6 text-red-600' />
</div>
<h1 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('auth.title')}
</h1>
<p className='max-w-2xl text-gray-600'>{t('auth.description')}</p>
</div>
<div data-aos='zoom-in' className='mx-auto max-w-lg'>
<Tabs <Tabs
defaultValue={defaultTab} defaultValue={defaultTab}
value={defaultTab} value={defaultTab}
@ -107,9 +94,7 @@ export default function LoginPage() {
: '/corporate-dashboard' : '/corporate-dashboard'
} }
> >
<Button className='flex items-center'> <Button className='flex items-center'>Открыть</Button>
Открыть
</Button>
</Link> </Link>
<Button <Button
variant='outline' variant='outline'
@ -142,11 +127,39 @@ export default function LoginPage() {
); );
})} })}
</Tabs> </Tabs>
);
}
export default function LoginPage() {
const { t } = useTextController();
return (
<Container>
<div className='flex min-h-screen flex-col items-center justify-center'>
<main className='flex-1'>
<div className='container max-w-6xl py-16'>
<div className='mb-12 flex flex-col items-center text-center'>
<div className='mb-4 inline-flex items-center justify-center rounded-full bg-red-100 p-2'>
<Fuel className='h-6 w-6 text-red-600' />
</div>
<h1 className='mb-4 text-3xl font-bold tracking-tight sm:text-4xl'>
{t('auth.title')}
</h1>
<p className='max-w-2xl text-gray-600'>{t('auth.description')}</p>
</div>
<div data-aos='zoom-in' className='mx-auto max-w-lg'>
<Suspense>
<LoginPageTabs />
</Suspense>
<div className='mt-8 text-center text-sm text-gray-500'> <div className='mt-8 text-center text-sm text-gray-500'>
<p> <p>
{t('auth.loginIssues')}{' '} {t('auth.loginIssues')}{' '}
<Link href='/contact' className='text-red-600 hover:underline'> <Link
href='/contact'
className='text-red-600 hover:underline'
>
{t('auth.contactLink')} {t('auth.contactLink')}
</Link> </Link>
</p> </p>

View File

@ -0,0 +1,14 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
const baseQuery = fetchBaseQuery({
baseUrl: process.env.TAYLOR_API_ENDPOINT,
headers: {
Authorization: process.env.TAYLOR_API_TOKEN || '',
},
});
export const taylorAPI = createApi({
reducerPath: 'taylorAPI',
baseQuery,
endpoints: () => ({}),
});

View File

@ -1,12 +1,25 @@
import { baseAPI } from '@/shared/api/base-api'; import { jsonToGraphQLQuery } from 'json-to-graphql-query';
import { presentTexts } from '@/app/api-utlities/presenters';
import { textsRequest } from '@/app/api-utlities/requests/common';
import { taylorAPI } from '@/shared/api/taylor-api';
import { TextItem } from '@/shared/types/text.types'; import { TextItem } from '@/shared/types/text.types';
export const textControlApi = baseAPI.injectEndpoints({ export const textControlApi = taylorAPI.injectEndpoints({
endpoints: (builder) => ({ endpoints: (builder) => ({
fetchText: builder.query<TextItem[], void>({ fetchText: builder.query<TextItem[], void>({
query: () => '/text', query: () => ({
url: '',
method: 'POST',
body: {
query: jsonToGraphQLQuery({ query: textsRequest }),
},
}),
transformResponse: (response: any) => {
return presentTexts(response.data._kontentSajta);
},
}), }),
}), }),
}); });
export const { useFetchTextQuery } = textControlApi;

View File

@ -4,13 +4,16 @@ import { createWrapper } from 'next-redux-wrapper';
import { baseAPI } from '@/shared/api/base-api'; import { baseAPI } from '@/shared/api/base-api';
import { taylorAPI } from '../api/taylor-api';
import { rootReducer } from './root-reducer'; import { rootReducer } from './root-reducer';
export const makeStore = () => export const makeStore = () =>
configureStore({ configureStore({
reducer: rootReducer, reducer: rootReducer,
middleware: (getDefaultMiddleware) => middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseAPI.middleware), getDefaultMiddleware()
.concat(baseAPI.middleware)
.concat(taylorAPI.middleware),
devTools: process.env.NODE_ENV === 'development', devTools: process.env.NODE_ENV === 'development',
}); });

View File

@ -2,6 +2,9 @@ import { combineReducers } from '@reduxjs/toolkit';
import { baseAPI } from '@/shared/api/base-api'; import { baseAPI } from '@/shared/api/base-api';
import { taylorAPI } from '../api/taylor-api';
export const rootReducer = combineReducers({ export const rootReducer = combineReducers({
[baseAPI.reducerPath]: baseAPI.reducer, [baseAPI.reducerPath]: baseAPI.reducer,
[taylorAPI.reducerPath]: taylorAPI.reducer,
}); });

View File

@ -3,7 +3,7 @@
import { Calendar, ChevronDown, ChevronUp } from 'lucide-react'; import { Calendar, ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { HistoryItems } from '@/app/api-utlities/@types/about-us'; import { HistoryItems } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';

View File

@ -2,9 +2,8 @@
import { MapPin } from 'lucide-react'; import { MapPin } from 'lucide-react';
import { Stations } from '@/app/api-utlities/@types/main'; import { Stations } from '@/app/api-utlities/@types';
import { GasStationMap } from '@/features/map';
import { Point } from '@/features/map/model'; import { Point } from '@/features/map/model';
import { YandexMap } from '@/features/map/ui/yandex-map'; import { YandexMap } from '@/features/map/ui/yandex-map';

View File

@ -4,7 +4,7 @@ import { Handshake } from 'lucide-react';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { Partners } from '@/app/api-utlities/@types/main'; import { Partners } from '@/app/api-utlities/@types';
import { useTextController } from '@/shared/language/hooks/use-text-controller'; import { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';

View File

@ -3,7 +3,9 @@
import { format, subMonths } from 'date-fns'; import { format, subMonths } from 'date-fns';
import { ru } from 'date-fns/locale'; import { ru } from 'date-fns/locale';
import { CalendarIcon } from 'lucide-react'; 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 { useTextController } from '@/shared/language/hooks/use-text-controller';
import { Button } from '@/shared/shadcn-ui/button'; import { Button } from '@/shared/shadcn-ui/button';
@ -23,85 +25,30 @@ import {
TableRow, TableRow,
} from '@/shared/shadcn-ui/table'; } 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 = () => { export const TransactionsTable = () => {
const [startDate, setStartDate] = useState<Date | undefined>( const [startDate, setStartDate] = useState<Date>(subMonths(new Date(), 1));
subMonths(new Date(), 1), const [endDate, setEndDate] = useState<Date>(new Date());
);
const [endDate, setEndDate] = useState<Date | undefined>(new Date()); const { data, refetch } = useFetchBonusTransactionsQuery({
const [filteredTransactions, setFilteredTransactions] = limit: 100,
useState(transactions); page: 1,
start_date: format(startDate, 'yyyy-MM-dd'),
end_date: format(endDate, 'yyyy-MM-dd'),
});
// Filter transactions by date range // Filter transactions by date range
const filterTransactions = () => { const filterTransactions = () => {
if (!startDate || !endDate) return; if (!startDate || !endDate) return;
const filtered = transactions.filter((transaction) => { refetch();
const transactionDate = new Date(transaction.date);
return transactionDate >= startDate && transactionDate <= endDate;
});
setFilteredTransactions(filtered);
}; };
const { t } = useTextController(); const { t } = useTextController();
useEffect(() => {}, [startDate, endDate]);
if (!data) return null;
return ( return (
<div className='space-y-6'> <div className='space-y-6'>
<div className='flex flex-col items-start justify-between gap-4 md:flex-row md:items-center'> <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> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredTransactions.length > 0 ? ( {data.transactions.length > 0 ? (
filteredTransactions.map((transaction) => ( data.transactions.map((transaction) => (
<TableRow key={transaction.id}> <TableRow key={transaction.id}>
<TableCell> <TableCell>
{format(transaction.date, 'dd.MM.yyyy')} {format(new Date(transaction.date_create), 'dd.MM.yyyy')}
</TableCell> </TableCell>
<TableCell>{transaction.station}</TableCell> <TableCell>{transaction.station}</TableCell>
<TableCell>{transaction.product}</TableCell> <TableCell>{transaction.product_name}</TableCell>
<TableCell className='text-right'> <TableCell className='text-right'>
{transaction.quantity} {transaction.price_real}
</TableCell> </TableCell>
<TableCell className='text-right'> <TableCell className='text-right'>
{transaction.cost.toFixed(2)} {t('corporate.currency')} {transaction.amount} {t('corporate.currency')}
</TableCell> </TableCell>
<TableCell className='text-right font-medium'> <TableCell className='text-right font-medium'>
{transaction.total.toFixed(2)} {t('corporate.currency')} {transaction.sum_real} {t('corporate.currency')}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))