diff --git a/src/client/index.ts b/src/client/index.ts index 927dbcb..0bc0782 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -42,8 +42,10 @@ export type { ClientCreateRequest } from './models/ClientCreateRequest'; export type { ClientCreateResponse } from './models/ClientCreateResponse'; export type { ClientDeleteRequest } from './models/ClientDeleteRequest'; export type { ClientDeleteResponse } from './models/ClientDeleteResponse'; +export type { ClientDetailedSchema } from './models/ClientDetailedSchema'; export type { ClientDetailsSchema } from './models/ClientDetailsSchema'; export type { ClientGetAllResponse } from './models/ClientGetAllResponse'; +export type { ClientGetResponse } from './models/ClientGetResponse'; export type { ClientSchema } from './models/ClientSchema'; export type { ClientUpdateDetailsRequest } from './models/ClientUpdateDetailsRequest'; export type { ClientUpdateRequest } from './models/ClientUpdateRequest'; @@ -71,6 +73,13 @@ export type { CreatePositionRequest } from './models/CreatePositionRequest'; export type { CreatePositionResponse } from './models/CreatePositionResponse'; export type { CreatePriceCategoryRequest } from './models/CreatePriceCategoryRequest'; export type { CreatePriceCategoryResponse } from './models/CreatePriceCategoryResponse'; +export type { CreateResidualBoxRequest } from './models/CreateResidualBoxRequest'; +export type { CreateResidualBoxResponse } from './models/CreateResidualBoxResponse'; +export type { CreateResidualPalletRequest } from './models/CreateResidualPalletRequest'; +export type { CreateResidualPalletResponse } from './models/CreateResidualPalletResponse'; +export type { CreateResidualProductRequest } from './models/CreateResidualProductRequest'; +export type { CreateResidualProductResponse } from './models/CreateResidualProductResponse'; +export type { CreateResidualProductSchema } from './models/CreateResidualProductSchema'; export type { CreateServiceKitSchema } from './models/CreateServiceKitSchema'; export type { CreateServicesKitRequest } from './models/CreateServicesKitRequest'; export type { CreateServicesKitResponse } from './models/CreateServicesKitResponse'; @@ -163,6 +172,9 @@ export type { DeletePositionRequest } from './models/DeletePositionRequest'; export type { DeletePositionResponse } from './models/DeletePositionResponse'; export type { DeletePriceCategoryRequest } from './models/DeletePriceCategoryRequest'; export type { DeletePriceCategoryResponse } from './models/DeletePriceCategoryResponse'; +export type { DeleteResidualBoxResponse } from './models/DeleteResidualBoxResponse'; +export type { DeleteResidualPalletResponse } from './models/DeleteResidualPalletResponse'; +export type { DeleteResidualProductResponse } from './models/DeleteResidualProductResponse'; export type { DeleteShiftResponse } from './models/DeleteShiftResponse'; export type { DeleteShippingProductResponse } from './models/DeleteShippingProductResponse'; export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest'; @@ -214,6 +226,8 @@ export type { GetProfitChartDataRequest } from './models/GetProfitChartDataReque export type { GetProfitChartDataResponse } from './models/GetProfitChartDataResponse'; export type { GetProfitTableDataRequest } from './models/GetProfitTableDataRequest'; export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResponse'; +export type { GetResidualBoxResponse } from './models/GetResidualBoxResponse'; +export type { GetResidualPalletResponse } from './models/GetResidualPalletResponse'; export type { GetServiceKitSchema } from './models/GetServiceKitSchema'; export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest'; export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse'; @@ -221,6 +235,8 @@ export type { GetTransactionTagsResponse } from './models/GetTransactionTagsResp export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse'; export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema'; export type { HTTPValidationError } from './models/HTTPValidationError'; +export type { LoadReceiptRequest } from './models/LoadReceiptRequest'; +export type { LoadReceiptResponse } from './models/LoadReceiptResponse'; export type { ManageEmployeeRequest } from './models/ManageEmployeeRequest'; export type { ManageEmployeeResponse } from './models/ManageEmployeeResponse'; export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema'; @@ -242,6 +258,7 @@ export type { PermissionSchema } from './models/PermissionSchema'; export type { PositionSchema } from './models/PositionSchema'; export type { ProductAddBarcodeRequest } from './models/ProductAddBarcodeRequest'; export type { ProductAddBarcodeResponse } from './models/ProductAddBarcodeResponse'; +export type { ProductAndQuantitySchema } from './models/ProductAndQuantitySchema'; export type { ProductCreateRequest } from './models/ProductCreateRequest'; export type { ProductCreateResponse } from './models/ProductCreateResponse'; export type { ProductDeleteBarcodeImageResponse } from './models/ProductDeleteBarcodeImageResponse'; @@ -262,6 +279,11 @@ export type { ProductUploadImageResponse } from './models/ProductUploadImageResp export type { ProfitChartDataItem } from './models/ProfitChartDataItem'; export type { ProfitTableDataItem } from './models/ProfitTableDataItem'; export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy'; +export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema'; +export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema'; +export type { ResidualBoxSchema } from './models/ResidualBoxSchema'; +export type { ResidualPalletSchema } from './models/ResidualPalletSchema'; +export type { ResidualProductSchema } from './models/ResidualProductSchema'; export type { RoleSchema } from './models/RoleSchema'; export type { ServiceCategoryPriceSchema } from './models/ServiceCategoryPriceSchema'; export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest'; @@ -310,6 +332,9 @@ export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest'; export type { UpdatePayRateResponse } from './models/UpdatePayRateResponse'; export type { UpdatePriceCategoryRequest } from './models/UpdatePriceCategoryRequest'; export type { UpdatePriceCategoryResponse } from './models/UpdatePriceCategoryResponse'; +export type { UpdateResidualProductRequest } from './models/UpdateResidualProductRequest'; +export type { UpdateResidualProductResponse } from './models/UpdateResidualProductResponse'; +export type { UpdateResidualProductSchema } from './models/UpdateResidualProductSchema'; export type { UpdateServiceKitSchema } from './models/UpdateServiceKitSchema'; export type { UpdateServicesKitRequest } from './models/UpdateServicesKitRequest'; export type { UpdateServicesKitResponse } from './models/UpdateServicesKitResponse'; @@ -345,6 +370,7 @@ export { MarketplaceService } from './services/MarketplaceService'; export { PayrollService } from './services/PayrollService'; export { PositionService } from './services/PositionService'; export { ProductService } from './services/ProductService'; +export { ResiduesService } from './services/ResiduesService'; export { RoleService } from './services/RoleService'; export { ServiceService } from './services/ServiceService'; export { ShippingService } from './services/ShippingService'; diff --git a/src/client/models/ClientDetailedSchema.ts b/src/client/models/ClientDetailedSchema.ts new file mode 100644 index 0000000..a183a2a --- /dev/null +++ b/src/client/models/ClientDetailedSchema.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema'; +import type { ClientDetailsSchema } from './ClientDetailsSchema'; +import type { ResidualBoxSchema } from './ResidualBoxSchema'; +import type { ResidualPalletSchema } from './ResidualPalletSchema'; +export type ClientDetailedSchema = { + id: number; + name: string; + companyName: string; + barcodeTemplate?: (BarcodeTemplateSchema | null); + comment?: (string | null); + details?: (ClientDetailsSchema | null); + pallets?: Array; + boxes?: Array; +}; + diff --git a/src/client/models/ClientGetResponse.ts b/src/client/models/ClientGetResponse.ts new file mode 100644 index 0000000..14667ce --- /dev/null +++ b/src/client/models/ClientGetResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ClientDetailedSchema } from './ClientDetailedSchema'; +export type ClientGetResponse = { + client: ClientDetailedSchema; +}; + diff --git a/src/client/models/CreateResidualBoxRequest.ts b/src/client/models/CreateResidualBoxRequest.ts new file mode 100644 index 0000000..03162ae --- /dev/null +++ b/src/client/models/CreateResidualBoxRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualBoxRequest = { + clientId: (number | null); + palletId: (number | null); +}; + diff --git a/src/client/models/CreateResidualBoxResponse.ts b/src/client/models/CreateResidualBoxResponse.ts new file mode 100644 index 0000000..005efbf --- /dev/null +++ b/src/client/models/CreateResidualBoxResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualBoxResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/CreateResidualPalletRequest.ts b/src/client/models/CreateResidualPalletRequest.ts new file mode 100644 index 0000000..08e5bfb --- /dev/null +++ b/src/client/models/CreateResidualPalletRequest.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualPalletRequest = { + clientId: number; +}; + diff --git a/src/client/models/CreateResidualPalletResponse.ts b/src/client/models/CreateResidualPalletResponse.ts new file mode 100644 index 0000000..cf0bdc1 --- /dev/null +++ b/src/client/models/CreateResidualPalletResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualPalletResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/CreateResidualProductRequest.ts b/src/client/models/CreateResidualProductRequest.ts new file mode 100644 index 0000000..13c7515 --- /dev/null +++ b/src/client/models/CreateResidualProductRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CreateResidualProductSchema } from './CreateResidualProductSchema'; +export type CreateResidualProductRequest = { + data: CreateResidualProductSchema; +}; + diff --git a/src/client/models/CreateResidualProductResponse.ts b/src/client/models/CreateResidualProductResponse.ts new file mode 100644 index 0000000..12ced0f --- /dev/null +++ b/src/client/models/CreateResidualProductResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualProductResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/CreateResidualProductSchema.ts b/src/client/models/CreateResidualProductSchema.ts new file mode 100644 index 0000000..3ac7ced --- /dev/null +++ b/src/client/models/CreateResidualProductSchema.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateResidualProductSchema = { + productId: (number | null); + quantity: (number | null); + palletId: (number | null); + boxId: (number | null); +}; + diff --git a/src/client/models/DealGeneralInfoSchema.ts b/src/client/models/DealGeneralInfoSchema.ts index 4d5906c..e468086 100644 --- a/src/client/models/DealGeneralInfoSchema.ts +++ b/src/client/models/DealGeneralInfoSchema.ts @@ -7,6 +7,7 @@ export type DealGeneralInfoSchema = { name: string; isDeleted: boolean; isCompleted: boolean; + isAccounted: boolean; comment: string; shippingWarehouse?: (string | null); deliveryDate?: (string | null); diff --git a/src/client/models/DeleteResidualBoxResponse.ts b/src/client/models/DeleteResidualBoxResponse.ts new file mode 100644 index 0000000..6040357 --- /dev/null +++ b/src/client/models/DeleteResidualBoxResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DeleteResidualBoxResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/DeleteResidualPalletResponse.ts b/src/client/models/DeleteResidualPalletResponse.ts new file mode 100644 index 0000000..b8faa29 --- /dev/null +++ b/src/client/models/DeleteResidualPalletResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DeleteResidualPalletResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/DeleteResidualProductResponse.ts b/src/client/models/DeleteResidualProductResponse.ts new file mode 100644 index 0000000..6557c5d --- /dev/null +++ b/src/client/models/DeleteResidualProductResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DeleteResidualProductResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/GetResidualBoxResponse.ts b/src/client/models/GetResidualBoxResponse.ts new file mode 100644 index 0000000..9ae66fa --- /dev/null +++ b/src/client/models/GetResidualBoxResponse.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ResidualBoxSchema } from './ResidualBoxSchema'; +export type GetResidualBoxResponse = { + box: ResidualBoxSchema; + clientId: number; +}; + diff --git a/src/client/models/GetResidualPalletResponse.ts b/src/client/models/GetResidualPalletResponse.ts new file mode 100644 index 0000000..c8daa88 --- /dev/null +++ b/src/client/models/GetResidualPalletResponse.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ResidualPalletSchema } from './ResidualPalletSchema'; +export type GetResidualPalletResponse = { + pallet: ResidualPalletSchema; + clientId: number; +}; + diff --git a/src/client/models/LoadReceiptRequest.ts b/src/client/models/LoadReceiptRequest.ts new file mode 100644 index 0000000..d7540d8 --- /dev/null +++ b/src/client/models/LoadReceiptRequest.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ReceiptBoxSchema } from './ReceiptBoxSchema'; +import type { ReceiptPalletSchema } from './ReceiptPalletSchema'; +export type LoadReceiptRequest = { + pallets: Array; + boxes: Array; + clientId: number; +}; + diff --git a/src/client/models/LoadReceiptResponse.ts b/src/client/models/LoadReceiptResponse.ts new file mode 100644 index 0000000..833f8f9 --- /dev/null +++ b/src/client/models/LoadReceiptResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type LoadReceiptResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/ProductAndQuantitySchema.ts b/src/client/models/ProductAndQuantitySchema.ts new file mode 100644 index 0000000..c92510f --- /dev/null +++ b/src/client/models/ProductAndQuantitySchema.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type ProductAndQuantitySchema = { + productId: (number | null); + quantity: (number | null); +}; + diff --git a/src/client/models/ReceiptBoxSchema.ts b/src/client/models/ReceiptBoxSchema.ts new file mode 100644 index 0000000..abade80 --- /dev/null +++ b/src/client/models/ReceiptBoxSchema.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProductAndQuantitySchema } from './ProductAndQuantitySchema'; +export type ReceiptBoxSchema = { + products: Array; +}; + diff --git a/src/client/models/ReceiptPalletSchema.ts b/src/client/models/ReceiptPalletSchema.ts new file mode 100644 index 0000000..45d179a --- /dev/null +++ b/src/client/models/ReceiptPalletSchema.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProductAndQuantitySchema } from './ProductAndQuantitySchema'; +import type { ReceiptBoxSchema } from './ReceiptBoxSchema'; +export type ReceiptPalletSchema = { + products: Array; + boxes: Array; +}; + diff --git a/src/client/models/ResidualBoxSchema.ts b/src/client/models/ResidualBoxSchema.ts new file mode 100644 index 0000000..13a9435 --- /dev/null +++ b/src/client/models/ResidualBoxSchema.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ResidualProductSchema } from './ResidualProductSchema'; +export type ResidualBoxSchema = { + id: number; + createdAt: string; + palletId: (number | null); + clientId: (number | null); + residualProducts: Array; +}; + diff --git a/src/client/models/ResidualPalletSchema.ts b/src/client/models/ResidualPalletSchema.ts new file mode 100644 index 0000000..7a147e5 --- /dev/null +++ b/src/client/models/ResidualPalletSchema.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ResidualBoxSchema } from './ResidualBoxSchema'; +import type { ResidualProductSchema } from './ResidualProductSchema'; +export type ResidualPalletSchema = { + id: number; + createdAt: string; + boxes: Array; + residualProducts: Array; +}; + diff --git a/src/client/models/ResidualProductSchema.ts b/src/client/models/ResidualProductSchema.ts new file mode 100644 index 0000000..8995365 --- /dev/null +++ b/src/client/models/ResidualProductSchema.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ProductSchema } from './ProductSchema'; +export type ResidualProductSchema = { + id: number; + quantity: number; + product: ProductSchema; + palletId: (number | null); + boxId: (number | null); +}; + diff --git a/src/client/models/UpdateResidualProductRequest.ts b/src/client/models/UpdateResidualProductRequest.ts new file mode 100644 index 0000000..2fd0654 --- /dev/null +++ b/src/client/models/UpdateResidualProductRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { UpdateResidualProductSchema } from './UpdateResidualProductSchema'; +export type UpdateResidualProductRequest = { + data: UpdateResidualProductSchema; +}; + diff --git a/src/client/models/UpdateResidualProductResponse.ts b/src/client/models/UpdateResidualProductResponse.ts new file mode 100644 index 0000000..4f6aa57 --- /dev/null +++ b/src/client/models/UpdateResidualProductResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateResidualProductResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/UpdateResidualProductSchema.ts b/src/client/models/UpdateResidualProductSchema.ts new file mode 100644 index 0000000..29e2526 --- /dev/null +++ b/src/client/models/UpdateResidualProductSchema.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateResidualProductSchema = { + productId: (number | null); + quantity: (number | null); +}; + diff --git a/src/client/services/ClientService.ts b/src/client/services/ClientService.ts index 4b2b1da..fccd9bb 100644 --- a/src/client/services/ClientService.ts +++ b/src/client/services/ClientService.ts @@ -7,6 +7,7 @@ import type { ClientCreateResponse } from '../models/ClientCreateResponse'; import type { ClientDeleteRequest } from '../models/ClientDeleteRequest'; import type { ClientDeleteResponse } from '../models/ClientDeleteResponse'; import type { ClientGetAllResponse } from '../models/ClientGetAllResponse'; +import type { ClientGetResponse } from '../models/ClientGetResponse'; import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest'; import type { ClientUpdateRequest } from '../models/ClientUpdateRequest'; import type { ClientUpdateResponse } from '../models/ClientUpdateResponse'; @@ -66,6 +67,27 @@ export class ClientService { url: '/client/get-all', }); } + /** + * Get Client + * @returns ClientGetResponse Successful Response + * @throws ApiError + */ + public static getClient({ + clientId, + }: { + clientId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/client/get/{client_id}', + path: { + 'client_id': clientId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } /** * Create Client * @returns ClientCreateResponse Successful Response diff --git a/src/client/services/ResiduesService.ts b/src/client/services/ResiduesService.ts new file mode 100644 index 0000000..2b20b72 --- /dev/null +++ b/src/client/services/ResiduesService.ts @@ -0,0 +1,258 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { CreateResidualBoxRequest } from '../models/CreateResidualBoxRequest'; +import type { CreateResidualBoxResponse } from '../models/CreateResidualBoxResponse'; +import type { CreateResidualPalletRequest } from '../models/CreateResidualPalletRequest'; +import type { CreateResidualPalletResponse } from '../models/CreateResidualPalletResponse'; +import type { CreateResidualProductRequest } from '../models/CreateResidualProductRequest'; +import type { CreateResidualProductResponse } from '../models/CreateResidualProductResponse'; +import type { DeleteResidualBoxResponse } from '../models/DeleteResidualBoxResponse'; +import type { DeleteResidualPalletResponse } from '../models/DeleteResidualPalletResponse'; +import type { DeleteResidualProductResponse } from '../models/DeleteResidualProductResponse'; +import type { GetResidualBoxResponse } from '../models/GetResidualBoxResponse'; +import type { GetResidualPalletResponse } from '../models/GetResidualPalletResponse'; +import type { LoadReceiptRequest } from '../models/LoadReceiptRequest'; +import type { LoadReceiptResponse } from '../models/LoadReceiptResponse'; +import type { UpdateResidualProductRequest } from '../models/UpdateResidualProductRequest'; +import type { UpdateResidualProductResponse } from '../models/UpdateResidualProductResponse'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class ResiduesService { + /** + * Get Pallet + * @returns GetResidualPalletResponse Successful Response + * @throws ApiError + */ + public static getResidualPallet({ + palletId, + }: { + palletId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/residues/pallet/{pallet_id}', + path: { + 'pallet_id': palletId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Pallet + * @returns DeleteResidualPalletResponse Successful Response + * @throws ApiError + */ + public static deleteResidualPallet({ + palletId, + }: { + palletId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/residues/pallet/{pallet_id}', + path: { + 'pallet_id': palletId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Create Pallet + * @returns CreateResidualPalletResponse Successful Response + * @throws ApiError + */ + public static createResidualPallet({ + requestBody, + }: { + requestBody: CreateResidualPalletRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/residues/pallet', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Create Residual Product + * @returns CreateResidualProductResponse Successful Response + * @throws ApiError + */ + public static createResidualProduct({ + requestBody, + }: { + requestBody: CreateResidualProductRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/residues/product', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Update Residual Product + * @returns UpdateResidualProductResponse Successful Response + * @throws ApiError + */ + public static updateResidualProduct({ + residualProductId, + requestBody, + }: { + residualProductId: number, + requestBody: UpdateResidualProductRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/residues/product/{residual_product_id}', + path: { + 'residual_product_id': residualProductId, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Residual Product + * @returns DeleteResidualProductResponse Successful Response + * @throws ApiError + */ + public static deleteResidualProduct({ + residualProductId, + }: { + residualProductId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/residues/product/{residual_product_id}', + path: { + 'residual_product_id': residualProductId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Box + * @returns GetResidualBoxResponse Successful Response + * @throws ApiError + */ + public static getResidualBox({ + boxId, + }: { + boxId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/residues/box/{box_id}', + path: { + 'box_id': boxId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Box + * @returns DeleteResidualBoxResponse Successful Response + * @throws ApiError + */ + public static deleteResidualBox({ + boxId, + }: { + boxId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/residues/box/{box_id}', + path: { + 'box_id': boxId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Create Box + * @returns CreateResidualBoxResponse Successful Response + * @throws ApiError + */ + public static createResidualBox({ + requestBody, + }: { + requestBody: CreateResidualBoxRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/residues/box', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Receipt + * @returns LoadReceiptResponse Successful Response + * @throws ApiError + */ + public static receipt({ + requestBody, + }: { + requestBody: LoadReceiptRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/residues/receipt', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Generate Pdf + * @returns any Successful Response + * @throws ApiError + */ + public static getPdf({ + palletIds = '', + boxIds = '', + }: { + palletIds?: string, + boxIds?: string, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/residues/pdf/', + query: { + 'pallet_ids': palletIds, + 'box_ids': boxIds, + }, + errors: { + 422: `Validation Error`, + }, + }); + } +} diff --git a/src/components/InlineButton/InlineButton.tsx b/src/components/InlineButton/InlineButton.tsx new file mode 100644 index 0000000..e1dac23 --- /dev/null +++ b/src/components/InlineButton/InlineButton.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from "react"; +import { Button, ButtonProps, Group } from "@mantine/core"; + +interface Props extends ButtonProps { + children?: ReactNode; + onClick?: () => void; +} + +const InlineButton = ({ children, onClick, ...props }: Props) => { + return ( + + ); +}; + +export default InlineButton; diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 36b7172..a0d5aac 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -7,7 +7,7 @@ import { IconCash, IconChartDots, IconDashboard, IconFileBarcode, - IconHome2, + IconHome2, IconHomeEdit, IconLogout, IconMan, IconMoon, @@ -94,6 +94,11 @@ const mockdata = [ label: "Маркетплейсы", href: "/marketplaces", }, + { + icon: IconHomeEdit, + label: "Остатки", + href: "/residues", + }, ]; const adminMockdata = [ diff --git a/src/components/PageBlock/PageBlock.module.css b/src/components/PageBlock/PageBlock.module.css index c426848..5d959ab 100644 --- a/src/components/PageBlock/PageBlock.module.css +++ b/src/components/PageBlock/PageBlock.module.css @@ -27,3 +27,7 @@ .container-full-height-fixed { height: calc(100vh - (rem(20) * 2)); } + +.container-no-border-radius { + border-radius: 0 !important; +} diff --git a/src/components/PageBlock/PageBlock.tsx b/src/components/PageBlock/PageBlock.tsx index 73d5e96..fc6e145 100644 --- a/src/components/PageBlock/PageBlock.tsx +++ b/src/components/PageBlock/PageBlock.tsx @@ -8,6 +8,7 @@ type Props = { style?: CSSProperties; fullHeight?: boolean; fullHeightFixed?: boolean; + noBorderRadius?: boolean; }; export const PageBlock: FC = ({ children, @@ -15,6 +16,7 @@ export const PageBlock: FC = ({ fluid = true, fullHeight = false, fullHeightFixed = false, + noBorderRadius = false, }) => { return (
= ({ styles["container"], fluid && styles["container-fluid"], fullHeight && styles["container-full-height"], - fullHeightFixed && styles["container-full-height-fixed"] + fullHeightFixed && styles["container-full-height-fixed"], + noBorderRadius && styles["container-no-border-radius"] )}> {children}
diff --git a/src/modals/modals.ts b/src/modals/modals.ts index dd68f51..6bba7c4 100644 --- a/src/modals/modals.ts +++ b/src/modals/modals.ts @@ -7,7 +7,8 @@ import AddDealServiceModal from "../pages/LeadsPage/modals/AddDealServiceModal.t import AddDealProductModal from "../pages/LeadsPage/modals/AddDealProductModal.tsx"; import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx"; import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx"; -import BarcodeTemplateFormModal from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx"; +import BarcodeTemplateFormModal + from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx"; import ProductServiceFormModal from "../pages/LeadsPage/modals/ProductServiceFormModal.tsx"; import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx"; import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx"; @@ -29,6 +30,9 @@ import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/ import AddUserToDepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx"; import AssignUserModal from "../pages/LeadsPage/tabs/EmployeesTab/modals/AssignUserModal.tsx"; +import ResidualProductModal from "../pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx"; +import NewReceiptModal from "../pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx"; +import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx"; export const modals = { enterDeadline: EnterDeadlineModal, @@ -58,6 +62,9 @@ export const modals = { transactionFormModal: TransactionFormModal, transactionTagsModal: TransactionTagsModal, shippingProductModal: ShippingProductModal, + residualProductModal: ResidualProductModal, + newReceiptModal: NewReceiptModal, + receiptModal: ReceiptModal, departmentModal: DepartmentModal, addUserToDepartmentModal: AddUserToDepartmentModal, assignUserModal: AssignUserModal, diff --git a/src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx b/src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx index ee3de4c..cdf9a8a 100644 --- a/src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx +++ b/src/pages/LeadsPage/tabs/ShippingTab/ShippingTab.tsx @@ -1,9 +1,9 @@ import ShippingTree from "./components/ShippingTree.tsx"; -import { Button, Group, rem, ScrollArea, Stack } from "@mantine/core"; +import { Group, ScrollArea, Stack } from "@mantine/core"; import useShipping from "./hooks/useShipping.tsx"; import useShippingQrs from "./hooks/useShippingQrs.tsx"; import { IconPrinter, IconQrcode } from "@tabler/icons-react"; -import { ReactNode } from "react"; +import InlineButton from "../../../../components/InlineButton/InlineButton.tsx"; const ShippingTab = () => { @@ -18,28 +18,27 @@ const ShippingTab = () => { onGetBoxesPdfClick, } = useShippingQrs(); - const getButton = (label: string, func: () => void, icon?: ReactNode) => { - return ( - - ); - }; - return ( - {getButton("Добавить паллет", onCreatePalletClick)} - {getButton("Добавить короб", onCreateBoxInDealClick)} - {getButton("Сделка", onGetDealQrPdfClick, )} - {getButton("Паллеты", onGetPalletsPdfClick, )} - {getButton("Короба", onGetBoxesPdfClick, )} + onCreatePalletClick()}> + Добавить паллет + + onCreateBoxInDealClick()}> + Добавить короб + + onGetDealQrPdfClick()}> + + Сделка + + onGetPalletsPdfClick()}> + + Паллеты + + onGetBoxesPdfClick()}> + + Короба + diff --git a/src/pages/PageWrapper/PageWrapper.module.css b/src/pages/PageWrapper/PageWrapper.module.css index 232223b..8202da8 100644 --- a/src/pages/PageWrapper/PageWrapper.module.css +++ b/src/pages/PageWrapper/PageWrapper.module.css @@ -9,4 +9,8 @@ .container { padding: rem(20); + + @media only screen and (max-width: 768px) { + padding: 0 !important; + } } diff --git a/src/pages/PageWrapper/PageWrapper.tsx b/src/pages/PageWrapper/PageWrapper.tsx index ef6c51b..10cb462 100644 --- a/src/pages/PageWrapper/PageWrapper.tsx +++ b/src/pages/PageWrapper/PageWrapper.tsx @@ -12,22 +12,26 @@ export type Props = { const PageWrapper: FC = ({ children }) => { const authState = useSelector((state: RootState) => state.auth); const uiState = useSelector((state: RootState) => state.ui); + const navbarNeeded = authState.isAuthorized && !authState.isGuest; + return ( + + + - {authState.isAuthorized && !authState.isGuest && ( + {navbarNeeded && ( { + const { boxes, boxesHandlers, onObjectEditClick, palletsHandlers, setBoxData } = useReceiptContext(); + + const deleteBox = (boxId: number) => { + if (palletIdx && pallet) { + palletsHandlers.setItemProp(palletIdx, "boxes", pallet.boxes.filter(box => box.id !== boxId)); + } else { + boxesHandlers.filter(item => item.id !== boxId); + } + }; + + const boxActions = (box: ReceiptBox) => { + return ( + <> + onObjectEditClick(box, true, pallet)} + mr={"sm"} + > + + + deleteBox(box.id)} + mr={"sm"} + > + + + + ); + }; + + const boxesData = pallet ? pallet.boxes : boxes; + + if (boxesData.length === 0) return; + + return ( + + {boxesData.map(box => ( + +
+ }> + Короб {box.id} + + {boxActions(box)} +
+ + setBoxData(box, pallet)} + /> + +
+ ))} +
+ ); +}; + +export default AccordionBoxes; diff --git a/src/pages/ReceiptPage/components/NewReceipt/components/AccordionPallets.tsx b/src/pages/ReceiptPage/components/NewReceipt/components/AccordionPallets.tsx new file mode 100644 index 0000000..30bde22 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/components/AccordionPallets.tsx @@ -0,0 +1,117 @@ +import { Accordion, ActionIcon, Box, Button, Center, Flex, Group } from "@mantine/core"; +import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react"; +import { ReceiptBox, ReceiptPallet } from "../types/types.tsx"; +import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; +import AccordionBoxes from "./AccordionBoxes.tsx"; +import ReceiptProducts from "./ReceiptProducts.tsx"; + +const AccordionPallets = () => { + const { pallets, palletsHandlers, nextId, onObjectEditClick, setPalletData } = useReceiptContext(); + + const deletePallet = (palletId: number) => { + palletsHandlers.filter(item => item.id !== palletId); + }; + + const createBox = (pallet: ReceiptPallet) => { + const box = { + id: nextId(), + residualProducts: [], + } as ReceiptBox; + + const palletIdx = pallets.findIndex(p => p.id === pallet.id); + palletsHandlers.setItemProp(palletIdx, "boxes", [box, ...pallets[palletIdx].boxes]); + }; + + const palletActions = (pallet: ReceiptPallet) => { + const isCreateButtonVisible = pallet.boxes.length > 0 || pallet.residualProducts.length > 0; + return ( + <> + {isCreateButtonVisible && { + if (pallet.residualProducts.length > 0) { + onObjectEditClick(pallet, false); + } else { + createBox(pallet); + } + }} + mr={"sm"} + > + + } + deletePallet(pallet.id)} + mr={"md"} + > + + + + ); + }; + + const palletButton = (label: string, onClick: () => void) => { + return ( + + ); + }; + + const palletButtons = (pallet: ReceiptPallet) => { + if (pallet.boxes.length > 0 || pallet.residualProducts.length > 0) { + return; + } + return ( + + {palletButton("Короб", () => createBox(pallet))} + {palletButton("Товар", () => onObjectEditClick(pallet, false))} + + ); + }; + + const palletItem = (pallet: ReceiptPallet, palletIdx: number) => { + return ( + +
+ }> + Паллет {pallet.id} + + {palletActions(pallet)} +
+ + {palletButtons(pallet)} + {pallet.boxes && + + + + } + {pallet.residualProducts && ( + setPalletData(object as ReceiptPallet)} + /> + )} + +
+ ); + }; + + if (pallets.length === 0) return; + + return ( + + {pallets.map(((pallet, idx) => palletItem(pallet, idx)))} + + ); +}; + +export default AccordionPallets; diff --git a/src/pages/ReceiptPage/components/NewReceipt/components/FinishReceiptButton.tsx b/src/pages/ReceiptPage/components/NewReceipt/components/FinishReceiptButton.tsx new file mode 100644 index 0000000..38cf58d --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/components/FinishReceiptButton.tsx @@ -0,0 +1,64 @@ +import { Button, Group } from "@mantine/core"; +import { IconCheck } from "@tabler/icons-react"; +import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; +import { type ProductAndQuantitySchema, ReceiptBoxSchema, ReceiptPalletSchema, ResiduesService } from "../../../../../client"; +import { ReceiptBox, ReceiptPallet, ReceiptProduct } from "../types/types.tsx"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; + +const FinishReceiptButton = () => { + const { pallets, boxes, client, reset } = useReceiptContext(); + const finishReceiptDisabled = pallets.length === 0 && boxes.length === 0; + + const processProducts = (products: ReceiptProduct[]): ProductAndQuantitySchema[] => { + return products.map(p => ({ + productId: p.product.id, + quantity: p.quantity, + })); + }; + + const processBoxes = (boxes: ReceiptBox[]): ReceiptBoxSchema[] => { + return boxes.map(box => ({ + products: processProducts(box.residualProducts), + })); + }; + + const processPallets = (pallets: ReceiptPallet[]): ReceiptPalletSchema[] => { + return pallets.map(pallet => ({ + products: processProducts(pallet.residualProducts), + boxes: processBoxes(pallet.boxes), + })); + }; + + const finishReceipt = () => { + if (!client) return; + + ResiduesService.receipt({ + requestBody: { + clientId: client.id, + pallets: processPallets(pallets), + boxes: processBoxes(boxes), + }, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + if (!ok) return; + reset(); + }) + .catch(err => console.log(err)); + }; + + return ( + + ); +}; + +export default FinishReceiptButton; diff --git a/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptEditor.tsx b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptEditor.tsx new file mode 100644 index 0000000..10f7078 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptEditor.tsx @@ -0,0 +1,49 @@ +import { Flex, Stack } from "@mantine/core"; +import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; +import { ReceiptBox } from "../types/types.tsx"; +import AccordionBoxes from "./AccordionBoxes.tsx"; +import AccordionPallets from "./AccordionPallets.tsx"; +import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx"; +import { IconPlus } from "@tabler/icons-react"; + +const ReceiptEditor = () => { + const { fixed, palletsHandlers, boxesHandlers, nextId } = useReceiptContext(); + + if (!fixed) return; + + const createPallet = () => { + const pallet = { + id: nextId(), + boxes: [], + residualProducts: [], + }; + palletsHandlers.prepend(pallet); + }; + + const createBox = () => { + const box = { + id: nextId(), + residualProducts: [], + } as ReceiptBox; + boxesHandlers.prepend(box); + }; + + return ( + + + createPallet()} flex={1}> + + Паллет + + createBox()} flex={1}> + + Короб + + + + + + ); +}; + +export default ReceiptEditor; diff --git a/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptProducts.tsx b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptProducts.tsx new file mode 100644 index 0000000..d1e787f --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptProducts.tsx @@ -0,0 +1,63 @@ +import { ReceiptBox, ReceiptPallet, ReceiptProduct } from "../types/types.tsx"; +import { ActionIcon, Divider, Group, NumberInput, rem, Stack, Text } from "@mantine/core"; +import { IconTrash } from "@tabler/icons-react"; + +type Props = { + products: ReceiptProduct[]; + object: ReceiptBox | ReceiptPallet; + setObjectData: (object: ReceiptBox | ReceiptPallet) => void; +} + +const ReceiptProducts = ({ products, object, setObjectData }: Props) => { + if (products.length === 0) return; + + const deleteProduct = (productId: number) => { + object.residualProducts = object.residualProducts.filter(product => product.id !== productId); + setObjectData({ ...object }); + }; + + const productQuantityInput = (receiptProduct: ReceiptProduct) => { + return ( + + Количество: + { + const idx = object.residualProducts.findIndex(p => p.id === receiptProduct.id); + object.residualProducts[idx].quantity = Number(value); + setObjectData({ ...object }); + }} + min={1} + allowDecimal={false} + allowNegative={false} + w={rem(100)} + /> + + ); + }; + + return ( + + {products.map((receiptProduct: ReceiptProduct) => ( + + + + + Товар: {receiptProduct.product.name} + {productQuantityInput(receiptProduct)} + {receiptProduct.product.size && Размер: {receiptProduct.product.size}} + + deleteProduct(receiptProduct.id)} + > + + + + + ))} + + ); +}; + +export default ReceiptProducts; diff --git a/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptUserSelect.tsx b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptUserSelect.tsx new file mode 100644 index 0000000..3965bd8 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/components/ReceiptUserSelect.tsx @@ -0,0 +1,88 @@ +import { useReceiptContext } from "../contexts/ReceiptContext.tsx"; +import ClientSelectNew from "../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx"; +import { Button, Flex, Group, Stack, Text } from "@mantine/core"; +import { modals } from "@mantine/modals"; +import { IconArrowLeft, IconX } from "@tabler/icons-react"; +import FinishReceiptButton from "./FinishReceiptButton.tsx"; + + +const ReceiptUserSelect = () => { + const { + client, + setClient, + fixed, + setFixed, + palletsHandlers, + boxesHandlers, + } = useReceiptContext(); + + const confirmModalText = ( + + Вы уверены, что хотите отменить приемку? + Все загруженные паллеты, короба и товары удалятся. + + ); + + const unfix = () => { + modals.openConfirmModal({ + title: "Отмена приемки", + children: confirmModalText, + labels: { confirm: "Да", cancel: "Нет" }, + confirmProps: { color: "red" }, + onConfirm: () => { + palletsHandlers.setState([]); + boxesHandlers.setState([]); + setFixed(false); + }, + }); + }; + + return ( + + + + + + + { + fixed ? ( + <> + + + + ) : ( + + ) + } + + ); +}; + +export default ReceiptUserSelect; diff --git a/src/pages/ReceiptPage/components/NewReceipt/contexts/ReceiptContext.tsx b/src/pages/ReceiptPage/components/NewReceipt/contexts/ReceiptContext.tsx new file mode 100644 index 0000000..ff6bd93 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/contexts/ReceiptContext.tsx @@ -0,0 +1,138 @@ +import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from "react"; +import { ClientSchema } from "../../../../../client"; +import { ReceiptBox, ReceiptPallet } from "../types/types.tsx"; +import { modals } from "@mantine/modals"; +import nextId from "../utils/nextId.ts"; +import { useListState, UseListStateHandlers } from "@mantine/hooks"; + +type ReceiptContextState = { + client?: ClientSchema; + setClient: Dispatch>; + fixed: boolean; + setFixed: Dispatch>; + pallets: ReceiptPallet[]; + palletsHandlers: UseListStateHandlers; + boxes: ReceiptBox[]; + boxesHandlers: UseListStateHandlers; + nextId: () => number; + onObjectEditClick: (object: ReceiptBox | ReceiptPallet, isBox: boolean, parentPallet?: ReceiptPallet) => void; + reset: () => void; + setBoxData: (box: ReceiptBox, pallet?: ReceiptPallet) => void; + setPalletData: (pallet: ReceiptPallet) => void; +}; + +const ReceiptContext = createContext( + undefined, +); + +const useReceiptContextState = () => { + const [client, setClient] = useState(); + const [fixed, setFixed] = useState(false); + + const [pallets, palletsHandlers] = useListState([]); + const [boxes, boxesHandlers] = useListState([]); + + const setBoxData = (box: ReceiptBox, pallet?: ReceiptPallet) => { + if (pallet) { + setBoxOnPalletData(box, pallet); + } else { + setBoxWithoutPalletData(box); + } + }; + + const setBoxWithoutPalletData = (box: ReceiptBox) => { + const boxIdx = boxes.findIndex(item => item.id === box.id); + if (boxIdx === -1) { + boxesHandlers.append(box); + } else { + boxesHandlers.setItem(boxIdx, box); + } + }; + + const setBoxOnPalletData = (box: ReceiptBox, pallet: ReceiptPallet) => { + const palletIdx = pallets.findIndex(p => p.id === pallet.id); + const boxIdx = pallets[palletIdx].boxes.findIndex(b => b.id === box.id); + if (boxIdx === -1) { + palletsHandlers.setItemProp(palletIdx, "boxes", [box, ...boxes]); + } else { + pallets[palletIdx].boxes[boxIdx] = box; + palletsHandlers.setState(pallets); + } + palletsHandlers.setState([...pallets]); + }; + + const setPalletData = (pallet: ReceiptPallet) => { + const palletIdx = pallets.findIndex(p => p.id === pallet.id); + if (palletIdx !== -1) { + palletsHandlers.setItem(palletIdx, pallet); + palletsHandlers.setState([...pallets]); + } + }; + + const onObjectEditClick = ( + object: ReceiptBox | ReceiptPallet, + isBox: boolean, + parentPallet?: ReceiptPallet, + ) => { + if (!client) return; + modals.openContextModal({ + modal: "newReceiptModal", + title: "Добавление товара", + withCloseButton: false, + innerProps: { + clientId: client.id, + object: object, + setObjectData: (object: ReceiptBox | ReceiptPallet) => { + if (isBox) setBoxData(object, parentPallet); + setPalletData(object as ReceiptPallet); + }, + }, + }); + }; + + const reset = () => { + palletsHandlers.setState([]); + boxesHandlers.setState([]); + setFixed(false); + setClient(undefined); + }; + + return { + client, + setClient, + fixed, + setFixed, + pallets, + palletsHandlers, + boxes, + boxesHandlers, + nextId, + onObjectEditClick, + reset, + setBoxData, + setPalletData, + }; +}; + +type ReceiptContextProviderProps = { + children: React.ReactNode; +}; + +export const ReceiptContextProvider: FC = ({ children }) => { + const state = useReceiptContextState(); + return ( + + {children} + + ); +}; + +export const useReceiptContext = () => { + const context = useContext(ReceiptContext); + if (!context) { + throw new Error( + "useReceiptContext must be used within a ReceiptContextProvider", + ); + } + return context; +}; diff --git a/src/pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx b/src/pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx new file mode 100644 index 0000000..09de8e7 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/modals/NewReceiptModal.tsx @@ -0,0 +1,126 @@ +import { useForm } from "@mantine/form"; +import { ContextModalProps } from "@mantine/modals"; +import { Button, Divider, Flex, NumberInput, rem, Stack } from "@mantine/core"; +import ProductSelect from "../../../../../components/ProductSelect/ProductSelect.tsx"; +import { ReceiptBox, ReceiptPallet } from "../types/types.tsx"; +import { ResidualModalForm } from "../../../../ResiduesPage/types/ResidualProductData.tsx"; +import nextId from "../utils/nextId.ts"; +import useScanning from "../../../hooks/useScanning.tsx"; +import ScanBarcode from "../../ScanBarcode/ScanBarcode.tsx"; +import { ProductSchema } from "../../../../../client"; + + +type Props = { + clientId: number; + object: ReceiptBox | ReceiptPallet; + setObjectData: (object: ReceiptBox | ReceiptPallet) => void; +} + +const NewReceiptModal = ({ + context, + id, + innerProps, + }: ContextModalProps) => { + const { + clientId, + setObjectData, + object, + } = innerProps; + const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning(); + + const initialValues = { + product: null, + quantity: 1, + }; + const form = useForm({ + initialValues, + validate: { + product: product => !product && "Необходимо выбрать товар", + quantity: quantity => (!quantity || quantity === 0) && "Слишком мало товара", + }, + }); + + const onSubmit = () => { + const productIdx = object.residualProducts.findIndex(p => p.product.id === form.values.product?.id); + + if (productIdx !== -1) { + object.residualProducts[productIdx].quantity += form.values.quantity; + } else { + const id = nextId(); + const receiptProduct = { + id, + product: form.values.product!, + quantity: form.values.quantity, + }; + object.residualProducts.unshift(receiptProduct); + } + + setObjectData(object); + form.reset(); + context.closeContextModal(id); + }; + + const onProductAfterScanningSelect = (product?: ProductSchema) => { + if (!product) { + setScannedValue(""); + return; + } + + const productIdx = object.residualProducts.findIndex(p => p.product.id === product.id); + if (productIdx === -1) { + form.setFieldValue("product", product); + } else { + object.residualProducts[productIdx].quantity++; + setObjectData({ ...object }); + context.closeContextModal(id); + } + setScannedValue(""); + }; + + return ( + +
onSubmit())}> + + + + + + + +
+ + +
+ ); +}; + +export default NewReceiptModal; diff --git a/src/pages/ReceiptPage/components/NewReceipt/types/types.tsx b/src/pages/ReceiptPage/components/NewReceipt/types/types.tsx new file mode 100644 index 0000000..57d60aa --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/types/types.tsx @@ -0,0 +1,18 @@ +import { ProductSchema } from "../../../../../client"; + +export type ReceiptProduct = { + id: number; + quantity: number; + product: ProductSchema; +} + +export type ReceiptBox = { + id: number; + residualProducts: ReceiptProduct[]; +} + +export type ReceiptPallet = { + id: number; + residualProducts: ReceiptProduct[]; + boxes: ReceiptBox[]; +} diff --git a/src/pages/ReceiptPage/components/NewReceipt/utils/nextId.ts b/src/pages/ReceiptPage/components/NewReceipt/utils/nextId.ts new file mode 100644 index 0000000..fc8e7a9 --- /dev/null +++ b/src/pages/ReceiptPage/components/NewReceipt/utils/nextId.ts @@ -0,0 +1,5 @@ +let id = 1; + +export default function nextId() { + return id++; +} diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/AccordionBoxes.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/AccordionBoxes.tsx new file mode 100644 index 0000000..e3622a5 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/AccordionBoxes.tsx @@ -0,0 +1,89 @@ +import { Accordion, ActionIcon, Center } from "@mantine/core"; +import { IconBox, IconPlus, IconTrash } from "@tabler/icons-react"; +import ReceiptProducts from "./ReceiptProducts.tsx"; +import { ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client"; +import { modals } from "@mantine/modals"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; + +type Props = { + pallet: ResidualPalletSchema; + clientId: number; + fetchPallet: () => void; +} + +const AccordionBoxesOnPallet = ({ pallet, clientId, fetchPallet }: Props) => { + const deleteBox = (boxId: number) => { + ResiduesService.deleteResidualBox({ + boxId, + }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + } + fetchPallet(); + }) + .catch(err => console.log(err)); + }; + + const onCreateProductClick = (box: ResidualBoxSchema) => { + if (!(box && clientId)) return; + modals.openContextModal({ + modal: "receiptModal", + title: "Добавление товара", + withCloseButton: false, + innerProps: { + clientId, + isBox: true, + object: box, + fetchObject: fetchPallet, + }, + }); + }; + + const boxActions = (box: ResidualBoxSchema) => { + return ( + <> + onCreateProductClick(box)} + mr={"sm"} + > + + + deleteBox(box.id)} + mr={"sm"} + > + + + + ); + }; + + if (pallet.boxes.length === 0) return; + + return ( + + {pallet.boxes.map(box => ( + +
+ }> + Короб {box.id} + + {boxActions(box)} +
+ + + +
+ ))} +
+ ); +}; + +export default AccordionBoxesOnPallet; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/ProductQuantityField.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/ProductQuantityField.tsx new file mode 100644 index 0000000..0a71b6b --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/ProductQuantityField.tsx @@ -0,0 +1,46 @@ +import { Group, NumberInput, rem, Text } from "@mantine/core"; +import { ResidualProductSchema, ResiduesService } from "../../../../../client"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; + +type Props = { + residualProduct: ResidualProductSchema; + updateObject: () => void; +} + +const ProductQuantityField = ({ residualProduct, updateObject }: Props) => { + const updateProduct = (quantity: number) => { + if (residualProduct.quantity === quantity) return; + ResiduesService.updateResidualProduct({ + requestBody: { + data: { + quantity, + productId: residualProduct.product.id, + }, + }, + residualProductId: residualProduct.id, + }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + } + updateObject(); + }) + .catch(err => console.log(err)); + }; + + return ( + + Количество: + updateProduct(Number(value))} + min={1} + allowDecimal={false} + allowNegative={false} + w={rem(100)} + /> + + ); +}; + +export default ProductQuantityField; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptBoxEditor.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptBoxEditor.tsx new file mode 100644 index 0000000..d9d2bd4 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptBoxEditor.tsx @@ -0,0 +1,50 @@ +import { Button, Flex, Stack, Title } from "@mantine/core"; +import useReceiptBox from "../hooks/useReceiptBox.tsx"; +import ReceiptProducts from "./ReceiptProducts.tsx"; +import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx"; +import { IconArrowLeft, IconPlus } from "@tabler/icons-react"; + +type Props = { + boxId: number; +} + +const ReceiptBoxEditor = ({ boxId }: Props) => { + const { + box, + fetchBox, + clientId, + onCreateProductClick, + } = useReceiptBox({ boxId }); + + const backButton = ( + + ); + + return ( + + + {backButton} + + Короб ID: К{box?.id} + + + + + Товар + + + + ); +}; + +export default ReceiptBoxEditor; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptPalletEditor.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptPalletEditor.tsx new file mode 100644 index 0000000..2ad94d8 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptPalletEditor.tsx @@ -0,0 +1,92 @@ +import useReceiptPallet from "../hooks/useReceiptPallet.tsx"; +import { Button, Flex, Group, Stack, Text, Title } from "@mantine/core"; +import { IconArrowLeft, IconPlus } from "@tabler/icons-react"; +import InlineButton from "../../../../../components/InlineButton/InlineButton.tsx"; +import ReceiptProducts from "./ReceiptProducts.tsx"; +import AccordionBoxesOnPallet from "./AccordionBoxes.tsx"; + +type Props = { + palletId: number; +} + +const ReceiptPalletEditor = ({ palletId }: Props) => { + const { + pallet, + fetchPallet, + clientId, + onCreateProductClick, + onCreateBoxClick, + } = useReceiptPallet({ palletId }); + + if (!pallet) return Паллет c ID П{palletId} не найден; + + const createButtons = () => { + const isBoxes = pallet.boxes.length > 0; + const isProducts = pallet.residualProducts.length > 0; + const isBoth = !(isBoxes || isProducts); + return ( + + {(isBoxes || isBoth) && ( + + + Короб + + )} + {(isProducts || isBoth) && ( + + + Товар + + )} + + ); + }; + + const renderPalletData = () => { + if (!clientId) return; + if (pallet?.boxes.length !== 0) { + return ( + + ); + } + if (pallet?.residualProducts.length !== 0) { + return ( + + ); + } + }; + + const backButton = ( + + ); + + + return ( + + + {backButton} + + Паллет ID: П{pallet?.id} + + + {createButtons()} + {renderPalletData()} + + ); +}; + +export default ReceiptPalletEditor; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptProducts.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptProducts.tsx new file mode 100644 index 0000000..09d2ca8 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptProducts.tsx @@ -0,0 +1,64 @@ +import { ActionIcon, Divider, Flex, Group, rem, Stack, Text } from "@mantine/core"; +import { ResidualBoxSchema, ResidualPalletSchema, ResidualProductSchema, ResiduesService } from "../../../../../client"; +import { IconTrash } from "@tabler/icons-react"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; +import ProductQuantityField from "./ProductQuantityField.tsx"; + + +type Props = { + clientId: number | null; + object: ResidualBoxSchema | ResidualPalletSchema | null; + updateObject: () => void; +} + +const ReceiptProducts = ({ clientId, object, updateObject }: Props) => { + if (!object || !clientId) return; + + const deleteProduct = (residualProductId: number) => { + ResiduesService.deleteResidualProduct({ + residualProductId, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + updateObject(); + }) + .catch(err => console.log(err)); + }; + + const renderProducts = () => { + return object.residualProducts + .sort((a, b) => a.id - b.id) + .map((residualProduct: ResidualProductSchema) => ( + + + + + Товар: {residualProduct.product.name} + + + deleteProduct(residualProduct.id)} + > + + + + + )); + }; + + return ( + + {renderProducts()} + + + ); +}; + +export default ReceiptProducts; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptQrCodeScan.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptQrCodeScan.tsx new file mode 100644 index 0000000..a3d8ba4 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/components/ReceiptQrCodeScan.tsx @@ -0,0 +1,64 @@ +import { Button, Center, Stack, Text } from "@mantine/core"; +import useScanning from "../../../hooks/useScanning.tsx"; +import { useEffect } from "react"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; +import ReceiptPalletEditor from "./ReceiptPalletEditor.tsx"; +import ReceiptBoxEditor from "./ReceiptBoxEditor.tsx"; +import { IconArrowLeft } from "@tabler/icons-react"; + +const ReceiptQrCodeScan = () => { + const { + setIsScanning, + scannedValue, + setScannedValue, + } = useScanning(); + + useEffect(() => { + setIsScanning(true); + }, []); + + const getId = () => Number.parseInt(scannedValue.substring(1)); + + const checkQrScannedCorrectly = () => { + if ( + scannedValue.length < 2 + || (scannedValue[0] !== "П" && scannedValue[0] !== "К") + || Number.isNaN(getId()) + ) { + notifications.error({ message: `Считанный ID ${scannedValue} некорректный` }); + setScannedValue(""); + setIsScanning(true); + } + }; + + useEffect(() => { + if (scannedValue) { + checkQrScannedCorrectly(); + } + }, [scannedValue]); + + if (!scannedValue) { + return ( + + +
+ + Отсканируйте QR код + +
+
+ ); + } + + if (scannedValue.length > 0 && scannedValue[0] === "К") { + return ; + } + return ; +}; + +export default ReceiptQrCodeScan; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptBox.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptBox.tsx new file mode 100644 index 0000000..e12b845 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptBox.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react"; +import { ResidualBoxSchema, ResiduesService } from "../../../../../client"; +import { modals } from "@mantine/modals"; + + +type Props = { + boxId: number; +} + +const useReceiptBox = ({ boxId }: Props) => { + const [box, setBox] = useState(null); + const [clientId, setClientId] = useState(null); + + useEffect(() => { + fetchBox(boxId); + }, []); + + const fetchBox = (boxId?: number) => { + const id = boxId ?? box?.id; + if (!id) return; + + ResiduesService.getResidualBox({ boxId: id }) + .then(res => { + setBox(res.box); + setClientId(res.clientId); + }) + .catch(err => console.log(err)); + }; + + const onCreateProductClick = () => { + if (!(box && clientId)) return; + modals.openContextModal({ + modal: "receiptModal", + title: "Добавление товара", + withCloseButton: false, + innerProps: { + clientId, + isBox: true, + object: box, + fetchObject: fetchBox, + }, + }); + }; + + return { + box, + fetchBox, + clientId, + onCreateProductClick, + }; +}; + +export default useReceiptBox; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptPallet.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptPallet.tsx new file mode 100644 index 0000000..4753729 --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/hooks/useReceiptPallet.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from "react"; +import { ResidualPalletSchema, ResiduesService } from "../../../../../client"; +import { modals } from "@mantine/modals"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; + +type Props = { + palletId: number; +} + +const useReceiptPallet = ({ palletId }: Props) => { + const [pallet, setPallet] = useState(null); + const [clientId, setClientId] = useState(null); + + useEffect(() => { + fetchPallet(palletId); + }, []); + + const fetchPallet = (palletId?: number) => { + const id = palletId ?? pallet?.id; + if (!id) return; + + ResiduesService.getResidualPallet({ palletId: id }) + .then(res => { + setPallet(res.pallet); + setClientId(res.clientId); + }) + .catch(err => console.log(err)); + }; + + const onCreateProductClick = () => { + if (!(pallet && clientId)) return; + modals.openContextModal({ + modal: "receiptModal", + title: "Добавление товара", + withCloseButton: false, + innerProps: { + clientId, + isBox: false, + object: pallet, + fetchObject: fetchPallet, + }, + }); + }; + + const onCreateBoxClick = () => { + ResiduesService.createResidualBox({ + requestBody: { + palletId, + clientId: null, + }, + }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + } + fetchPallet(); + }) + .catch(err => console.log(err)); + }; + + return { + pallet, + fetchPallet, + clientId, + onCreateProductClick, + onCreateBoxClick, + }; +}; + +export default useReceiptPallet; diff --git a/src/pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx b/src/pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx new file mode 100644 index 0000000..393cd6f --- /dev/null +++ b/src/pages/ReceiptPage/components/ReceiptEditing/modals/ReceiptModal.tsx @@ -0,0 +1,129 @@ +import { useForm } from "@mantine/form"; +import { ContextModalProps } from "@mantine/modals"; +import { Button, Divider, Flex, NumberInput, rem, Stack } from "@mantine/core"; +import ProductSelect from "../../../../../components/ProductSelect/ProductSelect.tsx"; +import useScanning from "../../../hooks/useScanning.tsx"; +import ScanBarcode from "../../ScanBarcode/ScanBarcode.tsx"; +import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema, ResiduesService } from "../../../../../client"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; +import { ResidualModalForm } from "../../../../ResiduesPage/types/ResidualProductData.tsx"; + + +type Props = { + clientId: number; + object: ResidualBoxSchema | ResidualPalletSchema; + fetchObject: () => void; + isBox: boolean; +} + +const ReceiptModal = ({ + context, + id, + innerProps, + }: ContextModalProps) => { + const { + clientId, + object, + fetchObject, + } = innerProps; + const { isScanning, setIsScanning, scannedValue, setScannedValue } = useScanning(); + const initialValues = { + product: null, + quantity: 1, + }; + const form = useForm({ + initialValues, + validate: { + product: product => !product && "Необходимо выбрать товар", + quantity: quantity => (!quantity || quantity === 0) && "Слишком мало товара", + }, + }); + + if (!object || !clientId) return; + + const postProduct = (product: ProductSchema, quantity: number) => { + ResiduesService.createResidualProduct({ + requestBody: { + data: { + productId: product.id, + quantity, + palletId: innerProps.isBox ? null : object.id, + boxId: innerProps.isBox ? object.id : null, + }, + }, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + fetchObject(); + if (ok) context.closeContextModal(id); + }) + .catch(err => console.log(err)); + }; + + const onProductAfterScanningSelect = (product?: ProductSchema) => { + if (!product) { + setScannedValue(""); + return; + } + + const productIdx = object.residualProducts.findIndex(p => p.product.id === product.id); + if (productIdx === -1) { + form.setFieldValue("product", product); + } else { + postProduct(product, 1); + } + setScannedValue(""); + }; + + const onSubmit = () => { + postProduct(form.values.product!, form.values.quantity!); + form.reset(); + }; + + return ( + +
onSubmit())}> + + + + + + + +
+ + +
+ ); +}; + +export default ReceiptModal; diff --git a/src/pages/ReceiptPage/components/ScanBarcode/ScanBarcode.tsx b/src/pages/ReceiptPage/components/ScanBarcode/ScanBarcode.tsx new file mode 100644 index 0000000..79d1015 --- /dev/null +++ b/src/pages/ReceiptPage/components/ScanBarcode/ScanBarcode.tsx @@ -0,0 +1,135 @@ +import { ProductSchema, ResidualBoxSchema, ResidualPalletSchema } from "../../../../client"; +import { useEffect, useState } from "react"; +import useProductsList from "../../../ProductsPage/hooks/useProductsList.tsx"; +import { Button, Group, Radio, Stack, Text } from "@mantine/core"; +import { notifications } from "../../../../shared/lib/notifications.ts"; +import { ReceiptBox, ReceiptPallet } from "../NewReceipt/types/types.tsx"; + +type Props = { + onProductSelect: (product?: ProductSchema) => void; + clientId: number; + isScanning: boolean; + setIsScanning: (isScanning: boolean) => void; + scannedValue: string; + object: ReceiptBox | ReceiptPallet | ResidualPalletSchema | ResidualBoxSchema; +} + +const ScanBarcode = ({ + onProductSelect, + clientId, + isScanning, + setIsScanning, + scannedValue, + object, + }: Props) => { + const productsData = useProductsList({ clientId, searchInput: "" }); + const [barcodesProducts, setBarcodesProducts] = useState(new Map()); + let productsToSelect: ProductSchema[] = []; + const [selectedProduct, setSelectedProduct] = useState(); + + const toggleScanning = () => { + setIsScanning(!isScanning); + }; + + const productsToBarcodesProducts = () => { + const data = new Map(); + productsData.products.forEach(product => { + product.barcodes.forEach(barcode => { + if (data.has(barcode)) { + data.set(barcode, [...data.get(barcode)!, product]); + } else { + data.set(barcode, [product]); + } + }); + }); + setBarcodesProducts(data); + }; + + useEffect(() => { + if (productsData.products.length !== 0) { + productsToBarcodesProducts(); + } + }, [productsData.isLoading]); + + const findProductInObject = (productsToSelect: ProductSchema[]): ProductSchema | undefined => { + for (let i = 0; i < productsToSelect.length; i++) { + for (let j = 0; j < object.residualProducts.length; j++) { + if (productsToSelect[i].id === object.residualProducts[j].product.id) { + return productsToSelect[i]; + } + } + } + }; + + const renderScanningResults = () => { + productsToSelect = barcodesProducts.get(scannedValue) ?? []; + if (productsToSelect?.length === 0) { + notifications.error({ message: `Товара с штрихкодом ${scannedValue} не найдено` }); + onProductSelect(); + return; + } + if (productsToSelect?.length === 1) { + onProductSelect(productsToSelect[0]); + return; + } + const product = findProductInObject(productsToSelect); + if (product) { + onProductSelect(product); + return; + } + return renderProductSelect(); + }; + + const renderProductsToSelect = () => { + return productsToSelect.map(product => ( + + setSelectedProduct(product)} + /> + + {product.name} + {product.size && {product.size}} + + + )); + }; + + const renderProductSelect = () => { + return ( + + Выберите товар для данного штрихкода + {renderProductsToSelect()} + + + ); + }; + + return (scannedValue) ? ( + renderScanningResults() + ) : ( + + + + ); +}; + +export default ScanBarcode; diff --git a/src/pages/ReceiptPage/hooks/useScanning.tsx b/src/pages/ReceiptPage/hooks/useScanning.tsx new file mode 100644 index 0000000..406b001 --- /dev/null +++ b/src/pages/ReceiptPage/hooks/useScanning.tsx @@ -0,0 +1,34 @@ +import { useState } from "react"; +import { useWindowEvent } from "@mantine/hooks"; + +const useScanning = () => { + const [scanningValue, setScanningValue] = useState(""); + const [scannedValue, setScannedValue] = useState(""); + const [isScanning, setIsScanning] = useState(false); + + const setIsScanningValue = (isScanning: boolean) => { + if (!isScanning) { + setScanningValue(""); + } + setIsScanning(isScanning); + }; + + useWindowEvent("keydown", (event) => { + if (!isScanning) return; + event.preventDefault(); + setScanningValue(prevState => prevState + event.key); + if (["\n", "\r", "Enter"].includes(event.key)) { + setScannedValue(scanningValue); + setIsScanningValue(false); + } + }); + + return { + isScanning, + setIsScanning: setIsScanningValue, + scannedValue, + setScannedValue, + }; +}; + +export default useScanning; diff --git a/src/pages/ReceiptPage/index.tsx b/src/pages/ReceiptPage/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/ReceiptPage/ui/ReceiptPage.module.css b/src/pages/ReceiptPage/ui/ReceiptPage.module.css new file mode 100644 index 0000000..45c901c --- /dev/null +++ b/src/pages/ReceiptPage/ui/ReceiptPage.module.css @@ -0,0 +1,7 @@ +.container { + display: flex; + flex-direction: column; + flex: 1; + gap: rem(10); +} + diff --git a/src/pages/ReceiptPage/ui/ReceiptPage.tsx b/src/pages/ReceiptPage/ui/ReceiptPage.tsx new file mode 100644 index 0000000..55bc384 --- /dev/null +++ b/src/pages/ReceiptPage/ui/ReceiptPage.tsx @@ -0,0 +1,61 @@ +import { ReceiptContextProvider } from "../components/NewReceipt/contexts/ReceiptContext.tsx"; +import ReceiptUserSelect from "../components/NewReceipt/components/ReceiptUserSelect.tsx"; +import ReceiptEditor from "../components/NewReceipt/components/ReceiptEditor.tsx"; +import { Button, Stack } from "@mantine/core"; +import { useState } from "react"; +import ReceiptQrCodeScan from "../components/ReceiptEditing/components/ReceiptQrCodeScan.tsx"; +import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; + +enum ReceiptType { + NEW_RECEIPT, + RECEIPT_EDITING, + UNDEFINED, +} + +const ReceiptPage = () => { + const [receiptType, setReceiptType] = useState(ReceiptType.UNDEFINED); + + const getContent = () => { + if (receiptType === ReceiptType.UNDEFINED) { + return ( + + + + + ); + } + if (receiptType === ReceiptType.NEW_RECEIPT) { + return ( + + + + + + + ); + } + return ( + + ); + }; + + return ( + + {getContent()} + + ); +}; + +export default ReceiptPage; diff --git a/src/pages/ResiduesPage/components/ResidualBoxes/ResidualBoxes.tsx b/src/pages/ResiduesPage/components/ResidualBoxes/ResidualBoxes.tsx new file mode 100644 index 0000000..f269bf7 --- /dev/null +++ b/src/pages/ResiduesPage/components/ResidualBoxes/ResidualBoxes.tsx @@ -0,0 +1,83 @@ +import { ResidualBoxSchema } from "../../../../client"; +import { Accordion, ActionIcon, Center, Checkbox, Text, Tooltip } from "@mantine/core"; +import { IconBox, IconTrash } from "@tabler/icons-react"; +import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx"; +import { useResiduesContext } from "../../contexts/ResiduesContext.tsx"; + +type Props = { + boxes?: ResidualBoxSchema[]; +} + +const ResidualBoxes = ({ boxes }: Props) => { + const { onDeleteBoxClick, boxIdsToPrint } = useResiduesContext(); + if (!boxes || boxes.length == 0) return; + + const boxIds = boxes.map((box) => box.id.toString()); + + const removeBoxButton = (box: ResidualBoxSchema) => { + return ( + + onDeleteBoxClick(box)} + mx={"md"} + > + + + + ); + }; + + const checkboxToPrint = (box: ResidualBoxSchema) => { + return ( + { + if (boxIdsToPrint.has(box.id)) { + boxIdsToPrint.delete(box.id); + } else { + boxIdsToPrint.add(box.id); + } + }} + /> + ); + }; + + const renderBox = (box: ResidualBoxSchema) => { + return ( + +
+ }> + Короб {box.id} + + {checkboxToPrint(box)} + {removeBoxButton(box)} +
+ + { + box.residualProducts.length > 0 ? ( + a.id - b.id)} + /> + ) : ( + Пустой + ) + } + +
+ ); + }; + + return ( + + {boxes.sort((a, b) => a.id - b.id).map(box => renderBox(box))} + + ); +}; + +export default ResidualBoxes; diff --git a/src/pages/ResiduesPage/components/ResidualProductsTable/ResidualProductsTable.tsx b/src/pages/ResiduesPage/components/ResidualProductsTable/ResidualProductsTable.tsx new file mode 100644 index 0000000..58a2176 --- /dev/null +++ b/src/pages/ResiduesPage/components/ResidualProductsTable/ResidualProductsTable.tsx @@ -0,0 +1,84 @@ +import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx"; +import { ResidualProductSchema, ResiduesService } from "../../../../client"; +import { ActionIcon, Flex, Tooltip } from "@mantine/core"; +import { IconEdit, IconTrash } from "@tabler/icons-react"; +import { MRT_TableOptions } from "mantine-react-table"; +import { notifications } from "../../../../shared/lib/notifications.ts"; +import { modals } from "@mantine/modals"; +import { useResiduesContext } from "../../contexts/ResiduesContext.tsx"; +import useResiduesTableColumns from "../../hooks/residuesTableColumns.tsx"; + + +type Props = { + items: ResidualProductSchema[]; +} + +const ResidualProductsTable = ({ items }: Props) => { + const columns = useResiduesTableColumns(); + const { selectedClient, refetchClient } = useResiduesContext(); + + const onDeleteClick = (residualProduct: ResidualProductSchema) => { + ResiduesService.deleteResidualProduct({ + residualProductId: residualProduct.id, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message }); + refetchClient(); + }) + .catch(err => console.log(err)); + }; + + const onEditClick = (residualProduct: ResidualProductSchema) => { + if (!selectedClient) return; + modals.openContextModal({ + modal: "residualProductModal", + title: "Редактирование товара на паллете", + withCloseButton: false, + innerProps: { + client: selectedClient, + updateOnSubmit: refetchClient, + residuesData: { + residualProductId: residualProduct.id, + product: residualProduct.product, + quantity: residualProduct.quantity, + }, + }, + }); + }; + + return ( + ( + + + onDeleteClick(row.original)} + variant={"default"}> + + + + + onEditClick(row.original)} + variant={"default"}> + + + + + ), + } as MRT_TableOptions + } + /> + ); +}; + +export default ResidualProductsTable; diff --git a/src/pages/ResiduesPage/components/ResiduesHeader/ResiduesHeader.tsx b/src/pages/ResiduesPage/components/ResiduesHeader/ResiduesHeader.tsx new file mode 100644 index 0000000..a45858b --- /dev/null +++ b/src/pages/ResiduesPage/components/ResiduesHeader/ResiduesHeader.tsx @@ -0,0 +1,34 @@ +import { Group } from "@mantine/core"; +import { useResiduesContext } from "../../contexts/ResiduesContext.tsx"; +import useResiduesPdf from "../../hooks/useResiduesPdf.tsx"; +import { IconQrcode } from "@tabler/icons-react"; +import InlineButton from "../../../../components/InlineButton/InlineButton.tsx"; + + +const ResiduesHeader = () => { + const { + onCreatePalletClick, + onCreateBoxClick, + } = useResiduesContext(); + + const { + onGetPalletsPdfClick, + } = useResiduesPdf(); + + return ( + + onCreatePalletClick()}> + Добавить паллет + + onCreateBoxClick()}> + Добавить короб + + onGetPalletsPdfClick()}> + + Печать + + + ); +}; + +export default ResiduesHeader; diff --git a/src/pages/ResiduesPage/components/ResiduesPageContent/ResiduesPageContent.tsx b/src/pages/ResiduesPage/components/ResiduesPageContent/ResiduesPageContent.tsx new file mode 100644 index 0000000..e6a0be3 --- /dev/null +++ b/src/pages/ResiduesPage/components/ResiduesPageContent/ResiduesPageContent.tsx @@ -0,0 +1,21 @@ +import { Group, Stack } from "@mantine/core"; +import ClientSelect from "../../../../components/Selects/ClientSelect/ClientSelect.tsx"; +import ResiduesHeader from "../ResiduesHeader/ResiduesHeader.tsx"; +import ResiduesTree from "../ResiduesTree/ResiduesTree.tsx"; +import { useResiduesContext } from "../../contexts/ResiduesContext.tsx"; + +const ResiduesPageContent = () => { + const { selectedClient, selectClient } = useResiduesContext(); + + return ( + + + + {selectedClient && } + + {selectedClient && } + + ); +}; + +export default ResiduesPageContent; diff --git a/src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx b/src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx new file mode 100644 index 0000000..d5194fd --- /dev/null +++ b/src/pages/ResiduesPage/components/ResiduesTree/ResiduesTree.tsx @@ -0,0 +1,135 @@ +import { + Accordion, + ActionIcon, + Button, + Center, Checkbox, + Group, + rem, + ScrollArea, + Stack, + Text, + Title, + Tooltip, +} from "@mantine/core"; +import { IconPlus, IconSpace, IconTrash } from "@tabler/icons-react"; +import { useResiduesContext } from "../../contexts/ResiduesContext.tsx"; +import { ResidualBoxSchema, ResidualPalletSchema, ResidualProductSchema } from "../../../../client"; +import ResidualProductsTable from "../ResidualProductsTable/ResidualProductsTable.tsx"; +import ResidualBoxes from "../ResidualBoxes/ResidualBoxes.tsx"; +import residualBoxes from "../ResidualBoxes/ResidualBoxes.tsx"; + +const ResiduesTree = () => { + const { + selectedClient, + onDeletePalletClick, + onCreateBoxClick, + palletIdsToPrint, + } = useResiduesContext(); + const palletIds: string[] = []; + + const sortById = (data?: ResidualPalletSchema[] | ResidualBoxSchema[] | ResidualProductSchema[]) => { + return data?.sort((a, b) => a.id - b.id); + }; + + const checkboxToPrint = (pallet: ResidualPalletSchema) => { + return ( + { + if (palletIdsToPrint.has(pallet.id)) { + palletIdsToPrint.delete(pallet.id); + } else { + palletIdsToPrint.add(pallet.id); + } + }} + /> + ); + }; + + const getPallets = () => { + const sortedPallets = sortById(selectedClient?.pallets) as ResidualPalletSchema[]; + return sortedPallets?.map((pallet => { + palletIds.push(pallet.id.toString()); + return ( + +
+ }> + Паллет - П{pallet.id} + + {checkboxToPrint(pallet)} + {removePalletButton(pallet)} +
+ + {getPalletContent(pallet)} + +
+ ); + })) ?? []; + }; + + const removePalletButton = (pallet: ResidualPalletSchema) => { + return ( + + onDeletePalletClick(pallet)} + mx={"md"} + > + + + + ); + }; + + const createBoxButton = (palletId: number) => { + return ( + + ); + }; + + const getPalletContent = (pallet: ResidualPalletSchema) => { + const isEmpty = pallet.boxes.length === 0 && pallet.residualProducts.length === 0; + const isBox = pallet.residualProducts.length === 0; + const title = isEmpty ? "Пустой" : isBox ? "Короба" : "Товары"; + + const residualProducts = sortById(pallet.residualProducts) as ResidualProductSchema[]; + + return ( + + + + Дата добавления: {new Date(pallet.createdAt).toLocaleString("ru-RU")} + {title} + + {isBox && createBoxButton(pallet.id)} + + {residualProducts.length > 0 && } + {residualBoxes.length > 0 && } + + ); + }; + + return ( + + + + {getPallets()} + + + ); +}; + +export default ResiduesTree; \ No newline at end of file diff --git a/src/pages/ResiduesPage/contexts/ResiduesContext.tsx b/src/pages/ResiduesPage/contexts/ResiduesContext.tsx new file mode 100644 index 0000000..817af14 --- /dev/null +++ b/src/pages/ResiduesPage/contexts/ResiduesContext.tsx @@ -0,0 +1,176 @@ +import { createContext, FC, useContext, useState } from "react"; +import { + ClientDetailedSchema, + ClientSchema, + ClientService, + ResidualBoxSchema, + ResidualPalletSchema, + ResiduesService, +} from "../../../client"; +import { modals } from "@mantine/modals"; +import { Text } from "@mantine/core"; +import { notifications } from "../../../shared/lib/notifications.ts"; +import { useSet } from "@mantine/hooks"; + +type ResiduesContextState = { + selectedClient?: ClientDetailedSchema; + selectClient: (client?: ClientSchema) => void; + refetchClient: () => void; + onDeletePalletClick: (pallet: ResidualPalletSchema) => void; + onCreatePalletClick: () => void; + onDeleteBoxClick: (box: ResidualBoxSchema) => void; + onCreateBoxClick: (pallet?: number) => void; + boxIdsToPrint: Set; + palletIdsToPrint: Set; +}; + +const ResiduesContext = createContext( + undefined, +); + +const useResiduesContextState = () => { + const [selectedClient, setSelectedClient] = useState(); + const boxIdsToPrint = useSet(); + const palletIdsToPrint = useSet(); + + const fetchClient = (clientId?: number) => { + if (!clientId) { + setSelectedClient(undefined); + return; + } + + ClientService.getClient({ + clientId, + }).then(res => { + setSelectedClient(res.client); + }).catch(err => console.log(err)); + }; + + const selectClient = (client?: ClientSchema) => { + fetchClient(client?.id); + }; + + const refetchClient = () => { + fetchClient(selectedClient?.id); + }; + + const onDeletePallet = (palletId: number) => { + ResiduesService.deleteResidualPallet({ palletId }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + return; + } + palletIdsToPrint.delete(palletId); + refetchClient(); + }) + .catch(err => console.log(err)); + }; + + const onDeletePalletClick = (pallet: ResidualPalletSchema) => { + if (!selectedClient) return; + if (pallet.boxes.length === 0 && pallet.residualProducts.length === 0) { + onDeletePallet(pallet.id); + return; + } + + modals.openConfirmModal({ + title: "Удаление паллета", + children: Вы уверены что хотите удалить паллет?, + labels: { confirm: "Да", cancel: "Нет" }, + confirmProps: { color: "red" }, + onConfirm: () => onDeletePallet(pallet.id), + }); + }; + + const onDeleteBox = (boxId: number) => { + ResiduesService.deleteResidualBox({ boxId }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + return; + } + boxIdsToPrint.delete(boxId); + refetchClient(); + }) + .catch(err => console.log(err)); + }; + + const onDeleteBoxClick = (box: ResidualBoxSchema) => { + if (!selectedClient) return; + if (box.residualProducts.length === 0) { + onDeleteBox(box.id); + return; + } + + modals.openConfirmModal({ + title: "Удаление короба", + children: Вы уверены что хотите удалить короб?, + labels: { confirm: "Да", cancel: "Нет" }, + confirmProps: { color: "red" }, + onConfirm: () => onDeleteBox(box.id), + }); + }; + + const onCreatePalletClick = () => { + if (!selectedClient) return; + ResiduesService.createResidualPallet({ + requestBody: { clientId: selectedClient.id }, + }) + .then(({ ok, message }) => { + if (!ok) notifications.error({ message }); + refetchClient(); + }) + .catch(err => console.log(err)); + }; + + const onCreateBoxClick = (palletId?: number) => { + if (!selectedClient) return; + ResiduesService.createResidualBox({ + requestBody: { + palletId: palletId ?? null, + clientId: palletId ? null : selectedClient.id, + }, + }) + .then(({ ok, message }) => { + if (!ok) notifications.error({ message }); + refetchClient(); + }) + .catch(err => console.log(err)); + }; + + return { + selectedClient, + selectClient, + refetchClient, + onDeletePalletClick, + onCreatePalletClick, + onDeleteBoxClick, + onCreateBoxClick, + boxIdsToPrint, + palletIdsToPrint, + }; +}; + +type ResiduesContextProviderProps = { + children: React.ReactNode; +}; + +export const ResiduesContextProvider: FC = ({ children }) => { + const state = useResiduesContextState(); + return ( + + {children} + + ); +}; + +export const useResiduesContext = () => { + const context = useContext(ResiduesContext); + if (!context) { + throw new Error( + "useResiduesContext must be used within a ResiduesContextProvider", + ); + } + return context; +}; diff --git a/src/pages/ResiduesPage/hooks/residuesTableColumns.tsx b/src/pages/ResiduesPage/hooks/residuesTableColumns.tsx new file mode 100644 index 0000000..56467eb --- /dev/null +++ b/src/pages/ResiduesPage/hooks/residuesTableColumns.tsx @@ -0,0 +1,32 @@ +import { useMemo } from "react"; +import { MRT_ColumnDef, MRT_RowData } from "mantine-react-table"; + + +const useResiduesTableColumns = () => { + return useMemo[]>( + () => [ + { + header: "Название", + accessorKey: "product.name", + Cell: ({ row }) => row.original.product?.name ?? "-", + }, + { + header: "Артикул", + accessorKey: "product.article", + Cell: ({ row }) => row.original.product?.article ?? "-", + }, + { + header: "Размер", + accessorKey: "product.size", + Cell: ({ row }) => row.original.product?.size ?? "-", + }, + { + header: "Количество", + accessorKey: "quantity", + }, + ], + [], + ); +}; + +export default useResiduesTableColumns; diff --git a/src/pages/ResiduesPage/hooks/useResiduesPdf.tsx b/src/pages/ResiduesPage/hooks/useResiduesPdf.tsx new file mode 100644 index 0000000..de20b79 --- /dev/null +++ b/src/pages/ResiduesPage/hooks/useResiduesPdf.tsx @@ -0,0 +1,31 @@ +import { useResiduesContext } from "../contexts/ResiduesContext.tsx"; +import { notifications } from "../../../shared/lib/notifications.ts"; + +const useResiduesPdf = () => { + const { palletIdsToPrint, boxIdsToPrint } = useResiduesContext(); + + const basePdfUrl = `${import.meta.env.VITE_API_URL}/residues/pdf`; + + const getPdf = (url: string) => { + const pdfWindow = window.open(url); + if (!pdfWindow) return; + pdfWindow.print(); + }; + + const onGetPalletsPdfClick = () => { + if (palletIdsToPrint.size === 0 && boxIdsToPrint.size === 0) { + notifications.show({ message: "Не выбран ни один элемент для печати "}); + return; + } + + const palletIdsStr = palletIdsToPrint.values().toArray().join(",") + const boxIdsStr = boxIdsToPrint.values().toArray().join(",") + getPdf(`${basePdfUrl}/?pallet_ids=${palletIdsStr}&box_ids=${boxIdsStr}`); + }; + + return { + onGetPalletsPdfClick, + }; +}; + +export default useResiduesPdf; diff --git a/src/pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx b/src/pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx new file mode 100644 index 0000000..3cc04da --- /dev/null +++ b/src/pages/ResiduesPage/modals/ResidualProductModal/ResidualProductModal.tsx @@ -0,0 +1,83 @@ +import { useForm } from "@mantine/form"; +import { ContextModalProps } from "@mantine/modals"; +import { Button, Flex, NumberInput, rem } from "@mantine/core"; +import { ClientDetailedSchema, ProductSchema, ResiduesService, UpdateResidualProductSchema } from "../../../../client"; +import { ResidualModalForm, UpdateResidualProductData } from "../../types/ResidualProductData.tsx"; +import { notifications } from "../../../../shared/lib/notifications.ts"; +import ProductSelect from "../../../../components/ProductSelect/ProductSelect.tsx"; + + +type Props = { + updateOnSubmit: () => void; + client: ClientDetailedSchema; + residuesData: UpdateResidualProductData; +} + +const ResidualProductModal = ({ + context, + id, + innerProps, + }: ContextModalProps) => { + const initialValues: ResidualModalForm = { + quantity: innerProps.residuesData.quantity ?? 0, + product: innerProps.residuesData.product, + }; + const form = useForm({ + initialValues, + validate: { + product: product => !product && "Необходимо выбрать товар", + quantity: quantity => quantity === 0 && "Слишком мало товара", + }, + }); + + const updateResidualProduct = () => { + const data = { + ...form.values, + productId: form.values.product!.id, + } as UpdateResidualProductSchema; + + ResiduesService.updateResidualProduct({ + requestBody: { data }, + residualProductId: innerProps.residuesData.residualProductId, + }) + .then(({ ok, message }) => { + notifications.guess(ok, { message: message }); + innerProps.updateOnSubmit(); + if (ok) context.closeContextModal(id); + }) + .catch(err => console.log(err)); + }; + + return ( +
updateResidualProduct())}> + + + + + + + +
+ ); +}; + +export default ResidualProductModal; diff --git a/src/pages/ResiduesPage/types/ResidualProductData.tsx b/src/pages/ResiduesPage/types/ResidualProductData.tsx new file mode 100644 index 0000000..377ec18 --- /dev/null +++ b/src/pages/ResiduesPage/types/ResidualProductData.tsx @@ -0,0 +1,12 @@ +import { ProductSchema } from "../../../client"; + +export type ResidualModalForm = { + quantity: number; + product: ProductSchema | null; +} + +export type UpdateResidualProductData = { + product: ProductSchema | null; + quantity: number; + residualProductId: number; +}; diff --git a/src/pages/ResiduesPage/ui/ResiduesPage.tsx b/src/pages/ResiduesPage/ui/ResiduesPage.tsx new file mode 100644 index 0000000..8c52586 --- /dev/null +++ b/src/pages/ResiduesPage/ui/ResiduesPage.tsx @@ -0,0 +1,19 @@ +import styles from "../../ProductsPage/ui/ProductsPage.module.css"; +import PageBlock from "../../../components/PageBlock/PageBlock.tsx"; +import { ResiduesContextProvider } from "../contexts/ResiduesContext.tsx"; +import ResiduesPageContent from "../components/ResiduesPageContent/ResiduesPageContent.tsx"; + + +const ResiduesPage = () => { + return ( +
+ + + + + +
+ ); +}; + +export default ResiduesPage; diff --git a/src/routes/receipt.lazy.tsx b/src/routes/receipt.lazy.tsx new file mode 100644 index 0000000..4873537 --- /dev/null +++ b/src/routes/receipt.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from '@tanstack/react-router' +import ReceiptPage from "../pages/ReceiptPage/ui/ReceiptPage.tsx"; + +export const Route = createLazyFileRoute('/receipt')({ + component: () => , +}) diff --git a/src/routes/residues.lazy.tsx b/src/routes/residues.lazy.tsx new file mode 100644 index 0000000..3accf6d --- /dev/null +++ b/src/routes/residues.lazy.tsx @@ -0,0 +1,6 @@ +import { createLazyFileRoute } from "@tanstack/react-router"; +import ResiduesPage from "../pages/ResiduesPage/ui/ResiduesPage.tsx"; + +export const Route = createLazyFileRoute("/residues")({ + component: () => , +});