feat: billing guest access
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
"dot-object": "^2.1.5",
|
"dot-object": "^2.1.5",
|
||||||
"framer-motion": "^11.3.8",
|
"framer-motion": "^11.3.8",
|
||||||
"globals": "^15.8.0",
|
"globals": "^15.8.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mantine-form-zod-resolver": "^1.1.0",
|
"mantine-form-zod-resolver": "^1.1.0",
|
||||||
"mantine-react-table": "^2.0.0-beta.5",
|
"mantine-react-table": "^2.0.0-beta.5",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export type { BarcodeTemplateUpdateResponse } from './models/BarcodeTemplateUpda
|
|||||||
export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
|
export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
|
||||||
export type { BaseEnumSchema } from './models/BaseEnumSchema';
|
export type { BaseEnumSchema } from './models/BaseEnumSchema';
|
||||||
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
||||||
|
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||||
|
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
||||||
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
||||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||||
@@ -37,6 +39,8 @@ export type { ClientUpdateRequest } from './models/ClientUpdateRequest';
|
|||||||
export type { ClientUpdateResponse } from './models/ClientUpdateResponse';
|
export type { ClientUpdateResponse } from './models/ClientUpdateResponse';
|
||||||
export type { CreateBarcodeTemplateAttributeRequest } from './models/CreateBarcodeTemplateAttributeRequest';
|
export type { CreateBarcodeTemplateAttributeRequest } from './models/CreateBarcodeTemplateAttributeRequest';
|
||||||
export type { CreateBarcodeTemplateAttributeResponse } from './models/CreateBarcodeTemplateAttributeResponse';
|
export type { CreateBarcodeTemplateAttributeResponse } from './models/CreateBarcodeTemplateAttributeResponse';
|
||||||
|
export type { CreateDealBillRequest } from './models/CreateDealBillRequest';
|
||||||
|
export type { CreateDealBillResponse } from './models/CreateDealBillResponse';
|
||||||
export type { CreatePaymentRecordRequest } from './models/CreatePaymentRecordRequest';
|
export type { CreatePaymentRecordRequest } from './models/CreatePaymentRecordRequest';
|
||||||
export type { CreatePaymentRecordResponse } from './models/CreatePaymentRecordResponse';
|
export type { CreatePaymentRecordResponse } from './models/CreatePaymentRecordResponse';
|
||||||
export type { CreatePayRateRequest } from './models/CreatePayRateRequest';
|
export type { CreatePayRateRequest } from './models/CreatePayRateRequest';
|
||||||
@@ -56,8 +60,11 @@ export type { DealAddServiceRequest } from './models/DealAddServiceRequest';
|
|||||||
export type { DealAddServiceResponse } from './models/DealAddServiceResponse';
|
export type { DealAddServiceResponse } from './models/DealAddServiceResponse';
|
||||||
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
|
export type { DealAddServicesRequest } from './models/DealAddServicesRequest';
|
||||||
export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
|
export type { DealAddServicesResponse } from './models/DealAddServicesResponse';
|
||||||
|
export type { DealBillRequestSchema } from './models/DealBillRequestSchema';
|
||||||
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
|
export type { DealChangeStatusRequest } from './models/DealChangeStatusRequest';
|
||||||
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
|
export type { DealChangeStatusResponse } from './models/DealChangeStatusResponse';
|
||||||
|
export type { DealCreateGuestUrlRequest } from './models/DealCreateGuestUrlRequest';
|
||||||
|
export type { DealCreateGuestUrlResponse } from './models/DealCreateGuestUrlResponse';
|
||||||
export type { DealCreateRequest } from './models/DealCreateRequest';
|
export type { DealCreateRequest } from './models/DealCreateRequest';
|
||||||
export type { DealDeleteProductRequest } from './models/DealDeleteProductRequest';
|
export type { DealDeleteProductRequest } from './models/DealDeleteProductRequest';
|
||||||
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
|
export type { DealDeleteProductResponse } from './models/DealDeleteProductResponse';
|
||||||
@@ -114,6 +121,7 @@ export type { GetAllShippingWarehousesResponse } from './models/GetAllShippingWa
|
|||||||
export type { GetAllUsersResponse } from './models/GetAllUsersResponse';
|
export type { GetAllUsersResponse } from './models/GetAllUsersResponse';
|
||||||
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
|
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
|
||||||
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
|
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
|
||||||
|
export type { GetDealBillById } from './models/GetDealBillById';
|
||||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
||||||
@@ -123,6 +131,7 @@ export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
|
|||||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
|
||||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
|
||||||
export type { HTTPValidationError } from './models/HTTPValidationError';
|
export type { HTTPValidationError } from './models/HTTPValidationError';
|
||||||
|
export type { NotificationChannel } from './models/NotificationChannel';
|
||||||
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
||||||
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
|
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
|
||||||
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
|
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
|
||||||
@@ -179,6 +188,7 @@ export type { ValidationError } from './models/ValidationError';
|
|||||||
|
|
||||||
export { AuthService } from './services/AuthService';
|
export { AuthService } from './services/AuthService';
|
||||||
export { BarcodeService } from './services/BarcodeService';
|
export { BarcodeService } from './services/BarcodeService';
|
||||||
|
export { BillingService } from './services/BillingService';
|
||||||
export { ClientService } from './services/ClientService';
|
export { ClientService } from './services/ClientService';
|
||||||
export { DealService } from './services/DealService';
|
export { DealService } from './services/DealService';
|
||||||
export { MarketplaceService } from './services/MarketplaceService';
|
export { MarketplaceService } from './services/MarketplaceService';
|
||||||
|
|||||||
8
src/client/models/BillPaymentStatus.ts
Normal file
8
src/client/models/BillPaymentStatus.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type BillPaymentStatus = {
|
||||||
|
payed: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/BillStatusUpdateRequest.ts
Normal file
12
src/client/models/BillStatusUpdateRequest.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BillPaymentStatus } from './BillPaymentStatus';
|
||||||
|
import type { NotificationChannel } from './NotificationChannel';
|
||||||
|
export type BillStatusUpdateRequest = {
|
||||||
|
listenerTransactionId: number;
|
||||||
|
channel: NotificationChannel;
|
||||||
|
info: BillPaymentStatus;
|
||||||
|
};
|
||||||
|
|
||||||
8
src/client/models/CreateDealBillRequest.ts
Normal file
8
src/client/models/CreateDealBillRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateDealBillRequest = {
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreateDealBillResponse.ts
Normal file
9
src/client/models/CreateDealBillResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateDealBillResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/DealBillRequestSchema.ts
Normal file
12
src/client/models/DealBillRequestSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealBillRequestSchema = {
|
||||||
|
dealId: number;
|
||||||
|
createdAt: string;
|
||||||
|
paid: boolean;
|
||||||
|
pdfUrl: (string | null);
|
||||||
|
invoiceNumber: (string | null);
|
||||||
|
};
|
||||||
|
|
||||||
8
src/client/models/DealCreateGuestUrlRequest.ts
Normal file
8
src/client/models/DealCreateGuestUrlRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealCreateGuestUrlRequest = {
|
||||||
|
dealId: number;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/DealCreateGuestUrlResponse.ts
Normal file
10
src/client/models/DealCreateGuestUrlResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DealCreateGuestUrlResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import type { ClientSchema } from './ClientSchema';
|
import type { ClientSchema } from './ClientSchema';
|
||||||
|
import type { DealBillRequestSchema } from './DealBillRequestSchema';
|
||||||
import type { DealProductSchema } from './DealProductSchema';
|
import type { DealProductSchema } from './DealProductSchema';
|
||||||
import type { DealServiceSchema } from './DealServiceSchema';
|
import type { DealServiceSchema } from './DealServiceSchema';
|
||||||
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
|
import type { DealStatusHistorySchema } from './DealStatusHistorySchema';
|
||||||
@@ -18,8 +19,10 @@ export type DealSchema = {
|
|||||||
statusHistory: Array<DealStatusHistorySchema>;
|
statusHistory: Array<DealStatusHistorySchema>;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
isCompleted: boolean;
|
isCompleted: boolean;
|
||||||
|
isLocked: boolean;
|
||||||
client: ClientSchema;
|
client: ClientSchema;
|
||||||
comment: string;
|
comment: string;
|
||||||
shippingWarehouse?: (ShippingWarehouseSchema | string | null);
|
shippingWarehouse?: (ShippingWarehouseSchema | string | null);
|
||||||
|
billRequest?: (DealBillRequestSchema | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
9
src/client/models/GetDealBillById.ts
Normal file
9
src/client/models/GetDealBillById.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { DealBillRequestSchema } from './DealBillRequestSchema';
|
||||||
|
export type GetDealBillById = {
|
||||||
|
dealBill: DealBillRequestSchema;
|
||||||
|
};
|
||||||
|
|
||||||
5
src/client/models/NotificationChannel.ts
Normal file
5
src/client/models/NotificationChannel.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type NotificationChannel = 'PAYMENT_DETAILS' | 'PAYMENT_VERIFICATION';
|
||||||
74
src/client/services/BillingService.ts
Normal file
74
src/client/services/BillingService.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BillStatusUpdateRequest } from '../models/BillStatusUpdateRequest';
|
||||||
|
import type { CreateDealBillRequest } from '../models/CreateDealBillRequest';
|
||||||
|
import type { CreateDealBillResponse } from '../models/CreateDealBillResponse';
|
||||||
|
import type { GetDealBillById } from '../models/GetDealBillById';
|
||||||
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
|
import { request as __request } from '../core/request';
|
||||||
|
export class BillingService {
|
||||||
|
/**
|
||||||
|
* Webhook
|
||||||
|
* @returns any Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static webhookBillingWebhookPost({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: BillStatusUpdateRequest,
|
||||||
|
}): CancelablePromise<any> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/billing/webhook',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Deal Bill
|
||||||
|
* @returns CreateDealBillResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createDealBill({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreateDealBillRequest,
|
||||||
|
}): CancelablePromise<CreateDealBillResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/billing/create-deal-bill',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Deal Bill By Id
|
||||||
|
* @returns GetDealBillById Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getDealBillById({
|
||||||
|
dealId,
|
||||||
|
}: {
|
||||||
|
dealId: number,
|
||||||
|
}): CancelablePromise<GetDealBillById> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/billing/deal-bill-request/{deal_id}',
|
||||||
|
path: {
|
||||||
|
'deal_id': dealId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ import type { DealAddServicesRequest } from '../models/DealAddServicesRequest';
|
|||||||
import type { DealAddServicesResponse } from '../models/DealAddServicesResponse';
|
import type { DealAddServicesResponse } from '../models/DealAddServicesResponse';
|
||||||
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
|
import type { DealChangeStatusRequest } from '../models/DealChangeStatusRequest';
|
||||||
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
|
import type { DealChangeStatusResponse } from '../models/DealChangeStatusResponse';
|
||||||
|
import type { DealCreateGuestUrlRequest } from '../models/DealCreateGuestUrlRequest';
|
||||||
|
import type { DealCreateGuestUrlResponse } from '../models/DealCreateGuestUrlResponse';
|
||||||
import type { DealCreateRequest } from '../models/DealCreateRequest';
|
import type { DealCreateRequest } from '../models/DealCreateRequest';
|
||||||
import type { DealDeleteProductRequest } from '../models/DealDeleteProductRequest';
|
import type { DealDeleteProductRequest } from '../models/DealDeleteProductRequest';
|
||||||
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
|
import type { DealDeleteProductResponse } from '../models/DealDeleteProductResponse';
|
||||||
@@ -230,6 +232,26 @@ export class DealService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Create Guest Url
|
||||||
|
* @returns DealCreateGuestUrlResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createDealGuestUrl({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: DealCreateGuestUrlRequest,
|
||||||
|
}): CancelablePromise<DealCreateGuestUrlResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/deal/create-guest-url',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Services Add
|
* Services Add
|
||||||
* @returns DealAddServicesResponse Successful Response
|
* @returns DealAddServicesResponse Successful Response
|
||||||
|
|||||||
54
src/components/ButtonCopy/ButtonCopy.tsx
Normal file
54
src/components/ButtonCopy/ButtonCopy.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {Button, rem, Tooltip} from '@mantine/core';
|
||||||
|
import {IconCheck, IconCopy} from '@tabler/icons-react';
|
||||||
|
import {FC} from "react";
|
||||||
|
import {useClipboard} from "@mantine/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: string;
|
||||||
|
value: string
|
||||||
|
onCopiedLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ButtonCopy: FC<Props> = ({children, onCopiedLabel, value}) => {
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label={onCopiedLabel}
|
||||||
|
offset={5}
|
||||||
|
position="bottom"
|
||||||
|
radius="xl"
|
||||||
|
transitionProps={{duration: 100, transition: 'slide-down'}}
|
||||||
|
opened={clipboard.copied}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
rightSection={
|
||||||
|
clipboard.copied ? (
|
||||||
|
<IconCheck
|
||||||
|
style={{width: rem(20), height: rem(20)}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconCopy
|
||||||
|
style={{width: rem(20), height: rem(20)}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
paddingRight: rem(14),
|
||||||
|
},
|
||||||
|
section: {marginLeft: rem(22)},
|
||||||
|
}}
|
||||||
|
onClick={() => clipboard.copy(value)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default ButtonCopy;
|
||||||
51
src/components/ButtonCopyControlled/ButtonCopyControlled.tsx
Normal file
51
src/components/ButtonCopyControlled/ButtonCopyControlled.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {Button, rem, Tooltip} from '@mantine/core';
|
||||||
|
import {IconCheck, IconCopy} from '@tabler/icons-react';
|
||||||
|
import {FC} from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: string;
|
||||||
|
onCopyClick: () => void;
|
||||||
|
onCopiedLabel: string;
|
||||||
|
copied: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ButtonCopyControlled: FC<Props> = ({children, onCopiedLabel, onCopyClick, copied}) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
label={onCopiedLabel}
|
||||||
|
offset={5}
|
||||||
|
position="bottom"
|
||||||
|
radius="xl"
|
||||||
|
transitionProps={{duration: 100, transition: 'slide-down'}}
|
||||||
|
opened={copied}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
rightSection={
|
||||||
|
copied ? (
|
||||||
|
<IconCheck
|
||||||
|
style={{width: rem(20), height: rem(20)}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconCopy
|
||||||
|
style={{width: rem(20), height: rem(20)}}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
paddingRight: rem(14),
|
||||||
|
},
|
||||||
|
section: {marginLeft: rem(22)},
|
||||||
|
}}
|
||||||
|
onClick={onCopyClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
|
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
|
||||||
|
import {jwtDecode, JwtPayload} from "jwt-decode";
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
isAuthorized: boolean;
|
isAuthorized: boolean;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
isGuest: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = (): AuthState => {
|
const initialState = (): AuthState => {
|
||||||
@@ -12,7 +14,8 @@ const initialState = (): AuthState => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
isAuthorized: false
|
isAuthorized: false,
|
||||||
|
isGuest: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,8 +24,19 @@ const authSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
login: (state, action: PayloadAction<{ accessToken: string }>) => {
|
login: (state, action: PayloadAction<{ accessToken: string }>) => {
|
||||||
|
try {
|
||||||
|
const {sub} = jwtDecode<JwtPayload>(action.payload.accessToken);
|
||||||
state.accessToken = action.payload.accessToken;
|
state.accessToken = action.payload.accessToken;
|
||||||
state.isAuthorized = true;
|
state.isAuthorized = true;
|
||||||
|
if (sub === "guest")
|
||||||
|
state.isGuest = true;
|
||||||
|
} catch (_) {
|
||||||
|
const url = window.location.href;
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
urlObj.search = '';
|
||||||
|
history.replaceState(null, '', urlObj);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
logout: (state) => {
|
logout: (state) => {
|
||||||
state.isAuthorized = false;
|
state.isAuthorized = false;
|
||||||
|
|||||||
1
src/pages/DealPage/index.ts
Normal file
1
src/pages/DealPage/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {DealPage} from './ui/DealPage'
|
||||||
38
src/pages/DealPage/ui/DealPage.tsx
Normal file
38
src/pages/DealPage/ui/DealPage.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import {useParams} from "@tanstack/react-router";
|
||||||
|
import {DealPageContextProvider, useDealPageContext} from "../../LeadsPage/contexts/DealPageContext.tsx";
|
||||||
|
import ProductAndServiceTab from "../../LeadsPage/tabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||||
|
import {FC, useEffect} from "react";
|
||||||
|
import {DealService} from "../../../client";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
dealId: number;
|
||||||
|
}
|
||||||
|
const DealPageContent: FC<Props> = ({dealId}) => {
|
||||||
|
const {setSelectedDeal} = useDealPageContext();
|
||||||
|
useEffect(() => {
|
||||||
|
DealService.getDealById({dealId}).then(deal => {
|
||||||
|
setSelectedDeal(deal);
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<ProductAndServiceTab/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const DealPageWrapper: FC<{ children: React.ReactNode }> = ({children}) => {
|
||||||
|
return (
|
||||||
|
<DealPageContextProvider>
|
||||||
|
{children}
|
||||||
|
</DealPageContextProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export const DealPage = () => {
|
||||||
|
const {dealId} = useParams({strict: false});
|
||||||
|
return (
|
||||||
|
|
||||||
|
|
||||||
|
<DealPageWrapper>
|
||||||
|
<DealPageContent dealId={parseInt(dealId || "-1")}/>
|
||||||
|
</DealPageWrapper>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import ServiceWithPriceInput from "../../../../components/ServiceWithPriceInput/
|
|||||||
import {isNumber} from "lodash";
|
import {isNumber} from "lodash";
|
||||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||||
import {IconTrash} from "@tabler/icons-react";
|
import {IconTrash} from "@tabler/icons-react";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../../redux/store.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -13,6 +15,8 @@ type RestProps = {
|
|||||||
type Props = BaseFormInputProps<DealProductServiceSchema[]> & RestProps;
|
type Props = BaseFormInputProps<DealProductServiceSchema[]> & RestProps;
|
||||||
const DealProductServiceTable: FC<Props> = (props: Props) => {
|
const DealProductServiceTable: FC<Props> = (props: Props) => {
|
||||||
const {value, onChange, quantity, error} = props;
|
const {value, onChange, quantity, error} = props;
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const [innerValue, setInnerValue] = useState<Partial<DealProductServiceSchema>[]>(value || []);
|
const [innerValue, setInnerValue] = useState<Partial<DealProductServiceSchema>[]>(value || []);
|
||||||
const onServiceChange = (idx: number, value: ServiceSchema) => {
|
const onServiceChange = (idx: number, value: ServiceSchema) => {
|
||||||
setInnerValue(oldValue => oldValue.map((item, i) => i === idx ? {...item, service: value} : item));
|
setInnerValue(oldValue => oldValue.map((item, i) => i === idx ? {...item, service: value} : item));
|
||||||
@@ -68,8 +72,8 @@ const DealProductServiceTable: FC<Props> = (props: Props) => {
|
|||||||
placeholder: "Введите стоимость",
|
placeholder: "Введите стоимость",
|
||||||
hideControls: true,
|
hideControls: true,
|
||||||
style: {width: "100%"},
|
style: {width: "100%"},
|
||||||
suffix: "₽"
|
suffix: "₽",
|
||||||
|
disabled: authState.isGuest
|
||||||
}}
|
}}
|
||||||
containerProps={{w: "100%"}}
|
containerProps={{w: "100%"}}
|
||||||
quantity={quantity}
|
quantity={quantity}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import {notifications} from "../../../../../shared/lib/notifications.ts";
|
|||||||
import {useQueryClient} from "@tanstack/react-query";
|
import {useQueryClient} from "@tanstack/react-query";
|
||||||
import ShippingWarehouseAutocomplete
|
import ShippingWarehouseAutocomplete
|
||||||
from "../../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
from "../../../../../components/Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||||
|
import {ButtonCopyControlled} from "../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
||||||
|
import {useClipboard} from "@mantine/hooks";
|
||||||
|
import ButtonCopy from "../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
deal: DealSchema
|
deal: DealSchema
|
||||||
@@ -18,6 +21,7 @@ type FormType = Omit<DealSchema, 'statusHistory' | 'services' | 'products'>
|
|||||||
|
|
||||||
const Content: FC<Props> = ({deal}) => {
|
const Content: FC<Props> = ({deal}) => {
|
||||||
const {setSelectedDeal} = useDealPageContext();
|
const {setSelectedDeal} = useDealPageContext();
|
||||||
|
const clipboard = useClipboard();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const initialValues: FormType = deal;
|
const initialValues: FormType = deal;
|
||||||
@@ -68,6 +72,20 @@ const Content: FC<Props> = ({deal}) => {
|
|||||||
return !["string", "null", "undefined"].includes((typeof value));
|
return !["string", "null", "undefined"].includes((typeof value));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onCopyGuestUrlClick = () => {
|
||||||
|
DealService.createDealGuestUrl({
|
||||||
|
requestBody: {
|
||||||
|
dealId: deal.id
|
||||||
|
}
|
||||||
|
}).then(({ok, message, url}) => {
|
||||||
|
if (!ok)
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
clipboard.copy(
|
||||||
|
`${window.location.origin}/${url}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
|
||||||
<Flex direction={'column'}>
|
<Flex direction={'column'}>
|
||||||
@@ -138,6 +156,30 @@ const Content: FC<Props> = ({deal}) => {
|
|||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Flex mt={'md'} gap={rem(10)} align={'center'} justify={'flex-end'}>
|
<Flex mt={'md'} gap={rem(10)} align={'center'} justify={'flex-end'}>
|
||||||
<Flex align={'center'} gap={rem(10)} justify={'center'}>
|
<Flex align={'center'} gap={rem(10)} justify={'center'}>
|
||||||
|
|
||||||
|
<Flex gap={rem(10)}>
|
||||||
|
{(deal.billRequest && deal.billRequest.pdfUrl) &&
|
||||||
|
<ButtonCopy
|
||||||
|
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||||
|
value={deal.billRequest.pdfUrl}
|
||||||
|
>
|
||||||
|
Ссылка на оплату
|
||||||
|
</ButtonCopy>
|
||||||
|
}
|
||||||
|
<ButtonCopyControlled
|
||||||
|
onCopyClick={onCopyGuestUrlClick}
|
||||||
|
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||||
|
copied={clipboard.copied}>
|
||||||
|
Ссылка на редактирование
|
||||||
|
</ButtonCopyControlled>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={rem(10)}>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
label={"Оплачен"}
|
||||||
|
checked={deal.billRequest?.paid || false}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={"Сделка завершена"}
|
label={"Сделка завершена"}
|
||||||
{...form.getInputProps('isCompleted')}
|
{...form.getInputProps('isCompleted')}
|
||||||
@@ -148,6 +190,8 @@ const Content: FC<Props> = ({deal}) => {
|
|||||||
{...form.getInputProps('isDeleted')}
|
{...form.getInputProps('isDeleted')}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
</Flex>
|
||||||
<Divider
|
<Divider
|
||||||
orientation={'vertical'}
|
orientation={'vertical'}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {useForm} from "@mantine/form";
|
|||||||
import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core";
|
import {ComboboxItem, ComboboxItemGroup, NumberInput, OptionsFilter} from "@mantine/core";
|
||||||
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
||||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../redux/store.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
serviceIds?: number[];
|
serviceIds?: number[];
|
||||||
@@ -15,6 +17,8 @@ const AddDealServiceModal = ({
|
|||||||
id,
|
id,
|
||||||
innerProps
|
innerProps
|
||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const isEditing = 'element' in innerProps;
|
const isEditing = 'element' in innerProps;
|
||||||
const form = useForm<Partial<DealServiceSchema>>({
|
const form = useForm<Partial<DealServiceSchema>>({
|
||||||
initialValues: isEditing ? innerProps.element : {
|
initialValues: isEditing ? innerProps.element : {
|
||||||
@@ -62,7 +66,8 @@ const AddDealServiceModal = ({
|
|||||||
...form.getInputProps('price'),
|
...form.getInputProps('price'),
|
||||||
label: "Цена",
|
label: "Цена",
|
||||||
placeholder: "Введите цену",
|
placeholder: "Введите цену",
|
||||||
style: {width: '100%'}
|
style: {width: '100%'},
|
||||||
|
disabled: authState.isGuest
|
||||||
}}
|
}}
|
||||||
quantity={form.values.quantity || 1}
|
quantity={form.values.quantity || 1}
|
||||||
containerProps={{
|
containerProps={{
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {isNil, isNumber} from "lodash";
|
|||||||
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
import ServiceWithPriceInput from "../../../components/ServiceWithPriceInput/ServiceWithPriceInput.tsx";
|
||||||
import {Flex} from "@mantine/core";
|
import {Flex} from "@mantine/core";
|
||||||
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
import {ServiceType} from "../../../shared/enums/ServiceType.ts";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../redux/store.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -17,6 +19,8 @@ const ProductServiceFormModal = ({
|
|||||||
context,
|
context,
|
||||||
id, innerProps
|
id, innerProps
|
||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const isEditing = 'onChange' in innerProps;
|
const isEditing = 'onChange' in innerProps;
|
||||||
const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : {
|
const initialValues: Partial<DealProductServiceSchema> = isEditing ? innerProps.element : {
|
||||||
service: undefined,
|
service: undefined,
|
||||||
@@ -57,8 +61,8 @@ const ProductServiceFormModal = ({
|
|||||||
...form.getInputProps('price'),
|
...form.getInputProps('price'),
|
||||||
label: "Цена",
|
label: "Цена",
|
||||||
placeholder: "Введите цену",
|
placeholder: "Введите цену",
|
||||||
style: {width: "100%"}
|
style: {width: "100%"},
|
||||||
|
disabled: authState.isGuest
|
||||||
}}
|
}}
|
||||||
filterType={ServiceType.PRODUCT_SERVICE}
|
filterType={ServiceType.PRODUCT_SERVICE}
|
||||||
containerProps={{
|
containerProps={{
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
.products-list {
|
.products-list {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import {FC} from "react";
|
import {FC} from "react";
|
||||||
import styles from './ProductAndServiceTab.module.css';
|
import styles from './ProductAndServiceTab.module.css';
|
||||||
import ProductView from "./components/ProductView/ProductView.tsx";
|
import ProductView from "./components/ProductView/ProductView.tsx";
|
||||||
import {Button, Flex, ScrollArea, Title} from "@mantine/core";
|
import {Button, Divider, Flex, rem, ScrollArea, Text, Title} from "@mantine/core";
|
||||||
import DealServicesTable from "./components/DealServicesTable/DealServicesTable.tsx";
|
import DealServicesTable from "./components/DealServicesTable/DealServicesTable.tsx";
|
||||||
import useDealProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
|
import useDealProductAndServiceTabState from "./hooks/useProductAndServiceTabState.tsx";
|
||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import {DealProductSchema, DealService, GetServiceKitSchema} from "../../../../client";
|
import {
|
||||||
|
BillingService,
|
||||||
|
DealProductSchema,
|
||||||
|
DealService,
|
||||||
|
GetServiceKitSchema,
|
||||||
|
ProductSchema,
|
||||||
|
ProductService
|
||||||
|
} from "../../../../client";
|
||||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||||
|
import {CreateProductRequest} from "../../../ProductsPage/types.ts";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
const ProductAndServiceTab: FC = () => {
|
const ProductAndServiceTab: FC = () => {
|
||||||
const {dealState, dealServicesState, dealProductsState} = useDealProductAndServiceTabState();
|
const {dealState, dealServicesState, dealProductsState} = useDealProductAndServiceTabState();
|
||||||
|
|
||||||
const onCreateProductClick = () => {
|
const onAddProductClick = () => {
|
||||||
if (!dealProductsState.onCreate || !dealState.deal) return;
|
if (!dealProductsState.onCreate || !dealState.deal) return;
|
||||||
const productIds = dealState.deal.products.map(product => product.product.id);
|
const productIds = dealState.deal.products.map(product => product.product.id);
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
@@ -88,14 +97,79 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
await dealState.refetch();
|
await dealState.refetch();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onCreateProduct = (newProduct: CreateProductRequest) => {
|
||||||
|
ProductService.createProduct({
|
||||||
|
requestBody: newProduct
|
||||||
|
}).then(({ok, message}) => {
|
||||||
|
notifications.guess(ok, {message: message});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const onCreateProductClick = () => {
|
||||||
|
if (!dealState.deal) return;
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "createProduct",
|
||||||
|
title: 'Создание товара',
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
clientId: dealState.deal.clientId,
|
||||||
|
onCreate: onCreateProduct
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const onProductEdit = (product: ProductSchema) => {
|
||||||
|
ProductService.updateProduct({requestBody: {product}})
|
||||||
|
.then(async ({ok, message}) => {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
if (!ok) return;
|
||||||
|
await dealState.refetch();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCreateBillClick = () => {
|
||||||
|
if (!dealState.deal) return;
|
||||||
|
const dealId = dealState.deal.id;
|
||||||
|
modals.openConfirmModal({
|
||||||
|
title: "Выставление счета",
|
||||||
|
size: "xl",
|
||||||
|
children:
|
||||||
|
<Text style={{textAlign: "justify"}}>
|
||||||
|
Создание заявки на выставление счета, подтвержденное нажатием кнопки "Выставить", заблокирует
|
||||||
|
возможность
|
||||||
|
редактирования товаров и услуг сделки. Пожалуйста, проверьте всю информацию на точность и полноту
|
||||||
|
перед подтверждением.
|
||||||
|
</Text>,
|
||||||
|
onConfirm: () => {
|
||||||
|
BillingService.createDealBill({
|
||||||
|
requestBody: {
|
||||||
|
dealId
|
||||||
|
}
|
||||||
|
}).then(async ({ok, message}) => {
|
||||||
|
notifications.guess(ok, {message});
|
||||||
|
if (ok) notifications.success({message: "Ссылка на оплату доступна во вкладе общее"});
|
||||||
|
await dealState.refetch();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
confirm: "Выставить",
|
||||||
|
cancel: "Отмена"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles['container']}>
|
<div
|
||||||
|
className={
|
||||||
|
classNames(styles['container'],
|
||||||
|
dealState.deal?.billRequest && styles['container-disabled']
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
|
||||||
<div className={styles['products-list']}>
|
<div className={styles['products-list']}>
|
||||||
<ScrollArea offsetScrollbars>
|
<ScrollArea offsetScrollbars>
|
||||||
|
|
||||||
{dealState.deal?.products.map(product => (
|
{dealState.deal?.products.map(product => (
|
||||||
<ProductView
|
<ProductView
|
||||||
|
onProductEdit={onProductEdit}
|
||||||
onKitAdd={onKitAdd}
|
onKitAdd={onKitAdd}
|
||||||
onCopyServices={onCopyServicesClick}
|
onCopyServices={onCopyServicesClick}
|
||||||
key={product.product.id}
|
key={product.product.id}
|
||||||
@@ -113,12 +187,25 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
onKitAdd={onDealKitAdd}
|
onKitAdd={onDealKitAdd}
|
||||||
{...dealServicesState}
|
{...dealServicesState}
|
||||||
/>
|
/>
|
||||||
|
<Divider my={rem(15)}/>
|
||||||
<div className={styles['deal-container-buttons']}>
|
<div className={styles['deal-container-buttons']}>
|
||||||
<Button
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
fullWidth
|
||||||
onClick={onCreateProductClick}
|
onClick={onCreateProductClick}
|
||||||
|
>Создать товар</Button>
|
||||||
|
<Button
|
||||||
|
onClick={onAddProductClick}
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
fullWidth>Добавить товар</Button>
|
fullWidth>Добавить товар</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<Divider my={rem(15)}/>
|
||||||
|
<div className={styles['deal-container-buttons']}>
|
||||||
|
<Button
|
||||||
|
onClick={onCreateBillClick}
|
||||||
|
variant={"default"}
|
||||||
|
fullWidth>Выставить счет</Button>
|
||||||
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={"column"} className={styles['deal-container-wrapper']}>
|
<Flex direction={"column"} className={styles['deal-container-wrapper']}>
|
||||||
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice().toLocaleString("ru")}₽</Title>
|
<Title order={3}>Общая стоимость всех услуг: {getTotalPrice().toLocaleString("ru")}₽</Title>
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ import {modals} from "@mantine/modals";
|
|||||||
import {isNumber} from "lodash";
|
import {isNumber} from "lodash";
|
||||||
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
||||||
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
onKitAdd?: (kit: GetServiceKitSchema) => void
|
onKitAdd?: (kit: GetServiceKitSchema) => void
|
||||||
};
|
};
|
||||||
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
|
type Props = CRUDTableProps<DealServiceSchema> & RestProps;
|
||||||
const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKitAdd}) => {
|
const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKitAdd}) => {
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>();
|
const [currentService, setCurrentService] = useState<DealServiceSchema | undefined>();
|
||||||
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
const [employeesModalVisible, setEmployeesModalVisible] = useState(false);
|
||||||
@@ -118,11 +121,13 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
|||||||
<IconTrash/>
|
<IconTrash/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{!authState.isGuest &&
|
||||||
<Tooltip label="Сотрудники">
|
<Tooltip label="Сотрудники">
|
||||||
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
|
<ActionIcon onClick={() => onEmployeeClick(service)} variant={"default"}>
|
||||||
<IconUsersGroup/>
|
<IconUsersGroup/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
}
|
||||||
<Text
|
<Text
|
||||||
flex={1}
|
flex={1}
|
||||||
>{service.service.name}</Text>
|
>{service.service.name}</Text>
|
||||||
@@ -135,6 +140,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
|||||||
onChange={event => isNumber(event) && onPriceChange(service, event)}
|
onChange={event => isNumber(event) && onPriceChange(service, event)}
|
||||||
suffix={"₽"}
|
suffix={"₽"}
|
||||||
value={service.price}
|
value={service.price}
|
||||||
|
disabled={authState.isGuest}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
@@ -146,7 +152,7 @@ const DealServicesTable: FC<Props> = ({items, onDelete, onCreate, onChange, onKi
|
|||||||
order={3}
|
order={3}
|
||||||
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}₽</Title>
|
>Итог: {items.reduce((acc, item) => acc + (item.price * item.quantity), 0)}₽</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex direction={"column"} gap={rem(10)} pb={rem(10)} mt={"auto"}>
|
<Flex direction={"column"} gap={rem(10)} mt={"auto"}>
|
||||||
<Button
|
<Button
|
||||||
onClick={onCreateClick}
|
onClick={onCreateClick}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {ActionIcon, Button, Flex, Modal, rem, Tooltip} from "@mantine/core";
|
|||||||
import {IconEdit, IconTrash, IconUsersGroup} from "@tabler/icons-react";
|
import {IconEdit, IconTrash, IconUsersGroup} from "@tabler/icons-react";
|
||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
import SimpleUsersTable from "../../../../components/SimpleUsersTable/SimpleUsersTable.tsx";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -25,6 +27,8 @@ const ProductServicesTable: FC<Props> = ({
|
|||||||
onCopyServices,
|
onCopyServices,
|
||||||
onKitAdd
|
onKitAdd
|
||||||
}) => {
|
}) => {
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const columns = useProductServicesTableColumns({data: items, quantity});
|
const columns = useProductServicesTableColumns({data: items, quantity});
|
||||||
const serviceIds = items.map(service => service.service.id);
|
const serviceIds = items.map(service => service.service.id);
|
||||||
|
|
||||||
@@ -124,13 +128,15 @@ const ProductServicesTable: FC<Props> = ({
|
|||||||
<IconEdit/>
|
<IconEdit/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{!authState.isGuest &&
|
||||||
<Tooltip label="Сотрудники">
|
<Tooltip label="Сотрудники">
|
||||||
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
|
<ActionIcon onClick={() => onEmployeeClick(row.original)} variant={"default"}>
|
||||||
<IconUsersGroup/>
|
<IconUsersGroup/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
),
|
||||||
} as MRT_TableOptions<DealProductServiceSchema>}
|
} as MRT_TableOptions<DealProductServiceSchema>}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {useMemo} from "react";
|
import {useMemo} from "react";
|
||||||
import {MRT_ColumnDef} from "mantine-react-table";
|
import {MRT_ColumnDef} from "mantine-react-table";
|
||||||
import {DealProductServiceSchema} from "../../../../../../client";
|
import {DealProductServiceSchema} from "../../../../../../client";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {RootState} from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: DealProductServiceSchema[];
|
data: DealProductServiceSchema[];
|
||||||
@@ -8,14 +10,19 @@ type Props = {
|
|||||||
}
|
}
|
||||||
const useProductServicesTableColumns = (props: Props) => {
|
const useProductServicesTableColumns = (props: Props) => {
|
||||||
const {data, quantity} = props;
|
const {data, quantity} = props;
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
const totalPrice = useMemo(() => data.reduce((acc, row) => acc + (row.price * quantity), 0), [data, quantity]);
|
const totalPrice = useMemo(() => data.reduce((acc, row) => acc + (row.price * quantity), 0), [data, quantity]);
|
||||||
const totalCost = useMemo(() => data.reduce((acc, row) => acc + ((row.service.cost || 0) * quantity), 0), [data, quantity]);
|
const totalCost = useMemo(() => data.reduce((acc, row) => acc + ((row.service.cost || 0) * quantity), 0), [data, quantity]);
|
||||||
|
const hideGuestColumns = [
|
||||||
|
"service.cost"
|
||||||
|
]
|
||||||
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(() => [
|
return useMemo<MRT_ColumnDef<DealProductServiceSchema>[]>(() => [
|
||||||
{
|
{
|
||||||
accessorKey: "service.name",
|
accessorKey: "service.name",
|
||||||
header: "Услуга",
|
header: "Услуга",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
enableHiding: true,
|
||||||
accessorKey: "service.cost",
|
accessorKey: "service.cost",
|
||||||
header: "Себестоимость",
|
header: "Себестоимость",
|
||||||
Footer: () => <>Итоговая себестоимость: {totalCost.toLocaleString("ru")}₽</>,
|
Footer: () => <>Итоговая себестоимость: {totalCost.toLocaleString("ru")}₽</>,
|
||||||
@@ -25,7 +32,7 @@ const useProductServicesTableColumns = (props: Props) => {
|
|||||||
header: "Цена",
|
header: "Цена",
|
||||||
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
Footer: () => <>Итог: {totalPrice.toLocaleString("ru")}₽</>,
|
||||||
}
|
}
|
||||||
], [totalPrice]);
|
], [totalPrice]).filter(columnDef => !(hideGuestColumns.includes(columnDef.accessorKey || "") && authState.isGuest));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useProductServicesTableColumns;
|
export default useProductServicesTableColumns;
|
||||||
@@ -9,7 +9,7 @@ import styles from './ProductView.module.css';
|
|||||||
import {ActionIcon, Flex, Image, NumberInput, rem, Spoiler, Text, Title, Tooltip} from '@mantine/core';
|
import {ActionIcon, Flex, Image, NumberInput, rem, Spoiler, Text, Title, Tooltip} from '@mantine/core';
|
||||||
import ProductServicesTable from "../ProductServicesTable/ProductServicesTable.tsx";
|
import ProductServicesTable from "../ProductServicesTable/ProductServicesTable.tsx";
|
||||||
import {isNil, isNumber} from "lodash";
|
import {isNil, isNumber} from "lodash";
|
||||||
import {IconBarcode, IconTrash} from "@tabler/icons-react";
|
import {IconBarcode, IconEdit, IconTrash} from "@tabler/icons-react";
|
||||||
import {modals} from "@mantine/modals";
|
import {modals} from "@mantine/modals";
|
||||||
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
import {ServiceType} from "../../../../../../shared/enums/ServiceType.ts";
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ type Props = {
|
|||||||
onDelete?: (item: DealProductSchema) => void
|
onDelete?: (item: DealProductSchema) => void
|
||||||
onCopyServices?: (item: DealProductSchema) => void;
|
onCopyServices?: (item: DealProductSchema) => void;
|
||||||
onKitAdd?: (item: DealProductSchema, kit: GetServiceKitSchema) => void;
|
onKitAdd?: (item: DealProductSchema, kit: GetServiceKitSchema) => void;
|
||||||
|
onProductEdit: (product: ProductSchema) => void;
|
||||||
}
|
}
|
||||||
type ProductFieldNames = {
|
type ProductFieldNames = {
|
||||||
[K in keyof ProductSchema]: string
|
[K in keyof ProductSchema]: string
|
||||||
@@ -31,7 +32,14 @@ export const ProductFieldNames: Partial<ProductFieldNames> = {
|
|||||||
composition: "Состав",
|
composition: "Состав",
|
||||||
additionalInfo: "Доп. информация",
|
additionalInfo: "Доп. информация",
|
||||||
}
|
}
|
||||||
const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, onKitAdd}) => {
|
const ProductView: FC<Props> = ({
|
||||||
|
product,
|
||||||
|
onDelete,
|
||||||
|
onChange,
|
||||||
|
onCopyServices,
|
||||||
|
onKitAdd,
|
||||||
|
onProductEdit
|
||||||
|
}) => {
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
if (!onDelete) return;
|
if (!onDelete) return;
|
||||||
onDelete(product);
|
onDelete(product);
|
||||||
@@ -91,6 +99,18 @@ const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, on
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const onProductEditClick = () => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "createProduct",
|
||||||
|
title: 'Редактирование товара',
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
onChange: (newProduct) => onProductEdit(newProduct),
|
||||||
|
product: product.product,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles['container']}>
|
<div className={styles['container']}>
|
||||||
<div className={styles['data-container']}>
|
<div className={styles['data-container']}>
|
||||||
@@ -145,6 +165,14 @@ const ProductView: FC<Props> = ({product, onDelete, onChange, onCopyServices, on
|
|||||||
<IconBarcode/>
|
<IconBarcode/>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
onClick={onProductEditClick}
|
||||||
|
label="Редактировать товар">
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}>
|
||||||
|
<IconEdit/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip onClick={onDeleteClick} label="Удалить товар">
|
<Tooltip onClick={onDeleteClick} label="Удалить товар">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant={"default"}>
|
variant={"default"}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {AppShell, Flex, rem} from "@mantine/core";
|
|||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {RootState} from "../../redux/store.ts";
|
import {RootState} from "../../redux/store.ts";
|
||||||
import styles from './PageWrapper.module.css';
|
import styles from './PageWrapper.module.css';
|
||||||
import { Navbar } from "../../components/Navbar/Navbar.tsx";
|
import {Navbar} from "../../components/Navbar/Navbar.tsx";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -14,14 +14,14 @@ const PageWrapper: FC<Props> = ({children}) => {
|
|||||||
<AppShell
|
<AppShell
|
||||||
layout={"alt"}
|
layout={"alt"}
|
||||||
withBorder={false}
|
withBorder={false}
|
||||||
navbar={authState.isAuthorized ? {
|
navbar={(authState.isAuthorized && !authState.isGuest) ? {
|
||||||
width: "130px",
|
width: "130px",
|
||||||
breakpoint: "sm"
|
breakpoint: "sm"
|
||||||
} : undefined}
|
} : undefined}
|
||||||
>
|
>
|
||||||
|
|
||||||
<AppShell.Navbar>
|
<AppShell.Navbar>
|
||||||
{authState.isAuthorized &&
|
{(authState.isAuthorized && !authState.isGuest) &&
|
||||||
<Flex className={styles['main-container']} h={"100%"} w={"100%"}
|
<Flex className={styles['main-container']} h={"100%"} w={"100%"}
|
||||||
pl={rem(20)}
|
pl={rem(20)}
|
||||||
py={rem(20)}
|
py={rem(20)}
|
||||||
@@ -31,8 +31,16 @@ const PageWrapper: FC<Props> = ({children}) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
<AppShell.Main className={styles['main-container']}>
|
<AppShell.Main
|
||||||
<div className={styles['container']}>
|
style={
|
||||||
|
authState.isGuest ? {backgroundColor: "var(--mantine-color-dark-8)"} : {}
|
||||||
|
}
|
||||||
|
className={styles['main-container']}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={styles['container']}
|
||||||
|
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import {useMatch, useMatches} from "@tanstack/react-router";
|
import {useMatch, useMatches, useSearch} from "@tanstack/react-router";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {RootState} from "../../redux/store.ts";
|
import {RootState, useAppDispatch} from "../../redux/store.ts";
|
||||||
import {OpenAPI} from "../../client";
|
import {OpenAPI} from "../../client";
|
||||||
import PageWrapper from "../PageWrapper/PageWrapper.tsx";
|
import PageWrapper from "../PageWrapper/PageWrapper.tsx";
|
||||||
import {LoadingOverlay} from "@mantine/core";
|
import {LoadingOverlay} from "@mantine/core";
|
||||||
import {AnimatePresence} from "framer-motion";
|
import {AnimatePresence} from "framer-motion";
|
||||||
import AnimatedOutlet from "../../components/AnimatedOutlet/au.tsx";
|
import AnimatedOutlet from "../../components/AnimatedOutlet/au.tsx";
|
||||||
|
import {SearchParams} from "../../shared/lib/general.ts";
|
||||||
|
import {login} from "../../features/authSlice.ts";
|
||||||
|
|
||||||
const RootPage = () => {
|
const RootPage = () => {
|
||||||
|
|
||||||
|
const search: SearchParams = useSearch({strict: false});
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const matches = useMatches();
|
const matches = useMatches();
|
||||||
const match = useMatch({strict: false});
|
const match = useMatch({strict: false});
|
||||||
const nextMatchIndex = matches.findIndex((d) => d.id === match.id) + 1;
|
const nextMatchIndex = matches.findIndex((d) => d.id === match.id) + 1;
|
||||||
@@ -27,6 +32,10 @@ const RootPage = () => {
|
|||||||
rewriteLocalStorage();
|
rewriteLocalStorage();
|
||||||
setOpenApiToken();
|
setOpenApiToken();
|
||||||
}, [authState]);
|
}, [authState]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!search.accessToken) return;
|
||||||
|
dispatch(login({accessToken: search.accessToken.toString()}))
|
||||||
|
}, [search])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay visible={uiState.isLoading}/>
|
<LoadingOverlay visible={uiState.isLoading}/>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
|
import { Route as DealsDealIdImport } from './routes/deals.$dealId'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
@@ -73,6 +74,11 @@ const IndexLazyRoute = IndexLazyImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
|
} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
|
||||||
|
|
||||||
|
const DealsDealIdRoute = DealsDealIdImport.update({
|
||||||
|
path: '/deals/$dealId',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
// Populate the FileRoutesByPath interface
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
@@ -140,6 +146,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof TestLazyImport
|
preLoaderRoute: typeof TestLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/deals/$dealId': {
|
||||||
|
id: '/deals/$dealId'
|
||||||
|
path: '/deals/$dealId'
|
||||||
|
fullPath: '/deals/$dealId'
|
||||||
|
preLoaderRoute: typeof DealsDealIdImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +168,7 @@ export const routeTree = rootRoute.addChildren({
|
|||||||
ProductsLazyRoute,
|
ProductsLazyRoute,
|
||||||
ServicesLazyRoute,
|
ServicesLazyRoute,
|
||||||
TestLazyRoute,
|
TestLazyRoute,
|
||||||
|
DealsDealIdRoute,
|
||||||
})
|
})
|
||||||
|
|
||||||
/* prettier-ignore-end */
|
/* prettier-ignore-end */
|
||||||
@@ -173,7 +187,8 @@ export const routeTree = rootRoute.addChildren({
|
|||||||
"/login",
|
"/login",
|
||||||
"/products",
|
"/products",
|
||||||
"/services",
|
"/services",
|
||||||
"/test"
|
"/test",
|
||||||
|
"/deals/$dealId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/": {
|
"/": {
|
||||||
@@ -202,6 +217,9 @@ export const routeTree = rootRoute.addChildren({
|
|||||||
},
|
},
|
||||||
"/test": {
|
"/test": {
|
||||||
"filePath": "test.lazy.tsx"
|
"filePath": "test.lazy.tsx"
|
||||||
|
},
|
||||||
|
"/deals/$dealId": {
|
||||||
|
"filePath": "deals.$dealId.tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import {createRootRoute, redirect} from "@tanstack/react-router";
|
import {createRootRoute, redirect} from "@tanstack/react-router";
|
||||||
import RootPage from "../pages/RootPage/RootPage.tsx";
|
import RootPage from "../pages/RootPage/RootPage.tsx";
|
||||||
|
import {SearchParams} from "../shared/lib/general.ts";
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootPage,
|
component: RootPage,
|
||||||
beforeLoad: async ({location}) => {
|
beforeLoad: async ({location, search}) => {
|
||||||
const isAuthorized = JSON.parse(localStorage.getItem('authState') || '{}')['isAuthorized'];
|
const isAuthorized = JSON.parse(localStorage.getItem('authState') || '{}')['isAuthorized'];
|
||||||
|
const searchParams: SearchParams = search;
|
||||||
|
if (searchParams.accessToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isAuthorized && location.pathname !== '/login') {
|
if (!isAuthorized && location.pathname !== '/login') {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: '/login',
|
to: '/login',
|
||||||
|
|||||||
8
src/routes/deals.$dealId.tsx
Normal file
8
src/routes/deals.$dealId.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import {createFileRoute} from "@tanstack/react-router";
|
||||||
|
import {DealPage} from "../pages/DealPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/deals/$dealId")({
|
||||||
|
component: DealPage
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import {createLazyFileRoute} from "@tanstack/react-router";
|
import {createLazyFileRoute} from "@tanstack/react-router";
|
||||||
import DebouncedNumberInput from "../components/DebouncedNumberInput/DebouncedNumberInput.tsx";
|
|
||||||
import {useEffect, useState} from "react";
|
|
||||||
import {isNumber} from "lodash";
|
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/test')({
|
export const Route = createLazyFileRoute('/test')({
|
||||||
component: TestPage
|
component: TestPage
|
||||||
@@ -9,14 +6,8 @@ export const Route = createLazyFileRoute('/test')({
|
|||||||
|
|
||||||
|
|
||||||
function TestPage() {
|
function TestPage() {
|
||||||
const [value, setValue] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
}, [value]);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DebouncedNumberInput
|
|
||||||
onChange={(event) => isNumber(event) && setValue(event)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
7
src/shared/lib/general.ts
Normal file
7
src/shared/lib/general.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export type SearchParams = {
|
||||||
|
accessToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JwtPayload = {
|
||||||
|
sub: number | string
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user