Compare commits
27 Commits
f28df5a074
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 27f27f4c38 | |||
| 6e19d98f81 | |||
| 7a052f7422 | |||
| b08f921c1c | |||
| d0da5155a1 | |||
| bdbdd10231 | |||
| 9dde9041b1 | |||
| 01260d0768 | |||
| 15a085721e | |||
| 655ae077ca | |||
| 50590fa41c | |||
| c866231730 | |||
| c3d135eba9 | |||
| 82e2ef6db2 | |||
| 4787151b69 | |||
| eea03bd70a | |||
| f7d514df4b | |||
| b09479302b | |||
| 74665e123b | |||
| 4fee2da42c | |||
| cc39d13b1c | |||
| 2a7a50886c | |||
| 3b082d7836 | |||
| 37136b83bc | |||
| d03ba66ebb | |||
| e4f8e90ae6 | |||
| d347c09199 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
FROM oven/bun:latest AS base
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
FROM base AS install
|
||||||
|
RUN mkdir -p /temp/dev
|
||||||
|
COPY package.json bun.lock /temp/dev/
|
||||||
|
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# install with --production (exclude devDependencies)
|
||||||
|
RUN mkdir -p /temp/prod
|
||||||
|
COPY package.json bun.lock /temp/prod/
|
||||||
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# copy node_modules from temp directory
|
||||||
|
# then copy all (non-ignored) project files into the image
|
||||||
|
FROM base AS prerelease
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=prerelease /usr/src/app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
2
build-docker.sh
Executable file
2
build-docker.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
docker build -t git.denco.store/fakz9/fulfillment-frontend:latest .
|
||||||
|
docker push git.denco.store/fakz9/fulfillment-frontend:latest
|
||||||
12
nginx.conf
Normal file
12
nginx.conf
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -32,6 +32,9 @@ export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
|
|||||||
export type { BaseEnumSchema } from './models/BaseEnumSchema';
|
export type { BaseEnumSchema } from './models/BaseEnumSchema';
|
||||||
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
||||||
export type { BaseMessageSchema } from './models/BaseMessageSchema';
|
export type { BaseMessageSchema } from './models/BaseMessageSchema';
|
||||||
|
export type { BasePlaceSchema } from './models/BasePlaceSchema';
|
||||||
|
export type { BasePlaceTypeSchema } from './models/BasePlaceTypeSchema';
|
||||||
|
export type { BasePlaceTypeWithCountSchema } from './models/BasePlaceTypeWithCountSchema';
|
||||||
export type { BaseProjectSchema } from './models/BaseProjectSchema';
|
export type { BaseProjectSchema } from './models/BaseProjectSchema';
|
||||||
export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema';
|
export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema';
|
||||||
export type { BaseStatusSchema } from './models/BaseStatusSchema';
|
export type { BaseStatusSchema } from './models/BaseStatusSchema';
|
||||||
@@ -117,6 +120,7 @@ export type { CardUpdateServiceQuantityResponse } from './models/CardUpdateServi
|
|||||||
export type { CardUpdateServiceRequest } from './models/CardUpdateServiceRequest';
|
export type { CardUpdateServiceRequest } from './models/CardUpdateServiceRequest';
|
||||||
export type { CardUpdateServiceResponse } from './models/CardUpdateServiceResponse';
|
export type { CardUpdateServiceResponse } from './models/CardUpdateServiceResponse';
|
||||||
export type { ChatSchema } from './models/ChatSchema';
|
export type { ChatSchema } from './models/ChatSchema';
|
||||||
|
export type { ChatsListItemSchema } from './models/ChatsListItemSchema';
|
||||||
export type { CityBreakdownFromExcelSchema } from './models/CityBreakdownFromExcelSchema';
|
export type { CityBreakdownFromExcelSchema } from './models/CityBreakdownFromExcelSchema';
|
||||||
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
export type { ClientCreateRequest } from './models/ClientCreateRequest';
|
||||||
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
export type { ClientCreateResponse } from './models/ClientCreateResponse';
|
||||||
@@ -151,6 +155,7 @@ export type { CreateDepartmentRequest } from './models/CreateDepartmentRequest';
|
|||||||
export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse';
|
export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse';
|
||||||
export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest';
|
export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest';
|
||||||
export type { CreateDepartmentSectionResponse } from './models/CreateDepartmentSectionResponse';
|
export type { CreateDepartmentSectionResponse } from './models/CreateDepartmentSectionResponse';
|
||||||
|
export type { CreateGuestUrlResponse } from './models/CreateGuestUrlResponse';
|
||||||
export type { CreateMarketplaceRequest } from './models/CreateMarketplaceRequest';
|
export type { CreateMarketplaceRequest } from './models/CreateMarketplaceRequest';
|
||||||
export type { CreateMarketplaceResponse } from './models/CreateMarketplaceResponse';
|
export type { CreateMarketplaceResponse } from './models/CreateMarketplaceResponse';
|
||||||
export type { CreatePalletResponse } from './models/CreatePalletResponse';
|
export type { CreatePalletResponse } from './models/CreatePalletResponse';
|
||||||
@@ -158,6 +163,10 @@ export type { CreatePaymentRecordRequest } from './models/CreatePaymentRecordReq
|
|||||||
export type { CreatePaymentRecordResponse } from './models/CreatePaymentRecordResponse';
|
export type { CreatePaymentRecordResponse } from './models/CreatePaymentRecordResponse';
|
||||||
export type { CreatePayRateRequest } from './models/CreatePayRateRequest';
|
export type { CreatePayRateRequest } from './models/CreatePayRateRequest';
|
||||||
export type { CreatePayRateResponse } from './models/CreatePayRateResponse';
|
export type { CreatePayRateResponse } from './models/CreatePayRateResponse';
|
||||||
|
export type { CreatePlaceRequest } from './models/CreatePlaceRequest';
|
||||||
|
export type { CreatePlaceResponse } from './models/CreatePlaceResponse';
|
||||||
|
export type { CreatePlaceTypeRequest } from './models/CreatePlaceTypeRequest';
|
||||||
|
export type { CreatePlaceTypeResponse } from './models/CreatePlaceTypeResponse';
|
||||||
export type { CreatePositionRequest } from './models/CreatePositionRequest';
|
export type { CreatePositionRequest } from './models/CreatePositionRequest';
|
||||||
export type { CreatePositionResponse } from './models/CreatePositionResponse';
|
export type { CreatePositionResponse } from './models/CreatePositionResponse';
|
||||||
export type { CreateProjectRequest } from './models/CreateProjectRequest';
|
export type { CreateProjectRequest } from './models/CreateProjectRequest';
|
||||||
@@ -196,6 +205,8 @@ export type { DeletePaymentRecordRequest } from './models/DeletePaymentRecordReq
|
|||||||
export type { DeletePaymentRecordResponse } from './models/DeletePaymentRecordResponse';
|
export type { DeletePaymentRecordResponse } from './models/DeletePaymentRecordResponse';
|
||||||
export type { DeletePayRateRequest } from './models/DeletePayRateRequest';
|
export type { DeletePayRateRequest } from './models/DeletePayRateRequest';
|
||||||
export type { DeletePayRateResponse } from './models/DeletePayRateResponse';
|
export type { DeletePayRateResponse } from './models/DeletePayRateResponse';
|
||||||
|
export type { DeletePlaceResponse } from './models/DeletePlaceResponse';
|
||||||
|
export type { DeletePlaceTypeResponse } from './models/DeletePlaceTypeResponse';
|
||||||
export type { DeletePositionRequest } from './models/DeletePositionRequest';
|
export type { DeletePositionRequest } from './models/DeletePositionRequest';
|
||||||
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
export type { DeletePositionResponse } from './models/DeletePositionResponse';
|
||||||
export type { DeleteProjectResponse } from './models/DeleteProjectResponse';
|
export type { DeleteProjectResponse } from './models/DeleteProjectResponse';
|
||||||
@@ -220,10 +231,16 @@ export type { DepartmentSectionSchema } from './models/DepartmentSectionSchema';
|
|||||||
export type { EditMessageRequest } from './models/EditMessageRequest';
|
export type { EditMessageRequest } from './models/EditMessageRequest';
|
||||||
export type { EditMessageResponse } from './models/EditMessageResponse';
|
export type { EditMessageResponse } from './models/EditMessageResponse';
|
||||||
export type { EditMessageSchema } from './models/EditMessageSchema';
|
export type { EditMessageSchema } from './models/EditMessageSchema';
|
||||||
|
export type { EditPlaceRequest } from './models/EditPlaceRequest';
|
||||||
|
export type { EditPlaceResponse } from './models/EditPlaceResponse';
|
||||||
|
export type { EditPlaceSchema } from './models/EditPlaceSchema';
|
||||||
|
export type { EditPlaceTypeRequest } from './models/EditPlaceTypeRequest';
|
||||||
|
export type { EditPlaceTypeResponse } from './models/EditPlaceTypeResponse';
|
||||||
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
|
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
|
||||||
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
|
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
|
||||||
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
|
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
|
||||||
export type { FinishShiftResponse } from './models/FinishShiftResponse';
|
export type { FinishShiftResponse } from './models/FinishShiftResponse';
|
||||||
|
export type { FlatPlaceTypeSchema } from './models/FlatPlaceTypeSchema';
|
||||||
export type { FullProjectSchema } from './models/FullProjectSchema';
|
export type { FullProjectSchema } from './models/FullProjectSchema';
|
||||||
export type { GenerateInviteCodeRequest } from './models/GenerateInviteCodeRequest';
|
export type { GenerateInviteCodeRequest } from './models/GenerateInviteCodeRequest';
|
||||||
export type { GenerateInviteCodeResponse } from './models/GenerateInviteCodeResponse';
|
export type { GenerateInviteCodeResponse } from './models/GenerateInviteCodeResponse';
|
||||||
@@ -255,14 +272,18 @@ export type { GetCardProductsBarcodesPdfResponse } from './models/GetCardProduct
|
|||||||
export type { GetCardSummariesRequest } from './models/GetCardSummariesRequest';
|
export type { GetCardSummariesRequest } from './models/GetCardSummariesRequest';
|
||||||
export type { GetChatRequest } from './models/GetChatRequest';
|
export type { GetChatRequest } from './models/GetChatRequest';
|
||||||
export type { GetChatResponse } from './models/GetChatResponse';
|
export type { GetChatResponse } from './models/GetChatResponse';
|
||||||
|
export type { GetChatsListResponse } from './models/GetChatsListResponse';
|
||||||
export type { GetClientMarketplacesRequest } from './models/GetClientMarketplacesRequest';
|
export type { GetClientMarketplacesRequest } from './models/GetClientMarketplacesRequest';
|
||||||
export type { GetClientMarketplacesResponse } from './models/GetClientMarketplacesResponse';
|
export type { GetClientMarketplacesResponse } from './models/GetClientMarketplacesResponse';
|
||||||
export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectionsResponse';
|
export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectionsResponse';
|
||||||
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
||||||
|
export type { GetFlatPlaceTypesResponse } from './models/GetFlatPlaceTypesResponse';
|
||||||
export type { GetManagersResponse } from './models/GetManagersResponse';
|
export type { GetManagersResponse } from './models/GetManagersResponse';
|
||||||
export type { GetMessagesRequest } from './models/GetMessagesRequest';
|
export type { GetMessagesRequest } from './models/GetMessagesRequest';
|
||||||
export type { GetMessagesResponse } from './models/GetMessagesResponse';
|
export type { GetMessagesResponse } from './models/GetMessagesResponse';
|
||||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||||
|
export type { GetPlacesResponse } from './models/GetPlacesResponse';
|
||||||
|
export type { GetPlaceTypesResponse } from './models/GetPlaceTypesResponse';
|
||||||
export type { GetPlannedWorkShiftsResponse } from './models/GetPlannedWorkShiftsResponse';
|
export type { GetPlannedWorkShiftsResponse } from './models/GetPlannedWorkShiftsResponse';
|
||||||
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
|
||||||
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
export type { GetProductBarcodePdfResponse } from './models/GetProductBarcodePdfResponse';
|
||||||
@@ -310,6 +331,8 @@ export type { PayRateSchema } from './models/PayRateSchema';
|
|||||||
export type { PayRateSchemaBase } from './models/PayRateSchemaBase';
|
export type { PayRateSchemaBase } from './models/PayRateSchemaBase';
|
||||||
export type { PayrollSchemeSchema } from './models/PayrollSchemeSchema';
|
export type { PayrollSchemeSchema } from './models/PayrollSchemeSchema';
|
||||||
export type { PermissionSchema } from './models/PermissionSchema';
|
export type { PermissionSchema } from './models/PermissionSchema';
|
||||||
|
export type { PlaceSchema } from './models/PlaceSchema';
|
||||||
|
export type { PlaceTypeSchema } from './models/PlaceTypeSchema';
|
||||||
export type { PlannedWorkShiftSchema } from './models/PlannedWorkShiftSchema';
|
export type { PlannedWorkShiftSchema } from './models/PlannedWorkShiftSchema';
|
||||||
export type { PlanningTableRow } from './models/PlanningTableRow';
|
export type { PlanningTableRow } from './models/PlanningTableRow';
|
||||||
export type { PositionSchema } from './models/PositionSchema';
|
export type { PositionSchema } from './models/PositionSchema';
|
||||||
@@ -343,6 +366,7 @@ export type { ProjectGeneralInfoSchema } from './models/ProjectGeneralInfoSchema
|
|||||||
export type { ProjectSchema } from './models/ProjectSchema';
|
export type { ProjectSchema } from './models/ProjectSchema';
|
||||||
export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
|
export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
|
||||||
export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema';
|
export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema';
|
||||||
|
export type { ReorderResponse } from './models/ReorderResponse';
|
||||||
export type { RepeatSendingMessageSchema } from './models/RepeatSendingMessageSchema';
|
export type { RepeatSendingMessageSchema } from './models/RepeatSendingMessageSchema';
|
||||||
export type { RepeatSendingTextMessageRequest } from './models/RepeatSendingTextMessageRequest';
|
export type { RepeatSendingTextMessageRequest } from './models/RepeatSendingTextMessageRequest';
|
||||||
export type { RepeatSendingTextMessageResponse } from './models/RepeatSendingTextMessageResponse';
|
export type { RepeatSendingTextMessageResponse } from './models/RepeatSendingTextMessageResponse';
|
||||||
@@ -481,5 +505,6 @@ export { TaskService } from './services/TaskService';
|
|||||||
export { TimeTrackingService } from './services/TimeTrackingService';
|
export { TimeTrackingService } from './services/TimeTrackingService';
|
||||||
export { TransactionService } from './services/TransactionService';
|
export { TransactionService } from './services/TransactionService';
|
||||||
export { UserService } from './services/UserService';
|
export { UserService } from './services/UserService';
|
||||||
|
export { WmsService } from './services/WmsService';
|
||||||
export { WorkShiftsService } from './services/WorkShiftsService';
|
export { WorkShiftsService } from './services/WorkShiftsService';
|
||||||
export { WorkShiftsPlanningService } from './services/WorkShiftsPlanningService';
|
export { WorkShiftsPlanningService } from './services/WorkShiftsPlanningService';
|
||||||
|
|||||||
9
src/client/models/BasePlaceSchema.ts
Normal file
9
src/client/models/BasePlaceSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type BasePlaceSchema = {
|
||||||
|
parentId: (number | null);
|
||||||
|
placeTypeId: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/BasePlaceTypeSchema.ts
Normal file
9
src/client/models/BasePlaceTypeSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type BasePlaceTypeSchema = {
|
||||||
|
name: string;
|
||||||
|
parentId: (number | null);
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/BasePlaceTypeWithCountSchema.ts
Normal file
10
src/client/models/BasePlaceTypeWithCountSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type BasePlaceTypeWithCountSchema = {
|
||||||
|
name: string;
|
||||||
|
parentId: (number | null);
|
||||||
|
childCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -7,6 +7,6 @@ export type CardGroupSchema = {
|
|||||||
id: number;
|
id: number;
|
||||||
name?: (string | null);
|
name?: (string | null);
|
||||||
lexorank: string;
|
lexorank: string;
|
||||||
billRequest?: (GroupBillRequestSchema | null);
|
billRequests?: Array<GroupBillRequestSchema>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type CardSchema = {
|
|||||||
clientId: (number | null);
|
clientId: (number | null);
|
||||||
client: (ClientSchema | null);
|
client: (ClientSchema | null);
|
||||||
shippingWarehouse?: (ShippingWarehouseSchema | string | null);
|
shippingWarehouse?: (ShippingWarehouseSchema | string | null);
|
||||||
billRequest?: (CardBillRequestSchema | null);
|
billRequests?: Array<CardBillRequestSchema>;
|
||||||
group?: (CardGroupSchema | null);
|
group?: (CardGroupSchema | null);
|
||||||
manager?: (UserSchema | null);
|
manager?: (UserSchema | null);
|
||||||
pallets?: Array<PalletSchema>;
|
pallets?: Array<PalletSchema>;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type CardSummary = {
|
|||||||
attributes: Array<CardAttributeSchema>;
|
attributes: Array<CardAttributeSchema>;
|
||||||
shipmentWarehouseId: (number | null);
|
shipmentWarehouseId: (number | null);
|
||||||
shipmentWarehouseName: (string | null);
|
shipmentWarehouseName: (string | null);
|
||||||
billRequest?: (CardBillRequestSchema | null);
|
billRequests: Array<CardBillRequestSchema>;
|
||||||
group?: (CardGroupSchema | null);
|
group?: (CardGroupSchema | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
13
src/client/models/ChatsListItemSchema.ts
Normal file
13
src/client/models/ChatsListItemSchema.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { TgGroupSchema } from './TgGroupSchema';
|
||||||
|
export type ChatsListItemSchema = {
|
||||||
|
id: number;
|
||||||
|
clientId: (number | null);
|
||||||
|
cardId: (number | null);
|
||||||
|
tgGroup: (TgGroupSchema | null);
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ export type ClientDetailedSchema = {
|
|||||||
comment?: (string | null);
|
comment?: (string | null);
|
||||||
details?: (ClientDetailsSchema | null);
|
details?: (ClientDetailsSchema | null);
|
||||||
chat?: (ChatSchema | null);
|
chat?: (ChatSchema | null);
|
||||||
|
isDeleted?: (boolean | null);
|
||||||
pallets?: Array<ResidualPalletSchema>;
|
pallets?: Array<ResidualPalletSchema>;
|
||||||
boxes?: Array<ResidualBoxSchema>;
|
boxes?: Array<ResidualBoxSchema>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ export type ClientSchema = {
|
|||||||
comment?: (string | null);
|
comment?: (string | null);
|
||||||
details?: (ClientDetailsSchema | null);
|
details?: (ClientDetailsSchema | null);
|
||||||
chat?: (ChatSchema | null);
|
chat?: (ChatSchema | null);
|
||||||
|
isDeleted?: (boolean | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
src/client/models/CreateGuestUrlResponse.ts
Normal file
10
src/client/models/CreateGuestUrlResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreateGuestUrlResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreatePlaceRequest.ts
Normal file
9
src/client/models/CreatePlaceRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BasePlaceSchema } from './BasePlaceSchema';
|
||||||
|
export type CreatePlaceRequest = {
|
||||||
|
place: BasePlaceSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreatePlaceResponse.ts
Normal file
9
src/client/models/CreatePlaceResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreatePlaceResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreatePlaceTypeRequest.ts
Normal file
9
src/client/models/CreatePlaceTypeRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BasePlaceTypeSchema } from './BasePlaceTypeSchema';
|
||||||
|
export type CreatePlaceTypeRequest = {
|
||||||
|
placeType: BasePlaceTypeSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/CreatePlaceTypeResponse.ts
Normal file
9
src/client/models/CreatePlaceTypeResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type CreatePlaceTypeResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DeletePlaceResponse.ts
Normal file
9
src/client/models/DeletePlaceResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeletePlaceResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/DeletePlaceTypeResponse.ts
Normal file
9
src/client/models/DeletePlaceTypeResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DeletePlaceTypeResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/EditPlaceRequest.ts
Normal file
9
src/client/models/EditPlaceRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { EditPlaceSchema } from './EditPlaceSchema';
|
||||||
|
export type EditPlaceRequest = {
|
||||||
|
place: EditPlaceSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/EditPlaceResponse.ts
Normal file
9
src/client/models/EditPlaceResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type EditPlaceResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
11
src/client/models/EditPlaceSchema.ts
Normal file
11
src/client/models/EditPlaceSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type EditPlaceSchema = {
|
||||||
|
parentId: (number | null);
|
||||||
|
placeTypeId: number;
|
||||||
|
id: number;
|
||||||
|
number: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/EditPlaceTypeRequest.ts
Normal file
9
src/client/models/EditPlaceTypeRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { FlatPlaceTypeSchema } from './FlatPlaceTypeSchema';
|
||||||
|
export type EditPlaceTypeRequest = {
|
||||||
|
placeType: FlatPlaceTypeSchema;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/EditPlaceTypeResponse.ts
Normal file
9
src/client/models/EditPlaceTypeResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type EditPlaceTypeResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/FlatPlaceTypeSchema.ts
Normal file
10
src/client/models/FlatPlaceTypeSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type FlatPlaceTypeSchema = {
|
||||||
|
name: string;
|
||||||
|
parentId: (number | null);
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetChatsListResponse.ts
Normal file
9
src/client/models/GetChatsListResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { ChatsListItemSchema } from './ChatsListItemSchema';
|
||||||
|
export type GetChatsListResponse = {
|
||||||
|
chats: Array<ChatsListItemSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetFlatPlaceTypesResponse.ts
Normal file
9
src/client/models/GetFlatPlaceTypesResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { FlatPlaceTypeSchema } from './FlatPlaceTypeSchema';
|
||||||
|
export type GetFlatPlaceTypesResponse = {
|
||||||
|
placeTypes: Array<FlatPlaceTypeSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetPlaceTypesResponse.ts
Normal file
9
src/client/models/GetPlaceTypesResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { PlaceTypeSchema } from './PlaceTypeSchema';
|
||||||
|
export type GetPlaceTypesResponse = {
|
||||||
|
placeTypes: Array<PlaceTypeSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/GetPlacesResponse.ts
Normal file
9
src/client/models/GetPlacesResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { PlaceSchema } from './PlaceSchema';
|
||||||
|
export type GetPlacesResponse = {
|
||||||
|
places: Array<PlaceSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type GetTimeTrackingRecordsRequest = {
|
export type GetTimeTrackingRecordsRequest = {
|
||||||
date: string;
|
dateFrom: string;
|
||||||
userIds: Array<number>;
|
dateTo: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
14
src/client/models/PlaceSchema.ts
Normal file
14
src/client/models/PlaceSchema.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { BasePlaceTypeWithCountSchema } from './BasePlaceTypeWithCountSchema';
|
||||||
|
export type PlaceSchema = {
|
||||||
|
parentId: (number | null);
|
||||||
|
placeTypeId: number;
|
||||||
|
id: number;
|
||||||
|
number: number;
|
||||||
|
children?: Array<PlaceSchema>;
|
||||||
|
placeType: BasePlaceTypeWithCountSchema;
|
||||||
|
};
|
||||||
|
|
||||||
12
src/client/models/PlaceTypeSchema.ts
Normal file
12
src/client/models/PlaceTypeSchema.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type PlaceTypeSchema = {
|
||||||
|
name: string;
|
||||||
|
parentId: (number | null);
|
||||||
|
id: number;
|
||||||
|
children?: Array<PlaceTypeSchema>;
|
||||||
|
placesCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/ReorderResponse.ts
Normal file
9
src/client/models/ReorderResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type ReorderResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ import type { ManageEmployeeResponse } from '../models/ManageEmployeeResponse';
|
|||||||
import type { ParseCardsExcelResponse } from '../models/ParseCardsExcelResponse';
|
import type { ParseCardsExcelResponse } from '../models/ParseCardsExcelResponse';
|
||||||
import type { ProductsAndServicesGeneralInfoRequest } from '../models/ProductsAndServicesGeneralInfoRequest';
|
import type { ProductsAndServicesGeneralInfoRequest } from '../models/ProductsAndServicesGeneralInfoRequest';
|
||||||
import type { ProductsAndServicesGeneralInfoResponse } from '../models/ProductsAndServicesGeneralInfoResponse';
|
import type { ProductsAndServicesGeneralInfoResponse } from '../models/ProductsAndServicesGeneralInfoResponse';
|
||||||
|
import type { ReorderResponse } from '../models/ReorderResponse';
|
||||||
import type { UpdateCardClientRequest } from '../models/UpdateCardClientRequest';
|
import type { UpdateCardClientRequest } from '../models/UpdateCardClientRequest';
|
||||||
import type { UpdateCardClientResponse } from '../models/UpdateCardClientResponse';
|
import type { UpdateCardClientResponse } from '../models/UpdateCardClientResponse';
|
||||||
import type { UpdateCardManagerRequest } from '../models/UpdateCardManagerRequest';
|
import type { UpdateCardManagerRequest } from '../models/UpdateCardManagerRequest';
|
||||||
@@ -180,14 +181,14 @@ export class CardService {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Reorder
|
* Reorder
|
||||||
* @returns CardSummaryResponse Successful Response
|
* @returns ReorderResponse Successful Response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static reorderCardSummaries({
|
public static reorderCardSummaries({
|
||||||
requestBody,
|
requestBody,
|
||||||
}: {
|
}: {
|
||||||
requestBody: CardSummaryReorderRequest,
|
requestBody: CardSummaryReorderRequest,
|
||||||
}): CancelablePromise<CardSummaryResponse> {
|
}): CancelablePromise<ReorderResponse> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/card/summaries/reorder',
|
url: '/card/summaries/reorder',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { EditMessageRequest } from '../models/EditMessageRequest';
|
|||||||
import type { EditMessageResponse } from '../models/EditMessageResponse';
|
import type { EditMessageResponse } from '../models/EditMessageResponse';
|
||||||
import type { GetChatRequest } from '../models/GetChatRequest';
|
import type { GetChatRequest } from '../models/GetChatRequest';
|
||||||
import type { GetChatResponse } from '../models/GetChatResponse';
|
import type { GetChatResponse } from '../models/GetChatResponse';
|
||||||
|
import type { GetChatsListResponse } from '../models/GetChatsListResponse';
|
||||||
import type { GetMessagesRequest } from '../models/GetMessagesRequest';
|
import type { GetMessagesRequest } from '../models/GetMessagesRequest';
|
||||||
import type { GetMessagesResponse } from '../models/GetMessagesResponse';
|
import type { GetMessagesResponse } from '../models/GetMessagesResponse';
|
||||||
import type { LoadMessagesResponse } from '../models/LoadMessagesResponse';
|
import type { LoadMessagesResponse } from '../models/LoadMessagesResponse';
|
||||||
@@ -150,6 +151,27 @@ export class ChatService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get Client Chats List
|
||||||
|
* @returns GetChatsListResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getClientChatsList({
|
||||||
|
clientId,
|
||||||
|
}: {
|
||||||
|
clientId: number,
|
||||||
|
}): CancelablePromise<GetChatsListResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/chat/for-client/{client_id}',
|
||||||
|
path: {
|
||||||
|
'client_id': clientId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create Chat
|
* Create Chat
|
||||||
* @returns CreateChatResponse Successful Response
|
* @returns CreateChatResponse Successful Response
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { ClientGetResponse } from '../models/ClientGetResponse';
|
|||||||
import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest';
|
import type { ClientUpdateDetailsRequest } from '../models/ClientUpdateDetailsRequest';
|
||||||
import type { ClientUpdateRequest } from '../models/ClientUpdateRequest';
|
import type { ClientUpdateRequest } from '../models/ClientUpdateRequest';
|
||||||
import type { ClientUpdateResponse } from '../models/ClientUpdateResponse';
|
import type { ClientUpdateResponse } from '../models/ClientUpdateResponse';
|
||||||
|
import type { CreateGuestUrlResponse } from '../models/CreateGuestUrlResponse';
|
||||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
import { OpenAPI } from '../core/OpenAPI';
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
import { request as __request } from '../core/request';
|
import { request as __request } from '../core/request';
|
||||||
@@ -148,4 +149,25 @@ export class ClientService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Client Link
|
||||||
|
* @returns CreateGuestUrlResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static clientLink({
|
||||||
|
clientId,
|
||||||
|
}: {
|
||||||
|
clientId: number,
|
||||||
|
}): CancelablePromise<CreateGuestUrlResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/client/link/{client_id}',
|
||||||
|
path: {
|
||||||
|
'client_id': clientId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
211
src/client/services/WmsService.ts
Normal file
211
src/client/services/WmsService.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { CreatePlaceRequest } from '../models/CreatePlaceRequest';
|
||||||
|
import type { CreatePlaceResponse } from '../models/CreatePlaceResponse';
|
||||||
|
import type { CreatePlaceTypeRequest } from '../models/CreatePlaceTypeRequest';
|
||||||
|
import type { CreatePlaceTypeResponse } from '../models/CreatePlaceTypeResponse';
|
||||||
|
import type { DeletePlaceResponse } from '../models/DeletePlaceResponse';
|
||||||
|
import type { DeletePlaceTypeResponse } from '../models/DeletePlaceTypeResponse';
|
||||||
|
import type { EditPlaceRequest } from '../models/EditPlaceRequest';
|
||||||
|
import type { EditPlaceResponse } from '../models/EditPlaceResponse';
|
||||||
|
import type { EditPlaceTypeRequest } from '../models/EditPlaceTypeRequest';
|
||||||
|
import type { EditPlaceTypeResponse } from '../models/EditPlaceTypeResponse';
|
||||||
|
import type { GetFlatPlaceTypesResponse } from '../models/GetFlatPlaceTypesResponse';
|
||||||
|
import type { GetPlacesResponse } from '../models/GetPlacesResponse';
|
||||||
|
import type { GetPlaceTypesResponse } from '../models/GetPlaceTypesResponse';
|
||||||
|
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||||
|
import { OpenAPI } from '../core/OpenAPI';
|
||||||
|
import { request as __request } from '../core/request';
|
||||||
|
export class WmsService {
|
||||||
|
/**
|
||||||
|
* Get Place Types
|
||||||
|
* @returns GetPlaceTypesResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getPlaceTypes(): CancelablePromise<GetPlaceTypesResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/wms/place-type',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Place Type
|
||||||
|
* @returns CreatePlaceTypeResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createPlaceType({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreatePlaceTypeRequest,
|
||||||
|
}): CancelablePromise<CreatePlaceTypeResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/wms/place-type',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Edit Place Type
|
||||||
|
* @returns EditPlaceTypeResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static editPlaceType({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: EditPlaceTypeRequest,
|
||||||
|
}): CancelablePromise<EditPlaceTypeResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/wms/place-type',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Flat Place Types
|
||||||
|
* @returns GetFlatPlaceTypesResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getFlatPlaceTypes({
|
||||||
|
parentPlaceTypeId,
|
||||||
|
}: {
|
||||||
|
parentPlaceTypeId: number,
|
||||||
|
}): CancelablePromise<GetFlatPlaceTypesResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/wms/place-type/flat/{parent_place_type_id}',
|
||||||
|
path: {
|
||||||
|
'parent_place_type_id': parentPlaceTypeId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Place Type
|
||||||
|
* @returns DeletePlaceTypeResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deletePlaceType({
|
||||||
|
placeTypeId,
|
||||||
|
}: {
|
||||||
|
placeTypeId: number,
|
||||||
|
}): CancelablePromise<DeletePlaceTypeResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/wms/place-type/{place_type_id}',
|
||||||
|
path: {
|
||||||
|
'place_type_id': placeTypeId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Places
|
||||||
|
* @returns GetPlacesResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getPlaces(): CancelablePromise<GetPlacesResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/wms/place',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create Place
|
||||||
|
* @returns CreatePlaceResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static createPlace({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: CreatePlaceRequest,
|
||||||
|
}): CancelablePromise<CreatePlaceResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/wms/place',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Edit Place
|
||||||
|
* @returns EditPlaceResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static editPlace({
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
requestBody: EditPlaceRequest,
|
||||||
|
}): CancelablePromise<EditPlaceResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/wms/place',
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Delete Place
|
||||||
|
* @returns DeletePlaceResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deletePlace({
|
||||||
|
placeId,
|
||||||
|
}: {
|
||||||
|
placeId: number,
|
||||||
|
}): CancelablePromise<DeletePlaceResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/wms/place/{place_id}',
|
||||||
|
path: {
|
||||||
|
'place_id': placeId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generate Place Qr Code Pdf
|
||||||
|
* @returns any Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getPlaceQrCodePdf({
|
||||||
|
placeId,
|
||||||
|
isShort,
|
||||||
|
}: {
|
||||||
|
placeId: number,
|
||||||
|
isShort: boolean,
|
||||||
|
}): CancelablePromise<any> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/wms/place/pdf/{place_id}/{is_short}',
|
||||||
|
path: {
|
||||||
|
'place_id': placeId,
|
||||||
|
'is_short': isShort,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,10 @@ import { CardGeneralFormType } from "../../pages/CardsPage/drawers/CardEditDrawe
|
|||||||
type Props = {
|
type Props = {
|
||||||
project: ProjectSchema;
|
project: ProjectSchema;
|
||||||
form: UseFormReturnType<CardGeneralFormType>;
|
form: UseFormReturnType<CardGeneralFormType>;
|
||||||
|
readOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardAttributeFields = ({ project, form }: Props) => {
|
const CardAttributeFields = ({ project, form, readOnly }: Props) => {
|
||||||
const attributes: AttributeSchema[] = [];
|
const attributes: AttributeSchema[] = [];
|
||||||
|
|
||||||
project.attributes.forEach(attribute => {
|
project.attributes.forEach(attribute => {
|
||||||
@@ -27,6 +28,7 @@ const CardAttributeFields = ({ project, form }: Props) => {
|
|||||||
key={attribute.id}
|
key={attribute.id}
|
||||||
attribute={attribute}
|
attribute={attribute}
|
||||||
form={form}
|
form={form}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import { UseFormReturnType } from "@mantine/form";
|
|||||||
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
|
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
|
||||||
import { CardGeneralFormType } from "../../../pages/CardsPage/drawers/CardEditDrawer/tabs/GeneralTab/GeneralTab.tsx";
|
import { CardGeneralFormType } from "../../../pages/CardsPage/drawers/CardEditDrawer/tabs/GeneralTab/GeneralTab.tsx";
|
||||||
import { IconInfoCircle } from "@tabler/icons-react";
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
import { dateWithoutTimezone } from "../../../shared/lib/date.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
attribute: AttributeSchema;
|
attribute: AttributeSchema;
|
||||||
form: UseFormReturnType<CardGeneralFormType>;
|
form: UseFormReturnType<CardGeneralFormType>;
|
||||||
|
readOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardAttributeField = ({ attribute, form }: Props) => {
|
const CardAttributeField = ({ attribute, form, readOnly }: Props) => {
|
||||||
const type = attribute.type.type;
|
const type = attribute.type.type;
|
||||||
|
|
||||||
const getDateValue = (): Date | null => {
|
const getDateValue = (): Date | null => {
|
||||||
@@ -35,6 +37,7 @@ const CardAttributeField = ({ attribute, form }: Props) => {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
label={label}
|
label={label}
|
||||||
{...form.getInputProps(attribute.name, { type: "checkbox" })}
|
{...form.getInputProps(attribute.name, { type: "checkbox" })}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -43,10 +46,20 @@ const CardAttributeField = ({ attribute, form }: Props) => {
|
|||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
label={label}
|
label={label}
|
||||||
{...form.getInputProps(attribute.name)}
|
{...form.getInputProps(attribute.name)}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!value) {
|
||||||
|
form.getInputProps(attribute.name).onChange(null);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form.getInputProps(attribute.name).onChange(
|
||||||
|
dateWithoutTimezone(value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
value={getDateValue()}
|
value={getDateValue()}
|
||||||
clearable
|
clearable
|
||||||
locale={"ru-RU"}
|
locale={"ru-RU"}
|
||||||
valueFormat="DD.MM.YYYY"
|
valueFormat="DD.MM.YYYY"
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,10 +68,20 @@ const CardAttributeField = ({ attribute, form }: Props) => {
|
|||||||
<DateTimePicker
|
<DateTimePicker
|
||||||
label={label}
|
label={label}
|
||||||
{...form.getInputProps(attribute.name)}
|
{...form.getInputProps(attribute.name)}
|
||||||
|
onChange={(value) => {
|
||||||
|
if (!value) {
|
||||||
|
form.getInputProps(attribute.name).onChange(null);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form.getInputProps(attribute.name).onChange(
|
||||||
|
dateWithoutTimezone(value),
|
||||||
|
);
|
||||||
|
}}
|
||||||
value={getDateValue()}
|
value={getDateValue()}
|
||||||
clearable
|
clearable
|
||||||
locale={"ru-RU"}
|
locale={"ru-RU"}
|
||||||
valueFormat="DD.MM.YYYY HH:mm"
|
valueFormat="DD.MM.YYYY HH:mm"
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -68,6 +91,7 @@ const CardAttributeField = ({ attribute, form }: Props) => {
|
|||||||
label={label}
|
label={label}
|
||||||
{...form.getInputProps(attribute.name)}
|
{...form.getInputProps(attribute.name)}
|
||||||
value={form.getInputProps(attribute.name).value ?? ""}
|
value={form.getInputProps(attribute.name).value ?? ""}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -77,6 +101,7 @@ const CardAttributeField = ({ attribute, form }: Props) => {
|
|||||||
allowDecimal={type === "float"}
|
allowDecimal={type === "float"}
|
||||||
label={label}
|
label={label}
|
||||||
{...form.getInputProps(attribute.name)}
|
{...form.getInputProps(attribute.name)}
|
||||||
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import MessageInput from "./components/MessageInput/MessageInput.tsx";
|
|||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import { Stack } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
|
|
||||||
const Chat = () => {
|
type Props = {
|
||||||
|
height?: number | string;
|
||||||
|
padding?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chat = ({ height = "96vh", padding = 0 }: Props) => {
|
||||||
const {
|
const {
|
||||||
messages,
|
messages,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
@@ -45,7 +50,7 @@ const Chat = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack mb={"xs"} mr={"xs"}>
|
<Stack mt={"xs"} mr={"xs"} ml={padding}>
|
||||||
{dateComponent}
|
{dateComponent}
|
||||||
<Message
|
<Message
|
||||||
key={`${sessionData.id}${index}`}
|
key={`${sessionData.id}${index}`}
|
||||||
@@ -59,14 +64,14 @@ const Chat = () => {
|
|||||||
|
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Stack h={"96vh"} justify={"flex-end"}>
|
<Stack h={height} justify={"flex-end"}>
|
||||||
<MessageInput />
|
<MessageInput inputMargin={padding} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack h={"96vh"}>
|
<Stack h={height}>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
data={messages}
|
data={messages}
|
||||||
followOutput={onFollowOutputHandler}
|
followOutput={onFollowOutputHandler}
|
||||||
@@ -78,7 +83,7 @@ const Chat = () => {
|
|||||||
increaseViewportBy={200}
|
increaseViewportBy={200}
|
||||||
alignToBottom
|
alignToBottom
|
||||||
/>
|
/>
|
||||||
<MessageInput />
|
<MessageInput inputMargin={padding} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.chats-list-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chats-list-item-selected {
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
}
|
||||||
|
@mixin light {
|
||||||
|
background-color: var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/components/Chat/components/ChatsList/ChatsList.tsx
Normal file
50
src/components/Chat/components/ChatsList/ChatsList.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Center, List, Text } from "@mantine/core";
|
||||||
|
import { ChatSchema, ChatsListItemSchema } from "../../../../client";
|
||||||
|
import { IconBriefcase, IconGrid3x3 } from "@tabler/icons-react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import styles from "./ChatsList.module.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
chats: ChatsListItemSchema[];
|
||||||
|
onChatSelect: (chat: ChatSchema) => void;
|
||||||
|
selectedChat: ChatSchema | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatsList = ({ chats, onChatSelect, selectedChat }: Props) => {
|
||||||
|
const clientChat = chats.find(chat => chat.clientId);
|
||||||
|
chats = chats.filter(chat => !chat.clientId);
|
||||||
|
|
||||||
|
const chatItem = (chat: ChatsListItemSchema, isGeneral: boolean) => {
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
p={"sm"}
|
||||||
|
onClick={() => onChatSelect(chat)}
|
||||||
|
key={chat.id}
|
||||||
|
icon={(
|
||||||
|
<Center>
|
||||||
|
{isGeneral ? (
|
||||||
|
<IconGrid3x3 />
|
||||||
|
) : (
|
||||||
|
<IconBriefcase />
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
className={classNames(
|
||||||
|
styles["chats-list-item"],
|
||||||
|
selectedChat?.id === chat.id && styles["chats-list-item-selected"],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Text>{chat.name}</Text>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List center>
|
||||||
|
{clientChat && chatItem(clientChat, true)}
|
||||||
|
{chats.map(chat => chatItem(chat, false))}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatsList;
|
||||||
@@ -6,7 +6,11 @@ import SelectedFile from "../SelectedFile/SelectedFile.tsx";
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
|
|
||||||
|
|
||||||
const MessageInput = () => {
|
type Props = {
|
||||||
|
inputMargin?: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageInput = ({ inputMargin }: Props) => {
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -26,7 +30,7 @@ const MessageInput = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFiles = files.map(file => (
|
const filesList = files.map(file => (
|
||||||
<SelectedFile key={file.name} file={file} />
|
<SelectedFile key={file.name} file={file} />
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -34,8 +38,12 @@ const MessageInput = () => {
|
|||||||
<form ref={formRef} onSubmit={form.onSubmit(values => submitMessage(values))}>
|
<form ref={formRef} onSubmit={form.onSubmit(values => submitMessage(values))}>
|
||||||
<Stack gap={"xs"}>
|
<Stack gap={"xs"}>
|
||||||
<Divider />
|
<Divider />
|
||||||
{getFiles}
|
{filesList.length > 0 && (
|
||||||
<Group wrap={"nowrap"} align={"flex-end"}>
|
<Stack mx={inputMargin}>
|
||||||
|
{filesList}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
<Group wrap={"nowrap"} align={"flex-end"} mx={inputMargin}>
|
||||||
{chat?.tgGroup?.tgInviteLink && (
|
{chat?.tgGroup?.tgInviteLink && (
|
||||||
<ActionIconCopy
|
<ActionIconCopy
|
||||||
onCopiedLabel={"Ссылка на чат скопирована в буфер обмена"}
|
onCopiedLabel={"Ссылка на чат скопирована в буфер обмена"}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import useStatus from "../../Statuses/Status/hooks/useStatus.tsx";
|
|||||||
import { useDndContext } from "../../../../pages/CardsPage/contexts/DndContext.tsx";
|
import { useDndContext } from "../../../../pages/CardsPage/contexts/DndContext.tsx";
|
||||||
import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
|
import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
|
||||||
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -22,6 +24,7 @@ const Board = ({ board }: Props) => {
|
|||||||
onDeleteBoardClick,
|
onDeleteBoardClick,
|
||||||
} = useBoardsContext();
|
} = useBoardsContext();
|
||||||
|
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
const { dragState } = useDndContext();
|
const { dragState } = useDndContext();
|
||||||
|
|
||||||
const { showContextMenu } = useContextMenu();
|
const { showContextMenu } = useContextMenu();
|
||||||
@@ -65,6 +68,7 @@ const Board = ({ board }: Props) => {
|
|||||||
<Draggable
|
<Draggable
|
||||||
draggableId={"board-" + board.id.toString()}
|
draggableId={"board-" + board.id.toString()}
|
||||||
index={board.ordinalNumber}
|
index={board.ordinalNumber}
|
||||||
|
isDragDisabled={authState.isGuest}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import PrefillCardsWithExcelDrawer
|
|||||||
from "../../../../pages/CardsPage/drawers/PrefillCardWithExcelDrawer/PrefillCardsWithExcelDrawer.tsx";
|
from "../../../../pages/CardsPage/drawers/PrefillCardWithExcelDrawer/PrefillCardsWithExcelDrawer.tsx";
|
||||||
import { useDndContext } from "../../../../pages/CardsPage/contexts/DndContext.tsx";
|
import { useDndContext } from "../../../../pages/CardsPage/contexts/DndContext.tsx";
|
||||||
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
const Boards = () => {
|
const Boards = () => {
|
||||||
@@ -16,6 +18,7 @@ const Boards = () => {
|
|||||||
selectedBoard,
|
selectedBoard,
|
||||||
onCreateBoardClick,
|
onCreateBoardClick,
|
||||||
} = useBoardsContext();
|
} = useBoardsContext();
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
@@ -37,14 +40,16 @@ const Boards = () => {
|
|||||||
board={board}
|
board={board}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Center
|
{!authState.isGuest && (
|
||||||
px={"md"}
|
<Center
|
||||||
py={"xs"}
|
px={"md"}
|
||||||
style={{ cursor: "pointer", borderBottom: "solid gray 1px" }}
|
py={"xs"}
|
||||||
onClick={onCreateBoardClick}
|
style={{ cursor: "pointer", borderBottom: "solid gray 1px" }}
|
||||||
>
|
onClick={onCreateBoardClick}
|
||||||
<IconPlus />
|
>
|
||||||
</Center>
|
<IconPlus />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
<Box w={"100%"} style={{ borderBottom: "solid gray 1px" }}></Box>
|
<Box w={"100%"} style={{ borderBottom: "solid gray 1px" }}></Box>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
|||||||
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
||||||
import CardTags from "../CardTags/CardTags.tsx";
|
import CardTags from "../CardTags/CardTags.tsx";
|
||||||
import { ModuleNames } from "../../../../modules/modules.tsx";
|
import { ModuleNames } from "../../../../modules/modules.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cards: CardSummary[];
|
cards: CardSummary[];
|
||||||
@@ -21,6 +23,7 @@ export const CardGroupView: FC<Props> = ({ cards, group }) => {
|
|||||||
const [debouncedName] = useDebouncedValue(name, 200);
|
const [debouncedName] = useDebouncedValue(name, 200);
|
||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const totalPrice = useMemo(() => cards.reduce((acc, card) => acc + card.totalPrice, 0), [cards]);
|
const totalPrice = useMemo(() => cards.reduce((acc, card) => acc + card.totalPrice, 0), [cards]);
|
||||||
const totalProducts = useMemo(() => cards.reduce((acc, card) => acc + card.totalProducts, 0), [cards]);
|
const totalProducts = useMemo(() => cards.reduce((acc, card) => acc + card.totalProducts, 0), [cards]);
|
||||||
@@ -77,7 +80,7 @@ export const CardGroupView: FC<Props> = ({ cards, group }) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
<CardTags groupId={group.id} tags={cards[0].tags}/>
|
{!authState.isGuest && <CardTags groupId={group.id} tags={cards[0].tags}/>}
|
||||||
{isServicesAndProductsIncluded && (
|
{isServicesAndProductsIncluded && (
|
||||||
<Flex
|
<Flex
|
||||||
p={rem(10)}
|
p={rem(10)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { CardService, CardSummary } from "../../../../client";
|
import { CardService, CardSummary } from "../../../../client";
|
||||||
import styles from "./CardSummaryItem.module.css";
|
import styles from "./CardSummaryItem.module.css";
|
||||||
|
|
||||||
@@ -14,6 +14,9 @@ import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
|||||||
import CardTags from "../CardTags/CardTags.tsx";
|
import CardTags from "../CardTags/CardTags.tsx";
|
||||||
import CardAttributesInSummaryItem from "../CardAttributesInSummaryItem/CardAttributesInSummaryItem.tsx";
|
import CardAttributesInSummaryItem from "../CardAttributesInSummaryItem/CardAttributesInSummaryItem.tsx";
|
||||||
import { ModuleNames } from "../../../../modules/modules.tsx";
|
import { ModuleNames } from "../../../../modules/modules.tsx";
|
||||||
|
import isDealPaid from "../../../../pages/CardsPage/utils/isDealPaid.ts";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cardSummary: CardSummary;
|
cardSummary: CardSummary;
|
||||||
@@ -25,6 +28,8 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
|||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
const { setSelectedCard } = useCardPageContext();
|
const { setSelectedCard } = useCardPageContext();
|
||||||
const { onDelete, onComplete, onDeleteFromGroup } = useCardSummaryState();
|
const { onDelete, onComplete, onDeleteFromGroup } = useCardSummaryState();
|
||||||
|
const [isPaid, setIsPaid] = useState<boolean>(false);
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
||||||
const isClientIncluded = isModuleInProject(ModuleNames.CLIENTS, selectedProject);
|
const isClientIncluded = isModuleInProject(ModuleNames.CLIENTS, selectedProject);
|
||||||
@@ -34,22 +39,23 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
|||||||
setSelectedCard(card);
|
setSelectedCard(card);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const isPaid = () => {
|
const canBeRemovedFromGroup = () => {
|
||||||
return cardSummary.billRequest?.paid || cardSummary.group?.billRequest?.paid;
|
return !!(cardSummary.group && (!cardSummary.group.billRequests || cardSummary.group.billRequests?.length === 0));
|
||||||
};
|
|
||||||
const isLockedInsideGroup = () => {
|
|
||||||
return cardSummary.group && !cardSummary.group.billRequest;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsPaid(isDealPaid(cardSummary));
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onContextMenu={showContextMenu([
|
onContextMenu={!authState.isDealsViewer ? showContextMenu([
|
||||||
...isLockedInsideGroup() ? [{
|
...!canBeRemovedFromGroup() ? [] : [{
|
||||||
key: "removeFromGroup",
|
key: "removeFromGroup",
|
||||||
onClick: () => onDeleteFromGroup(cardSummary),
|
onClick: () => onDeleteFromGroup(cardSummary),
|
||||||
title: "Убрать из группы",
|
title: "Убрать из группы",
|
||||||
icon: <IconLayoutGridRemove />,
|
icon: <IconLayoutGridRemove />,
|
||||||
}] : [],
|
}],
|
||||||
{
|
{
|
||||||
key: "complete",
|
key: "complete",
|
||||||
onClick: () => onComplete(cardSummary),
|
onClick: () => onComplete(cardSummary),
|
||||||
@@ -62,7 +68,7 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
|||||||
title: "Удалить",
|
title: "Удалить",
|
||||||
icon: <IconTrash />,
|
icon: <IconTrash />,
|
||||||
},
|
},
|
||||||
])}
|
]) : undefined}
|
||||||
onClick={() => onCardSummaryClick()}
|
onClick={() => onCardSummaryClick()}
|
||||||
className={styles["container"]}
|
className={styles["container"]}
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
@@ -105,7 +111,7 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
<CardAttributesInSummaryItem cardSummary={cardSummary} />
|
<CardAttributesInSummaryItem cardSummary={cardSummary} />
|
||||||
{!cardSummary.group?.id && (
|
{!authState.isGuest && !cardSummary.group?.id && (
|
||||||
<CardTags cardId={cardSummary.id} tags={cardSummary.tags} />
|
<CardTags cardId={cardSummary.id} tags={cardSummary.tags} />
|
||||||
)}
|
)}
|
||||||
<Flex align={"center"} justify={"space-between"}>
|
<Flex align={"center"} justify={"space-between"}>
|
||||||
@@ -147,7 +153,7 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
|||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
</CopyButton>
|
</CopyButton>
|
||||||
{isPaid() && (
|
{isPaid && (
|
||||||
<Tooltip label={"Оплачен"}>
|
<Tooltip label={"Оплачен"}>
|
||||||
<ThemeIcon variant={"transparent"}>
|
<ThemeIcon variant={"transparent"}>
|
||||||
<IconCheck />
|
<IconCheck />
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
|
|||||||
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
||||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||||
import { ModuleNames } from "../../../../modules/modules.tsx";
|
import { ModuleNames } from "../../../../modules/modules.tsx";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: StatusSchema;
|
status: StatusSchema;
|
||||||
@@ -28,6 +30,7 @@ export const CardsDndColumn: FC<Props> = ({
|
|||||||
dragState,
|
dragState,
|
||||||
withCreateButton = false,
|
withCreateButton = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
const isCreatingDealFromFileEnabled = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
const isCreatingDealFromFileEnabled = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
||||||
const isDropDisabled = dragState !== DragState.DRAG_CARD;
|
const isDropDisabled = dragState !== DragState.DRAG_CARD;
|
||||||
@@ -77,7 +80,9 @@ export const CardsDndColumn: FC<Props> = ({
|
|||||||
<Draggable
|
<Draggable
|
||||||
draggableId={card.id.toString()}
|
draggableId={card.id.toString()}
|
||||||
index={card.rank}
|
index={card.rank}
|
||||||
key={card.id}>
|
key={card.id}
|
||||||
|
isDragDisabled={authState.isGuest}
|
||||||
|
>
|
||||||
{(provided, snapshot) => (
|
{(provided, snapshot) => (
|
||||||
<div
|
<div
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
@@ -104,6 +109,7 @@ export const CardsDndColumn: FC<Props> = ({
|
|||||||
draggableId={"group-" + group.id}
|
draggableId={"group-" + group.id}
|
||||||
index={cards[0].rank}
|
index={cards[0].rank}
|
||||||
key={"group-" + group.id}
|
key={"group-" + group.id}
|
||||||
|
isDragDisabled={authState.isGuest}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div
|
||||||
@@ -135,7 +141,7 @@ export const CardsDndColumn: FC<Props> = ({
|
|||||||
styles["items-list-drag-over"],
|
styles["items-list-drag-over"],
|
||||||
)}
|
)}
|
||||||
{...provided.droppableProps}>
|
{...provided.droppableProps}>
|
||||||
{withCreateButton && (
|
{withCreateButton && !authState.isGuest && (
|
||||||
<>
|
<>
|
||||||
<CreateCardButton status={status} />
|
<CreateCardButton status={status} />
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { Flex } from "@mantine/core";
|
import { Flex } from "@mantine/core";
|
||||||
import { DropResult } from "@hello-pangea/dnd";
|
import { DropResult } from "@hello-pangea/dnd";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { CardGroupService, CardService, CardSummary, CardSummaryReorderRequest } from "../../../../../client";
|
import { CardGroupService, CardService, CardSummary, CardSummaryReorderRequest } from "../../../../../client";
|
||||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
import { dateWithoutTimezone } from "../../../../../shared/lib/date.ts";
|
import { dateWithoutTimezone } from "../../../../../shared/lib/date.ts";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
summariesRaw: CardSummary[];
|
summaries: CardSummary[];
|
||||||
refetchSummaries: () => void;
|
refetchSummaries: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCardsDnd = ({
|
const useCardsDnd = ({ summaries, refetchSummaries }: Props) => {
|
||||||
summariesRaw,
|
|
||||||
refetchSummaries,
|
|
||||||
}: Props) => {
|
|
||||||
const [summaries, setSummaries] = useState(summariesRaw);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSummaries(summariesRaw);
|
|
||||||
}, [summariesRaw]);
|
|
||||||
|
|
||||||
const recalculate = async (cardId: number) => {
|
const recalculate = async (cardId: number) => {
|
||||||
return CardService.recalculateCardPrice({
|
return CardService.recalculateCardPrice({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
@@ -103,7 +93,6 @@ const useCardsDnd = ({
|
|||||||
notifications.error({ message: response.message });
|
notifications.error({ message: response.message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await refetchSummaries();
|
|
||||||
await recalculate(sourceId);
|
await recalculate(sourceId);
|
||||||
await refetchSummaries();
|
await refetchSummaries();
|
||||||
});
|
});
|
||||||
@@ -120,7 +109,6 @@ const useCardsDnd = ({
|
|||||||
notifications.error({ message: response.message });
|
notifications.error({ message: response.message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await refetchSummaries();
|
|
||||||
await recalculate(sourceId);
|
await recalculate(sourceId);
|
||||||
await refetchSummaries();
|
await refetchSummaries();
|
||||||
});
|
});
|
||||||
@@ -185,8 +173,7 @@ const useCardsDnd = ({
|
|||||||
if (statusId == summary.status.id) {
|
if (statusId == summary.status.id) {
|
||||||
CardService.reorderCardSummaries({
|
CardService.reorderCardSummaries({
|
||||||
requestBody: request as CardSummaryReorderRequest,
|
requestBody: request as CardSummaryReorderRequest,
|
||||||
}).then(async response => {
|
}).then(async () => {
|
||||||
setSummaries(response.summaries);
|
|
||||||
await refetchSummaries();
|
await refetchSummaries();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -200,14 +187,12 @@ const useCardsDnd = ({
|
|||||||
comment: "",
|
comment: "",
|
||||||
deadline: dateWithoutTimezone(new Date()),
|
deadline: dateWithoutTimezone(new Date()),
|
||||||
},
|
},
|
||||||
}).then(async response => {
|
}).then(async () => {
|
||||||
setSummaries(response.summaries);
|
|
||||||
await refetchSummaries();
|
await refetchSummaries();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
summaries,
|
|
||||||
onCardDragEnd,
|
onCardDragEnd,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/Pref
|
|||||||
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
||||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||||
import { ModuleNames } from "../../../../modules/modules.tsx";
|
import { ModuleNames } from "../../../../modules/modules.tsx";
|
||||||
|
import { useDndContext } from "../../../../pages/CardsPage/contexts/DndContext.tsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: StatusSchema;
|
status: StatusSchema;
|
||||||
@@ -20,6 +21,7 @@ const CreateCardButton = ({ status }: Props) => {
|
|||||||
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
const [isTransitionEnded, setIsTransitionEnded] = useState(true);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { prefillCard, setPrefillCard } = usePrefillCardContext();
|
const { prefillCard, setPrefillCard } = usePrefillCardContext();
|
||||||
|
const { refetchSummaries } = useDndContext();
|
||||||
|
|
||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
const isPrefillingDealEnabled = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
const isPrefillingDealEnabled = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedProject);
|
||||||
@@ -57,7 +59,7 @@ const CreateCardButton = ({ status }: Props) => {
|
|||||||
},
|
},
|
||||||
}).then(async (result) => {
|
}).then(async (result) => {
|
||||||
if (isPrefillingDealEnabled && prefillCard) {
|
if (isPrefillingDealEnabled && prefillCard) {
|
||||||
CardService.prefillCard({
|
await CardService.prefillCard({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
oldCardId: prefillCard.id,
|
oldCardId: prefillCard.id,
|
||||||
newCardId: result.cardId,
|
newCardId: result.cardId,
|
||||||
@@ -67,6 +69,7 @@ const CreateCardButton = ({ status }: Props) => {
|
|||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ["getCardSummaries"],
|
queryKey: ["getCardSummaries"],
|
||||||
});
|
});
|
||||||
|
refetchSummaries();
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
setPrefillCard(undefined);
|
setPrefillCard(undefined);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import isModuleInProject from "../../../../modules/utils/isModuleInProject.ts";
|
|||||||
import { useEqualHeightsContext } from "./contexts/EqualHeightContext.tsx";
|
import { useEqualHeightsContext } from "./contexts/EqualHeightContext.tsx";
|
||||||
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
import { useBoardsContext } from "../../../../contexts/BoardsContext.tsx";
|
||||||
import { ModuleNames } from "../../../../modules/modules.tsx";
|
import { ModuleNames } from "../../../../modules/modules.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -26,6 +28,7 @@ const Status = ({ summaries, status, dragState, index }: Props) => {
|
|||||||
const {
|
const {
|
||||||
selectedBoard,
|
selectedBoard,
|
||||||
} = useBoardsContext();
|
} = useBoardsContext();
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const isDropDisabled = dragState !== DragState.DRAG_STATUS;
|
const isDropDisabled = dragState !== DragState.DRAG_STATUS;
|
||||||
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedBoard?.project);
|
const isServicesAndProductsIncluded = isModuleInProject(ModuleNames.SERVICES_AND_PRODUCTS, selectedBoard?.project);
|
||||||
@@ -107,6 +110,7 @@ const Status = ({ summaries, status, dragState, index }: Props) => {
|
|||||||
<Draggable
|
<Draggable
|
||||||
draggableId={"status-" + status.id.toString()}
|
draggableId={"status-" + status.id.toString()}
|
||||||
index={status.ordinalNumber}
|
index={status.ordinalNumber}
|
||||||
|
isDragDisabled={authState.isGuest}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import getRenderOptions from "./utils/getRenderOptions.tsx";
|
|||||||
type RestProps = {
|
type RestProps = {
|
||||||
clientId: number;
|
clientId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_PRODUCTS = 200;
|
const MAX_PRODUCTS = 200;
|
||||||
type Props = Omit<ObjectSelectProps<ProductSchema>, "data"> & RestProps;
|
type Props = Omit<ObjectSelectProps<ProductSchema>, "data"> & RestProps;
|
||||||
|
|
||||||
const ProductSelect: FC<Props> = (props: Props) => {
|
const ProductSelect: FC<Props> = (props: Props) => {
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const [debounced] = useDebouncedValue(searchValue, 500);
|
const [searchValueServer, setSearchValueServer] = useState("");
|
||||||
|
const [debounced] = useDebouncedValue(searchValueServer, 500);
|
||||||
const { products, isLoading } = useProductsList({
|
const { products, isLoading } = useProductsList({
|
||||||
clientId: props.clientId,
|
clientId: props.clientId,
|
||||||
searchInput: debounced,
|
searchInput: debounced,
|
||||||
@@ -22,21 +25,35 @@ const ProductSelect: FC<Props> = (props: Props) => {
|
|||||||
itemsPerPage: MAX_PRODUCTS,
|
itemsPerPage: MAX_PRODUCTS,
|
||||||
});
|
});
|
||||||
const restProps = omit(props, ["clientId"]);
|
const restProps = omit(props, ["clientId"]);
|
||||||
|
const filterProducts = (searchFilter: string): ProductSchema[] => {
|
||||||
const optionsFilter: OptionsFilter = ({ options }) => options;
|
searchFilter = searchFilter.toLowerCase().trim();
|
||||||
|
if (!searchFilter) return products;
|
||||||
|
const filteredByName = products.filter((v) => v.name.toLowerCase().includes(searchFilter));
|
||||||
|
const filteredByBarcodes = products.filter((v) => v.barcodes?.some(barcode => barcode.toLowerCase().includes(searchFilter)));
|
||||||
|
const filteredByArticle = products.filter((v) => v.article == searchFilter);
|
||||||
|
const uniqueProducts = new Set([...filteredByName, ...filteredByBarcodes, ...filteredByArticle]);
|
||||||
|
return Array.from(uniqueProducts).sort((a, b) => a.id - b.id);
|
||||||
|
};
|
||||||
|
const optionsFilter: OptionsFilter = ({ search }) => {
|
||||||
|
return filterProducts(search).map(product => ({ label: product.name, value: product.id.toString() }));
|
||||||
|
};
|
||||||
const setSearchValueImpl = (value: string) => {
|
const setSearchValueImpl = (value: string) => {
|
||||||
const names = products.map(product => product.name);
|
|
||||||
if (names.includes(value)) return;
|
|
||||||
setSearchValue(value);
|
setSearchValue(value);
|
||||||
|
const filtered = filterProducts(value);
|
||||||
|
if (filtered && filtered.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSearchValueServer(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ObjectSelect
|
<ObjectSelect
|
||||||
rightSection={
|
rightSection={
|
||||||
isLoading || searchValue !== debounced ? (
|
isLoading? (
|
||||||
<Loader size={"sm"} />
|
<Loader size={"sm"} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
searchValue={searchValue}
|
||||||
onSearchChange={setSearchValueImpl}
|
onSearchChange={setSearchValueImpl}
|
||||||
renderOption={getRenderOptions(products)}
|
renderOption={getRenderOptions(products)}
|
||||||
searchable
|
searchable
|
||||||
|
|||||||
@@ -1,44 +1,23 @@
|
|||||||
import { FC, ReactNode } from "react";
|
import { FC } from "react";
|
||||||
import { Select } from "@mantine/core";
|
|
||||||
import { ClientSchema } from "../../../client";
|
import { ClientSchema } from "../../../client";
|
||||||
import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx";
|
import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx";
|
||||||
|
import ObjectSelect, { ObjectSelectProps } from "../../ObjectSelect/ObjectSelect.tsx";
|
||||||
|
|
||||||
|
type Props = Omit<
|
||||||
|
ObjectSelectProps<ClientSchema>,
|
||||||
|
"data" | "getLabelFn" | "getValueFn"
|
||||||
|
>;
|
||||||
|
|
||||||
|
const ClientSelect: FC<Props> = (props: Props) => {
|
||||||
|
const { clients } = useClientsList({ all: true });
|
||||||
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
value?: ClientSchema;
|
|
||||||
onChange: (client: ClientSchema) => void;
|
|
||||||
withLabel?: boolean;
|
|
||||||
error?: string;
|
|
||||||
inputContainer?: (children: ReactNode) => ReactNode;
|
|
||||||
disabled?: boolean;
|
|
||||||
};
|
|
||||||
const ClientSelect: FC<Props> = ({ value, onChange, error, inputContainer, withLabel = false, disabled = false }) => {
|
|
||||||
const { clients } = useClientsList();
|
|
||||||
const options = clients.map(client => ({
|
|
||||||
label: client.name,
|
|
||||||
value: client.id.toString(),
|
|
||||||
}));
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<ObjectSelect
|
||||||
|
{...props}
|
||||||
searchable
|
searchable
|
||||||
placeholder={"Выберите клиента"}
|
placeholder={"Выберите клиента"}
|
||||||
value={
|
data={clients.filter(cl => !cl.isDeleted)}
|
||||||
value &&
|
|
||||||
options.find(client => client.value == value.id.toString())
|
|
||||||
?.value
|
|
||||||
}
|
|
||||||
onChange={event => {
|
|
||||||
if (!event) return;
|
|
||||||
const client = clients.find(
|
|
||||||
client => client.id == parseInt(event)
|
|
||||||
);
|
|
||||||
if (!client) return;
|
|
||||||
onChange(client);
|
|
||||||
}}
|
|
||||||
data={options}
|
|
||||||
label={withLabel && "Клиент"}
|
|
||||||
error={error}
|
|
||||||
inputContainer={inputContainer}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import useClientsList from "../../../pages/ClientsPage/hooks/useClientsList.tsx"
|
|||||||
type Props = Omit<ObjectSelectProps<ClientSchema>, "data">;
|
type Props = Omit<ObjectSelectProps<ClientSchema>, "data">;
|
||||||
|
|
||||||
const ClientSelectNew: FC<Props> = props => {
|
const ClientSelectNew: FC<Props> = props => {
|
||||||
const { clients } = useClientsList();
|
const { clients } = useClientsList({ all: false });
|
||||||
return (
|
return (
|
||||||
<ObjectSelect
|
<ObjectSelect
|
||||||
searchable
|
searchable
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { jwtDecode, JwtPayload as JwtPayloadBase } from "jwt-decode";
|
import { jwtDecode, JwtPayload as JwtPayloadBase } from "jwt-decode";
|
||||||
|
import { OpenAPI } from "../client";
|
||||||
|
|
||||||
interface AuthState {
|
interface AuthState {
|
||||||
isAuthorized: boolean;
|
isAuthorized: boolean;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
isDealEditor: boolean;
|
||||||
|
isDealsViewer: boolean;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
role: string;
|
role: string;
|
||||||
}
|
}
|
||||||
@@ -16,6 +19,8 @@ const initialState = (): AuthState => {
|
|||||||
return {
|
return {
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
isAuthorized: false,
|
isAuthorized: false,
|
||||||
|
isDealEditor: false,
|
||||||
|
isDealsViewer: false,
|
||||||
isGuest: false,
|
isGuest: false,
|
||||||
role: "user",
|
role: "user",
|
||||||
};
|
};
|
||||||
@@ -34,10 +39,13 @@ const authSlice = createSlice({
|
|||||||
const { sub, role } = jwtDecode<JwtPayload>(
|
const { sub, role } = jwtDecode<JwtPayload>(
|
||||||
action.payload.accessToken,
|
action.payload.accessToken,
|
||||||
);
|
);
|
||||||
|
OpenAPI.TOKEN = action.payload.accessToken;
|
||||||
state.accessToken = action.payload.accessToken;
|
state.accessToken = action.payload.accessToken;
|
||||||
state.isAuthorized = true;
|
state.isAuthorized = true;
|
||||||
state.role = role;
|
state.role = role;
|
||||||
if (sub === "guest") state.isGuest = true;
|
state.isDealEditor = sub === "deal_editor";
|
||||||
|
state.isDealsViewer = sub === "deals_viewer";
|
||||||
|
state.isGuest = state.isDealEditor || state.isDealsViewer;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { type FullProjectSchema, ProjectService } from "../client";
|
import { type FullProjectSchema, ProjectService } from "../client";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
const useProjects = () => {
|
const useProjects = () => {
|
||||||
const [projects, setProjects] = useState<FullProjectSchema[]>([]);
|
const [projects, setProjects] = useState<FullProjectSchema[]>([]);
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const refetchProjects = () => {
|
const refetchProjects = () => {
|
||||||
ProjectService.getProjects()
|
ProjectService.getProjects()
|
||||||
@@ -14,8 +17,10 @@ const useProjects = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetchProjects();
|
if (authState.isAuthorized) {
|
||||||
}, []);
|
refetchProjects();
|
||||||
|
}
|
||||||
|
}, [authState.isAuthorized]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ import AttributeModal from "../pages/AdminPage/tabs/Attributes/modals/AttributeM
|
|||||||
import CreateProjectModal
|
import CreateProjectModal
|
||||||
from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/General/modals/CreateProjectModal.tsx";
|
from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/General/modals/CreateProjectModal.tsx";
|
||||||
import CardTagModal from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/Tags/modals/CardTagModal.tsx";
|
import CardTagModal from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/Tags/modals/CardTagModal.tsx";
|
||||||
|
import PlaceTypeModal from "../pages/AdminPage/tabs/WarehouseManagement/placeType/modals/PlaceTypeModal.tsx";
|
||||||
|
import PlaceModal from "../pages/AdminPage/tabs/WarehouseManagement/place/modals/PlaceModal.tsx";
|
||||||
|
import SelectPlaceQrType from "../pages/AdminPage/tabs/WarehouseManagement/place/modals/SelectPlaceQrType.tsx";
|
||||||
|
|
||||||
export const modals = {
|
export const modals = {
|
||||||
enterDeadline: EnterDeadlineModal,
|
enterDeadline: EnterDeadlineModal,
|
||||||
@@ -82,4 +85,7 @@ export const modals = {
|
|||||||
attributeModal: AttributeModal,
|
attributeModal: AttributeModal,
|
||||||
createProjectModal: CreateProjectModal,
|
createProjectModal: CreateProjectModal,
|
||||||
cardTagModal: CardTagModal,
|
cardTagModal: CardTagModal,
|
||||||
|
placeTypeModal: PlaceTypeModal,
|
||||||
|
placeModal: PlaceModal,
|
||||||
|
selectPlaceQrType: SelectPlaceQrType,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ const ClientTab = () => {
|
|||||||
initialValues,
|
initialValues,
|
||||||
validate: {
|
validate: {
|
||||||
details: {
|
details: {
|
||||||
phoneNumber: value =>
|
phoneNumber: value => {
|
||||||
!phone(value || "", {
|
if (!value || value === "+7 ") return false;
|
||||||
|
return !phone(value || "", {
|
||||||
country: "",
|
country: "",
|
||||||
strictDetection: false,
|
strictDetection: false,
|
||||||
validateMobilePrefix: false,
|
validateMobilePrefix: false,
|
||||||
}).isValid && "Неверно указан номер телефона",
|
}).isValid && "Неверно указан номер телефона";
|
||||||
|
},
|
||||||
inn: (inn: string | undefined | null) => inn && !isValidInn(inn) ? "Некорректный ИНН" : null,
|
inn: (inn: string | undefined | null) => inn && !isValidInn(inn) ? "Некорректный ИНН" : null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -40,9 +42,10 @@ const ClientTab = () => {
|
|||||||
form.setValues(data);
|
form.setValues(data);
|
||||||
}, [card]);
|
}, [card]);
|
||||||
|
|
||||||
const isEditorDisabled = () => client?.id !== card?.client?.id;
|
const isEditorDisabled = () => client?.id !== card?.client?.id || client?.isDeleted === true;
|
||||||
|
|
||||||
const handleSubmitClientInfo = (values: ClientSchema) => {
|
const handleSubmitClientInfo = (values: ClientSchema) => {
|
||||||
|
if (values.details?.phoneNumber === "+7 ") values.details!.phoneNumber = "";
|
||||||
ClientService.updateClient({
|
ClientService.updateClient({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
data: values,
|
data: values,
|
||||||
@@ -98,6 +101,7 @@ const ClientTab = () => {
|
|||||||
mask="+7 000 000-00-00"
|
mask="+7 000 000-00-00"
|
||||||
placeholder={"Введите номер телефона"}
|
placeholder={"Введите номер телефона"}
|
||||||
{...form.getInputProps("details.phoneNumber")}
|
{...form.getInputProps("details.phoneNumber")}
|
||||||
|
disabled={isEditorDisabled()}
|
||||||
/>
|
/>
|
||||||
</Input.Wrapper>
|
</Input.Wrapper>
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -145,7 +149,6 @@ const ClientTab = () => {
|
|||||||
<ClientSelect
|
<ClientSelect
|
||||||
value={client}
|
value={client}
|
||||||
onChange={setClient}
|
onChange={setClient}
|
||||||
withLabel
|
|
||||||
disabled={!isEqual(initialValues, form.values) || !!card?.chat}
|
disabled={!isEqual(initialValues, form.values) || !!card?.chat}
|
||||||
/>
|
/>
|
||||||
{!card?.chat && (
|
{!card?.chat && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import styles from "./ProductAndServiceTab.module.css";
|
import styles from "./ProductAndServiceTab.module.css";
|
||||||
import ProductView from "./components/ProductView/ProductView.tsx";
|
import ProductView from "./components/ProductView/ProductView.tsx";
|
||||||
import { Button, Checkbox, Divider, Flex, Group, rem, ScrollArea, Stack, Text, Title } from "@mantine/core";
|
import { Button, Checkbox, Divider, Flex, Group, rem, ScrollArea, Stack, Text, Title } from "@mantine/core";
|
||||||
@@ -20,10 +20,22 @@ import GeneralDataForm from "./components/GeneralDataForm/GeneralDataForm.tsx";
|
|||||||
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton/PrintDealBarcodesButton.tsx";
|
import PrintDealBarcodesButton from "./components/PrintDealBarcodesButton/PrintDealBarcodesButton.tsx";
|
||||||
import PaymentLinkButton from "./components/PaymentLinkButton/PaymentLinkButton.tsx";
|
import PaymentLinkButton from "./components/PaymentLinkButton/PaymentLinkButton.tsx";
|
||||||
import isValidInn from "../../../../pages/ClientsPage/utils/isValidInn.ts";
|
import isValidInn from "../../../../pages/ClientsPage/utils/isValidInn.ts";
|
||||||
|
import isDealPaid, { isDealLocked } from "../../../../pages/CardsPage/utils/isDealPaid.ts";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../redux/store.ts";
|
||||||
|
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||||
|
|
||||||
const ProductAndServiceTab: FC = () => {
|
const ProductAndServiceTab: FC = () => {
|
||||||
const { cardState, cardServicesState, cardProductsState } = useCardProductAndServiceTabState();
|
const { cardState, cardServicesState, cardProductsState } = useCardProductAndServiceTabState();
|
||||||
const isLocked = Boolean(cardState.card?.billRequest || cardState.card?.group?.billRequest);
|
const { selectedCard: card } = useCardPageContext();
|
||||||
|
const isLocked = isDealLocked(cardState.card);
|
||||||
|
const [paid, setPaid] = useState<boolean>(false);
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPaid(isDealPaid(cardState.card));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onAddProductClick = () => {
|
const onAddProductClick = () => {
|
||||||
if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return;
|
if (!cardProductsState.onCreate || !cardState.card || !cardState.card.clientId) return;
|
||||||
const productIds = cardState.card.products.map(
|
const productIds = cardState.card.products.map(
|
||||||
@@ -121,7 +133,9 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
ProductService.createProduct({
|
ProductService.createProduct({
|
||||||
requestBody: newProduct,
|
requestBody: newProduct,
|
||||||
}).then(({ ok, message }) => {
|
}).then(({ ok, message }) => {
|
||||||
notifications.guess(ok, { message: message });
|
notifications.guess(ok, { message });
|
||||||
|
}).catch(err => {
|
||||||
|
notifications.error({ message: err.toString() });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const onCreateProductClick = () => {
|
const onCreateProductClick = () => {
|
||||||
@@ -222,7 +236,7 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles["container"],
|
styles["container"],
|
||||||
cardState.card?.billRequest && styles["container-disabled"],
|
cardState.card?.billRequests?.length && styles["container-disabled"],
|
||||||
)}>
|
)}>
|
||||||
<div className={styles["products-list"]}>
|
<div className={styles["products-list"]}>
|
||||||
<ScrollArea offsetScrollbars>
|
<ScrollArea offsetScrollbars>
|
||||||
@@ -249,18 +263,18 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
mr={"xs"}
|
mr={"xs"}
|
||||||
>
|
>
|
||||||
<Group wrap={"nowrap"}>
|
<Group wrap={"nowrap"}>
|
||||||
<PrintDealBarcodesButton card={cardState.card} />
|
{!isDealsViewer && <PrintDealBarcodesButton card={cardState.card} />}
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={"Оплачен"}
|
label={"Оплачен"}
|
||||||
checked={cardState.card.billRequest?.paid || cardState.card.group?.billRequest?.paid || false}
|
checked={paid}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<PaymentLinkButton card={cardState.card} />
|
{!isDealsViewer && <PaymentLinkButton card={cardState.card} />}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
<Stack className={styles["card-container-wrapper"]} mr={"xs"}>
|
<Stack className={styles["card-container-wrapper"]} mr={"xs"}>
|
||||||
<GeneralDataForm />
|
{card && <GeneralDataForm />}
|
||||||
</Stack>
|
</Stack>
|
||||||
<ScrollArea offsetScrollbars>
|
<ScrollArea offsetScrollbars>
|
||||||
<Flex
|
<Flex
|
||||||
@@ -271,41 +285,45 @@ const ProductAndServiceTab: FC = () => {
|
|||||||
{...cardServicesState}
|
{...cardServicesState}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Divider my={rem(15)} />
|
{!isDealsViewer && (
|
||||||
<div className={styles["card-container-buttons"]}>
|
<>
|
||||||
<Button
|
<Divider my={rem(15)} />
|
||||||
disabled={isLocked}
|
<div className={styles["card-container-buttons"]}>
|
||||||
variant={"default"}
|
<Button
|
||||||
fullWidth
|
disabled={isLocked}
|
||||||
onClick={onCreateProductClick}>
|
variant={"default"}
|
||||||
Создать товар
|
fullWidth
|
||||||
</Button>
|
onClick={onCreateProductClick}>
|
||||||
<Button
|
Создать товар
|
||||||
disabled={isLocked}
|
</Button>
|
||||||
onClick={onAddProductClick}
|
<Button
|
||||||
variant={"default"}
|
disabled={isLocked}
|
||||||
fullWidth>
|
onClick={onAddProductClick}
|
||||||
Добавить товар
|
variant={"default"}
|
||||||
</Button>
|
fullWidth>
|
||||||
</div>
|
Добавить товар
|
||||||
<Divider my={rem(15)} />
|
</Button>
|
||||||
<div className={styles["card-container-buttons"]}>
|
</div>
|
||||||
{isLocked ? (
|
<Divider my={rem(15)} />
|
||||||
<Button
|
<div className={styles["card-container-buttons"]}>
|
||||||
onClick={onCancelBillClick}
|
{isLocked ? (
|
||||||
color={"red"}>
|
<Button
|
||||||
Отозвать счет
|
onClick={onCancelBillClick}
|
||||||
</Button>
|
color={"red"}>
|
||||||
) : (
|
Отозвать счет
|
||||||
<Button
|
</Button>
|
||||||
disabled={isLocked}
|
) : (
|
||||||
onClick={onCreateBillClick}
|
<Button
|
||||||
variant={"default"}
|
disabled={isLocked}
|
||||||
fullWidth>
|
onClick={onCreateBillClick}
|
||||||
Выставить счет
|
variant={"default"}
|
||||||
</Button>
|
fullWidth>
|
||||||
)}
|
Выставить счет
|
||||||
</div>
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
<Flex
|
||||||
direction={"column"}
|
direction={"column"}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { RootState } from "../../../../../../redux/store.ts";
|
|||||||
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
||||||
import LockCheckbox from "../../../../../../components/LockCheckbox/LockCheckbox.tsx";
|
import LockCheckbox from "../../../../../../components/LockCheckbox/LockCheckbox.tsx";
|
||||||
import { useDebouncedCallback } from "@mantine/hooks";
|
import { useDebouncedCallback } from "@mantine/hooks";
|
||||||
|
import { isDealLocked } from "../../../../../../pages/CardsPage/utils/isDealPaid.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
onKitAdd?: (kit: GetServiceKitSchema) => void;
|
onKitAdd?: (kit: GetServiceKitSchema) => void;
|
||||||
@@ -31,7 +32,7 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
const authState = useSelector((state: RootState) => state.auth);
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const { cardState } = useCardProductAndServiceTabState();
|
const { cardState } = useCardProductAndServiceTabState();
|
||||||
const isLocked = Boolean(cardState.card?.billRequest);
|
const isLocked = isDealLocked(cardState.card);
|
||||||
|
|
||||||
const [currentService, setCurrentService] = useState<
|
const [currentService, setCurrentService] = useState<
|
||||||
CardServiceSchema | undefined
|
CardServiceSchema | undefined
|
||||||
@@ -137,15 +138,17 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
w={"100%"}
|
w={"100%"}
|
||||||
gap={rem(10)}
|
gap={rem(10)}
|
||||||
align={"center"}>
|
align={"center"}>
|
||||||
<Tooltip
|
{!authState.isDealsViewer && (
|
||||||
onClick={() => onDeleteClick(service)}
|
<Tooltip
|
||||||
label="Удалить услугу">
|
onClick={() => onDeleteClick(service)}
|
||||||
<ActionIcon
|
label="Удалить услугу">
|
||||||
disabled={isLocked}
|
<ActionIcon
|
||||||
variant={"default"}>
|
disabled={isLocked}
|
||||||
<IconTrash />
|
variant={"default"}>
|
||||||
</ActionIcon>
|
<IconTrash />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
{!authState.isGuest && (
|
{!authState.isGuest && (
|
||||||
<Tooltip label="Сотрудники">
|
<Tooltip label="Сотрудники">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
@@ -166,9 +169,8 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
isNumber(event) &&
|
isNumber(event) &&
|
||||||
onQuantityChange(service, event)
|
onQuantityChange(service, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
value={service.quantity}
|
value={service.quantity}
|
||||||
|
readOnly={authState.isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
flex={1}
|
flex={1}
|
||||||
@@ -179,6 +181,7 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
suffix={"₽"}
|
suffix={"₽"}
|
||||||
value={service.price}
|
value={service.price}
|
||||||
disabled={authState.isGuest || isLocked || service.isFixedPrice}
|
disabled={authState.isGuest || isLocked || service.isFixedPrice}
|
||||||
|
readOnly={authState.isDealsViewer}
|
||||||
rightSectionProps={{
|
rightSectionProps={{
|
||||||
style: {
|
style: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -187,12 +190,14 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rightSection={
|
rightSection={
|
||||||
<LockCheckbox
|
!authState.isDealsViewer && (
|
||||||
label={"Зафиксировать цену"}
|
<LockCheckbox
|
||||||
variant={"default"}
|
label={"Зафиксировать цену"}
|
||||||
value={service.isFixedPrice}
|
variant={"default"}
|
||||||
onChange={value => onLockChange(service, value)}
|
value={service.isFixedPrice}
|
||||||
/>
|
onChange={value => onLockChange(service, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -211,25 +216,27 @@ const CardServicesTable: FC<Props> = ({
|
|||||||
₽
|
₽
|
||||||
</Title>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex
|
{!authState.isDealsViewer && (
|
||||||
direction={"column"}
|
<Flex
|
||||||
gap={rem(10)}
|
direction={"column"}
|
||||||
mt={"auto"}>
|
gap={rem(10)}
|
||||||
<Button
|
mt={"auto"}>
|
||||||
disabled={isLocked}
|
<Button
|
||||||
onClick={onCreateClick}
|
disabled={isLocked}
|
||||||
fullWidth
|
onClick={onCreateClick}
|
||||||
variant={"default"}>
|
fullWidth
|
||||||
Добавить услугу
|
variant={"default"}>
|
||||||
</Button>
|
Добавить услугу
|
||||||
<Button
|
</Button>
|
||||||
disabled={isLocked}
|
<Button
|
||||||
onClick={onAddKitClick}
|
disabled={isLocked}
|
||||||
fullWidth
|
onClick={onAddKitClick}
|
||||||
variant={"default"}>
|
fullWidth
|
||||||
Добавить набор услуг
|
variant={"default"}>
|
||||||
</Button>
|
Добавить набор услуг
|
||||||
</Flex>
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Modal
|
<Modal
|
||||||
title={"Добавление сотрудника к услуге"}
|
title={"Добавление сотрудника к услуге"}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Button, Checkbox, Stack } from "@mantine/core";
|
|||||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { isEqual } from "lodash";
|
import { isEqual } from "lodash";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
type GeneralDataFormType = {
|
type GeneralDataFormType = {
|
||||||
@@ -16,7 +18,8 @@ type GeneralDataFormType = {
|
|||||||
|
|
||||||
const GeneralDataForm = () => {
|
const GeneralDataForm = () => {
|
||||||
const { selectedCard: card, refetchCard } = useCardPageContext();
|
const { selectedCard: card, refetchCard } = useCardPageContext();
|
||||||
if (!card) return;
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
if (!card) return <></>;
|
||||||
|
|
||||||
const [initialValues, setInitialValues] = useState<GeneralDataFormType>(card);
|
const [initialValues, setInitialValues] = useState<GeneralDataFormType>(card);
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ const GeneralDataForm = () => {
|
|||||||
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<ShippingWarehouseAutocomplete
|
<ShippingWarehouseAutocomplete
|
||||||
placeholder={"Введите склад отгрузки"}
|
placeholder={isDealsViewer ? "" : "Введите склад отгрузки"}
|
||||||
label={"Склад отгрузки"}
|
label={"Склад отгрузки"}
|
||||||
value={
|
value={
|
||||||
isShippingWarehouse(
|
isShippingWarehouse(
|
||||||
@@ -87,18 +90,23 @@ const GeneralDataForm = () => {
|
|||||||
"shippingWarehouse",
|
"shippingWarehouse",
|
||||||
).onChange(event);
|
).onChange(event);
|
||||||
}}
|
}}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
{!isDealsViewer && (
|
||||||
label={"Учет выручки в статистике"}
|
<>
|
||||||
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
|
<Checkbox
|
||||||
/>
|
label={"Учет выручки в статистике"}
|
||||||
<Button
|
{...form.getInputProps("isServicesProfitAccounted", { type: "checkbox" })}
|
||||||
type={"submit"}
|
/>
|
||||||
variant={"default"}
|
<Button
|
||||||
disabled={isEqual(initialValues, form.values)}
|
type={"submit"}
|
||||||
>
|
variant={"default"}
|
||||||
Сохранить
|
disabled={isEqual(initialValues, form.values)}
|
||||||
</Button>
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,19 +3,40 @@ import ButtonCopy from "../../../../../../components/ButtonCopy/ButtonCopy.tsx";
|
|||||||
import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyControlled/ButtonCopyControlled.tsx";
|
||||||
import { getCurrentDateTimeForFilename } from "../../../../../../shared/lib/date.ts";
|
import { getCurrentDateTimeForFilename } from "../../../../../../shared/lib/date.ts";
|
||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
|
import { Button, Popover, Stack } from "@mantine/core";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
card: CardSchema;
|
card: CardSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaymentLinkButton = ({ card }: Props) => {
|
const PaymentLinkButton = ({ card }: Props) => {
|
||||||
const billRequestPdfUrl = card?.billRequest?.pdfUrl || card?.group?.billRequest?.pdfUrl;
|
if ((!card.billRequests || card.billRequests.length === 0) && (!card?.group?.billRequests || card?.group?.billRequests.length === 0)) {
|
||||||
|
return (
|
||||||
|
<ButtonCopyControlled
|
||||||
|
onCopyClick={() => {
|
||||||
|
const date =
|
||||||
|
getCurrentDateTimeForFilename();
|
||||||
|
FileSaver.saveAs(
|
||||||
|
`${import.meta.env.VITE_API_URL}/card/billing-document/${card.id}`,
|
||||||
|
`bill_${card.id}_${date}.pdf`,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
copied={false}
|
||||||
|
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||||
|
>
|
||||||
|
Ссылка на оплату (PDF)
|
||||||
|
</ButtonCopyControlled>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (billRequestPdfUrl) {
|
const requests = (card?.group ? card?.group?.billRequests : card.billRequests) ?? [];
|
||||||
|
const urls = requests.map(request => request.pdfUrl).filter(url => url !== null);
|
||||||
|
|
||||||
|
if (urls.length === 1) {
|
||||||
return (
|
return (
|
||||||
<ButtonCopy
|
<ButtonCopy
|
||||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||||
value={billRequestPdfUrl}
|
value={urls[0]}
|
||||||
>
|
>
|
||||||
Ссылка на оплату
|
Ссылка на оплату
|
||||||
</ButtonCopy>
|
</ButtonCopy>
|
||||||
@@ -23,21 +44,25 @@ const PaymentLinkButton = ({ card }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonCopyControlled
|
<Popover width={380} position="bottom" withArrow shadow="md">
|
||||||
onCopyClick={() => {
|
<Popover.Target>
|
||||||
const date =
|
<Button variant={"default"}>Ссылки на оплату</Button>
|
||||||
getCurrentDateTimeForFilename();
|
</Popover.Target>
|
||||||
FileSaver.saveAs(
|
<Popover.Dropdown>
|
||||||
`${import.meta.env.VITE_API_URL}/card/billing-document/${card.id}`,
|
<Stack gap={"md"}>
|
||||||
`bill_${card.id}_${date}.pdf`,
|
{urls.map((url, i) => (
|
||||||
);
|
<ButtonCopy
|
||||||
}}
|
key={i}
|
||||||
copied={false}
|
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
||||||
onCopiedLabel={"Ссылка скопирована в буфер обмена"}
|
value={url}
|
||||||
>
|
>
|
||||||
Ссылка на оплату (PDF)
|
{`Ссылка на оплату (часть ${String(i + 1)})`}
|
||||||
</ButtonCopyControlled>
|
</ButtonCopy>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default PaymentLinkButton;
|
export default PaymentLinkButton;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import SimpleUsersTable from "../../../../../../pages/CardsPage/components/Simpl
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../../../../redux/store.ts";
|
import { RootState } from "../../../../../../redux/store.ts";
|
||||||
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
||||||
|
import { isDealLocked } from "../../../../../../pages/CardsPage/utils/isDealPaid.ts";
|
||||||
|
|
||||||
type RestProps = {
|
type RestProps = {
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@@ -20,16 +21,16 @@ type RestProps = {
|
|||||||
|
|
||||||
type Props = CRUDTableProps<CardProductServiceSchema> & RestProps;
|
type Props = CRUDTableProps<CardProductServiceSchema> & RestProps;
|
||||||
const ProductServicesTable: FC<Props> = ({
|
const ProductServicesTable: FC<Props> = ({
|
||||||
items,
|
items,
|
||||||
quantity,
|
quantity,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onChange,
|
onChange,
|
||||||
onCopyServices,
|
onCopyServices,
|
||||||
onKitAdd,
|
onKitAdd,
|
||||||
}) => {
|
}) => {
|
||||||
const { cardState } = useCardProductAndServiceTabState();
|
const { cardState } = useCardProductAndServiceTabState();
|
||||||
const isLocked = Boolean(cardState.card?.billRequest);
|
const isLocked = isDealLocked(cardState.card);
|
||||||
const authState = useSelector((state: RootState) => state.auth);
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const columns = useProductServicesTableColumns({ data: items, quantity });
|
const columns = useProductServicesTableColumns({ data: items, quantity });
|
||||||
@@ -78,7 +79,7 @@ const ProductServicesTable: FC<Props> = ({
|
|||||||
const getCurrentEmployees = (): UserSchema[] => {
|
const getCurrentEmployees = (): UserSchema[] => {
|
||||||
if (!currentService) return [];
|
if (!currentService) return [];
|
||||||
const item = items.find(
|
const item = items.find(
|
||||||
i => i.service.id === currentService.service.id
|
i => i.service.id === currentService.service.id,
|
||||||
);
|
);
|
||||||
if (!item) return [];
|
if (!item) return [];
|
||||||
return item.employees;
|
return item.employees;
|
||||||
@@ -102,8 +103,8 @@ const ProductServicesTable: FC<Props> = ({
|
|||||||
{
|
{
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableRowActions: true,
|
enableRowActions: !authState.isDealsViewer,
|
||||||
enableBottomToolbar: true,
|
enableBottomToolbar: !authState.isDealsViewer,
|
||||||
renderBottomToolbar: (
|
renderBottomToolbar: (
|
||||||
<Flex
|
<Flex
|
||||||
justify={"flex-end"}
|
justify={"flex-end"}
|
||||||
@@ -158,7 +159,7 @@ const ProductServicesTable: FC<Props> = ({
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onEmployeeClick(
|
onEmployeeClick(
|
||||||
row.original
|
row.original,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
variant={"default"}>
|
variant={"default"}>
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import { modals } from "@mantine/modals";
|
|||||||
import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts";
|
import { ServiceType } from "../../../../../../shared/enums/ServiceType.ts";
|
||||||
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
import useCardProductAndServiceTabState from "../../hooks/useProductAndServiceTabState.tsx";
|
||||||
import { useDebouncedCallback } from "@mantine/hooks";
|
import { useDebouncedCallback } from "@mantine/hooks";
|
||||||
|
import { isDealLocked } from "../../../../../../pages/CardsPage/utils/isDealPaid.ts";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: CardProductSchema;
|
product: CardProductSchema;
|
||||||
@@ -47,12 +50,14 @@ const ProductView: FC<Props> = ({
|
|||||||
if (!onChange) return;
|
if (!onChange) return;
|
||||||
onChange(item);
|
onChange(item);
|
||||||
}, 200);
|
}, 200);
|
||||||
const isLocked = Boolean(cardState.card?.billRequest);
|
const isLocked = isDealLocked(cardState.card);
|
||||||
const onDeleteClick = () => {
|
const onDeleteClick = () => {
|
||||||
if (!onDelete) return;
|
if (!onDelete) return;
|
||||||
onDelete(product);
|
onDelete(product);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const onServiceDelete = (item: CardProductServiceSchema) => {
|
const onServiceDelete = (item: CardProductServiceSchema) => {
|
||||||
if (!onChange) return;
|
if (!onChange) return;
|
||||||
onChange({
|
onChange({
|
||||||
@@ -161,25 +166,29 @@ const ProductView: FC<Props> = ({
|
|||||||
isNumber(event) && onQuantityChange(event)
|
isNumber(event) && onQuantityChange(event)
|
||||||
}
|
}
|
||||||
placeholder={"Введите количество товара"}
|
placeholder={"Введите количество товара"}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<Textarea
|
{!(isDealsViewer && product.comment?.length === 0) && (
|
||||||
mih={rem(140)}
|
<Textarea
|
||||||
styles={{
|
mih={rem(140)}
|
||||||
wrapper: { height: "90%" },
|
styles={{
|
||||||
input: { height: "90%" },
|
wrapper: { height: "90%" },
|
||||||
}}
|
input: { height: "90%" },
|
||||||
my={rem(10)}
|
}}
|
||||||
disabled={isLocked}
|
my={rem(10)}
|
||||||
defaultValue={product.comment}
|
disabled={isLocked}
|
||||||
onChange={event => {
|
defaultValue={product.comment}
|
||||||
if (!onChange) return;
|
onChange={event => {
|
||||||
debouncedOnChange({
|
if (!onChange) return;
|
||||||
...product,
|
debouncedOnChange({
|
||||||
comment: event.currentTarget.value,
|
...product,
|
||||||
});
|
comment: event.currentTarget.value,
|
||||||
}}
|
});
|
||||||
placeholder={"Введите комментарий для товара"}
|
}}
|
||||||
/>
|
placeholder={isDealsViewer ? "" : "Введите комментарий для товара"}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["services-container"]}>
|
<div className={styles["services-container"]}>
|
||||||
@@ -194,34 +203,36 @@ const ProductView: FC<Props> = ({
|
|||||||
onDelete={onServiceDelete}
|
onDelete={onServiceDelete}
|
||||||
onChange={onServiceChange}
|
onChange={onServiceChange}
|
||||||
/>
|
/>
|
||||||
<Flex
|
{!isDealsViewer && (
|
||||||
mt={"auto"}
|
<Flex
|
||||||
ml={"auto"}
|
mt={"auto"}
|
||||||
gap={rem(10)}>
|
ml={"auto"}
|
||||||
<Tooltip
|
gap={rem(10)}>
|
||||||
onClick={onPrintBarcodeClick}
|
<Tooltip
|
||||||
label="Печать штрихкода">
|
onClick={onPrintBarcodeClick}
|
||||||
<ActionIcon variant={"default"}>
|
label="Печать штрихкода">
|
||||||
<IconBarcode />
|
<ActionIcon variant={"default"}>
|
||||||
</ActionIcon>
|
<IconBarcode />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
<Tooltip
|
</Tooltip>
|
||||||
onClick={onProductEditClick}
|
<Tooltip
|
||||||
label="Редактировать товар">
|
onClick={onProductEditClick}
|
||||||
<ActionIcon variant={"default"}>
|
label="Редактировать товар">
|
||||||
<IconEdit />
|
<ActionIcon variant={"default"}>
|
||||||
</ActionIcon>
|
<IconEdit />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
<Tooltip
|
</Tooltip>
|
||||||
onClick={onDeleteClick}
|
<Tooltip
|
||||||
label="Удалить товар">
|
onClick={onDeleteClick}
|
||||||
<ActionIcon
|
label="Удалить товар">
|
||||||
disabled={isLocked}
|
<ActionIcon
|
||||||
variant={"default"}>
|
disabled={isLocked}
|
||||||
<IconTrash />
|
variant={"default"}>
|
||||||
</ActionIcon>
|
<IconTrash />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { createContext, FC, useContext, useEffect, useState } from "react";
|
import React, { createContext, FC, useContext, useEffect, useState } from "react";
|
||||||
import { useProjectsContext } from "../../contexts/ProjectsContext.tsx";
|
import { useProjectsContext } from "../../contexts/ProjectsContext.tsx";
|
||||||
import { MODULES } from "../modules.tsx";
|
import { ModuleNames, MODULES } from "../modules.tsx";
|
||||||
import { Module } from "../types.tsx";
|
import { Module } from "../types.tsx";
|
||||||
|
import { RootState } from "../../redux/store.ts";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
type ModulesContextState = {
|
type ModulesContextState = {
|
||||||
modules: Module[];
|
modules: Module[];
|
||||||
@@ -13,15 +15,24 @@ const ModulesContext = createContext<ModulesContextState | undefined>(
|
|||||||
|
|
||||||
const useModulesContextState = () => {
|
const useModulesContextState = () => {
|
||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
const [modules, setModules] = useState<Module[]>([]);
|
const [modules, setModules] = useState<Module[]>([]);
|
||||||
|
|
||||||
|
const filterModules = (modulesToFilter: Module[]): Module[] => {
|
||||||
|
if (isDealsViewer) {
|
||||||
|
const modulesForDealsViewer: string[] = [ModuleNames.SERVICES_AND_PRODUCTS];
|
||||||
|
return modulesToFilter.filter(module => modulesForDealsViewer.includes(module.info.key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulesToFilter;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const modules = selectedProject?.modules ?? [];
|
const modules = selectedProject?.modules ?? [];
|
||||||
const projectModules = modules.map(module => {
|
const projectModules = modules.map(module => {
|
||||||
return MODULES[module.key];
|
return MODULES[module.key];
|
||||||
}) ?? [];
|
}) ?? [];
|
||||||
setModules(projectModules);
|
setModules(filterModules(projectModules));
|
||||||
}, [selectedProject?.id]);
|
}, [selectedProject?.id]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -22,42 +22,42 @@ const modules: ModulesType = {
|
|||||||
[ModuleNames.CLIENTS]: {
|
[ModuleNames.CLIENTS]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Клиенты",
|
label: "Клиенты",
|
||||||
key: "clients",
|
key: ModuleNames.CLIENTS,
|
||||||
icon: <IconUser />,
|
icon: <IconUser />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ModuleNames.SERVICES_AND_PRODUCTS]: {
|
[ModuleNames.SERVICES_AND_PRODUCTS]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Товары и услуги",
|
label: "Товары и услуги",
|
||||||
key: "servicesAndProducts",
|
key: ModuleNames.SERVICES_AND_PRODUCTS,
|
||||||
icon: <IconBox />,
|
icon: <IconBox />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ModuleNames.SHIPMENT]: {
|
[ModuleNames.SHIPMENT]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Отгрузка",
|
label: "Отгрузка",
|
||||||
key: "shipment",
|
key: ModuleNames.SHIPMENT,
|
||||||
icon: <IconCubeSend />,
|
icon: <IconCubeSend />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ModuleNames.EMPLOYEES]: {
|
[ModuleNames.EMPLOYEES]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Сотрудники",
|
label: "Сотрудники",
|
||||||
key: "employees",
|
key: ModuleNames.EMPLOYEES,
|
||||||
icon: <IconUsersGroup />,
|
icon: <IconUsersGroup />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ModuleNames.MANAGERS]: {
|
[ModuleNames.MANAGERS]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Менеджер",
|
label: "Менеджер",
|
||||||
key: "managers",
|
key: ModuleNames.MANAGERS,
|
||||||
icon: <IconUserCog />,
|
icon: <IconUserCog />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[ModuleNames.CHAT]: {
|
[ModuleNames.CHAT]: {
|
||||||
info: {
|
info: {
|
||||||
label: "Чат",
|
label: "Чат",
|
||||||
key: "chat",
|
key: ModuleNames.CHAT,
|
||||||
icon: <IconMessage />,
|
icon: <IconMessage />,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
IconQrcode, IconSubtask,
|
IconQrcode, IconSubtask,
|
||||||
IconTopologyStar3,
|
IconTopologyStar3,
|
||||||
IconUser,
|
IconUser,
|
||||||
|
IconBuildingWarehouse,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
@@ -22,6 +23,7 @@ import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/Organi
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
|
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
|
||||||
import Attributes from "./tabs/Attributes/Attributes.tsx";
|
import Attributes from "./tabs/Attributes/Attributes.tsx";
|
||||||
|
import WarehouseManagementTab from "./tabs/WarehouseManagement/WarehouseManagementTab.tsx";
|
||||||
|
|
||||||
const AdminPage = () => {
|
const AdminPage = () => {
|
||||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
const userRole = useSelector((state: RootState) => state.auth.role);
|
||||||
@@ -96,6 +98,11 @@ const AdminPage = () => {
|
|||||||
Атрибуты карточек
|
Атрибуты карточек
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
)}
|
)}
|
||||||
|
<Tabs.Tab
|
||||||
|
value={"warehouseManagement"}
|
||||||
|
leftSection={<IconBuildingWarehouse />}>
|
||||||
|
Управление складом
|
||||||
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
{getTabPanel("users", <UsersTab />)}
|
{getTabPanel("users", <UsersTab />)}
|
||||||
{getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
|
{getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
|
||||||
@@ -105,6 +112,7 @@ const AdminPage = () => {
|
|||||||
{getTabPanel("workShifts", <WorkShiftsTab />)}
|
{getTabPanel("workShifts", <WorkShiftsTab />)}
|
||||||
{getTabPanel("transactions", <TransactionsTab />)}
|
{getTabPanel("transactions", <TransactionsTab />)}
|
||||||
{getTabPanel("attributes", <Attributes />)}
|
{getTabPanel("attributes", <Attributes />)}
|
||||||
|
{getTabPanel("warehouseManagement", <WarehouseManagementTab />)}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</PageBlock>
|
</PageBlock>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { WarehouseManagementTabContextProvider } from "./placeType/contexts/WarehouseManagementTabContext.tsx";
|
||||||
|
import { WmsPage, WmsSegmentedControl } from "./placeType/components/WmsSegmentedControl.tsx";
|
||||||
|
import { useState } from "react";
|
||||||
|
import PlaceTypesEditor from "./placeType/components/PlaceTypesEditor.tsx";
|
||||||
|
import PlacesEditor from "./place/components/PlacesEditor.tsx";
|
||||||
|
|
||||||
|
const WarehouseManagementTab = () => {
|
||||||
|
const [page, setPage] = useState(WmsPage.PLACE);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WarehouseManagementTabContextProvider>
|
||||||
|
<WmsSegmentedControl
|
||||||
|
w={"100%"}
|
||||||
|
value={page.toString()}
|
||||||
|
onChange={event => {
|
||||||
|
setPage(parseInt(event));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{page === WmsPage.PLACE_TYPE ? (
|
||||||
|
<PlaceTypesEditor />
|
||||||
|
) : (
|
||||||
|
<PlacesEditor />
|
||||||
|
)}
|
||||||
|
</WarehouseManagementTabContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WarehouseManagementTab;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { IconChevronRight, IconSpace } from "@tabler/icons-react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "../../../OrganizationalStructureTab/components/DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { PlaceSchema } from "../../../../../../client";
|
||||||
|
import PlaceActions from "./PlaceActions.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
place: PlaceSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Place = ({ place }: Props) => {
|
||||||
|
const [placeTypeIds, setPlaceTypeIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Место",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, number, placeType }) => (
|
||||||
|
<>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: placeTypeIds?.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconSpace className={classes.icon} />
|
||||||
|
<span>{placeType.name} {number}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (place) => (
|
||||||
|
<PlaceActions place={place} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={place.children?.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: placeTypeIds, onRecordIdsChange: setPlaceTypeIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<Place place={record} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Place;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { PlaceSchema } from "../../../../../../client";
|
||||||
|
import { IconEdit, IconPlaylistAdd, IconQrcode, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { useWarehouseManagementTabContext } from "../../placeType/contexts/WarehouseManagementTabContext.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
place: PlaceSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaceActions = ({ place }: Props) => {
|
||||||
|
const { placeCrud, onCreatePlace, onEditPlace, generateQrCode } = useWarehouseManagementTabContext();
|
||||||
|
|
||||||
|
const getAction = (
|
||||||
|
label: string,
|
||||||
|
func: () => void,
|
||||||
|
icon: ReactNode,
|
||||||
|
disabled: boolean = false,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label={label} key={label}>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
func();
|
||||||
|
}}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
getAction(
|
||||||
|
"Распечатать QR-код",
|
||||||
|
() => generateQrCode(place),
|
||||||
|
<IconQrcode />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Добавить",
|
||||||
|
() => onCreatePlace(place),
|
||||||
|
<IconPlaylistAdd />,
|
||||||
|
place.placeType.childCount === 0,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Редактировать",
|
||||||
|
() => onEditPlace(place),
|
||||||
|
<IconEdit />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Удалить",
|
||||||
|
() => placeCrud.onDelete && placeCrud.onDelete(place),
|
||||||
|
<IconTrash />,
|
||||||
|
place.children?.length !== 0,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex gap={"md"} mx={"md"} direction={"row"}>
|
||||||
|
{...actions}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceActions;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { FlatPlaceTypeSchema, PlaceTypeSchema } from "../../../../../../client";
|
||||||
|
import ObjectSelect, { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||||
|
|
||||||
|
type Props = Omit<
|
||||||
|
ObjectSelectProps<FlatPlaceTypeSchema | PlaceTypeSchema | null>,
|
||||||
|
"getValueFn" | "getLabelFn"
|
||||||
|
>;
|
||||||
|
|
||||||
|
const PlaceTypeSelect: FC<Props> = (props) => {
|
||||||
|
return (
|
||||||
|
<ObjectSelect
|
||||||
|
getLabelFn={(placeType: FlatPlaceTypeSchema) => placeType.name}
|
||||||
|
getValueFn={(placeType: FlatPlaceTypeSchema) => placeType.id.toString()}
|
||||||
|
clearable
|
||||||
|
searchable
|
||||||
|
{...props}
|
||||||
|
onClear={() => props.onChange(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceTypeSelect;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { Button, Group, Stack } from "@mantine/core";
|
||||||
|
import { IconChevronRight, IconSpace } from "@tabler/icons-react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "../../../OrganizationalStructureTab/components/DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useWarehouseManagementTabContext } from "../../placeType/contexts/WarehouseManagementTabContext.tsx";
|
||||||
|
import PlaceActions from "./PlaceActions.tsx";
|
||||||
|
import Place from "./Place.tsx";
|
||||||
|
|
||||||
|
const PlacesEditor = () => {
|
||||||
|
const { places, onCreatePlace } = useWarehouseManagementTabContext();
|
||||||
|
const [placeIds, setPlaceIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack w={"100%"} mt={"md"}>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onCreatePlace()}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
withTableBorder
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Место",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, number, placeType }) => (
|
||||||
|
<>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: placeIds?.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconSpace className={classes.icon} />
|
||||||
|
<span>{placeType.name} {number}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (place) => (
|
||||||
|
<PlaceActions place={place} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={places.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: placeIds, onRecordIdsChange: setPlaceIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<Place place={record} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlacesEditor;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { useCRUD } from "../../../../../../hooks/useCRUD.tsx";
|
||||||
|
import { BasePlaceSchema, EditPlaceSchema, PlaceSchema, WmsService } from "../../../../../../client";
|
||||||
|
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type PlaceCrud = {
|
||||||
|
onCreate: (element: BasePlaceSchema) => void,
|
||||||
|
onDelete: (element: PlaceSchema) => void,
|
||||||
|
onChange: (element: EditPlaceSchema) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
fetchPlaces: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usePlacesCrud = ({ fetchPlaces }: Props): PlaceCrud => {
|
||||||
|
return useCRUD<EditPlaceSchema, BasePlaceSchema>({
|
||||||
|
onChange: (place: EditPlaceSchema) => {
|
||||||
|
WmsService.editPlace({
|
||||||
|
requestBody: { place },
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaces();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
onDelete: (place: EditPlaceSchema) => {
|
||||||
|
WmsService.deletePlace({
|
||||||
|
placeId: place.id,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaces();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
onCreate: (place: BasePlaceSchema) => {
|
||||||
|
WmsService.createPlace({
|
||||||
|
requestBody: {
|
||||||
|
place,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaces();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default usePlacesCrud;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { WmsService } from "../../../../../../client";
|
||||||
|
import ObjectList from "../../../../../../hooks/objectList.tsx";
|
||||||
|
|
||||||
|
const usePlacesList = () =>
|
||||||
|
ObjectList({
|
||||||
|
queryFn: WmsService.getPlaces,
|
||||||
|
getObjectsFn: response => response.places,
|
||||||
|
queryKey: "getAllPlaces",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default usePlacesList;
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { EditPlaceSchema, FlatPlaceTypeSchema, PlaceSchema, PlaceTypeSchema } from "../../../../../../client";
|
||||||
|
import { Button, NumberInput, Stack } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { PlaceCrud } from "../hooks/usePlacesCrud.tsx";
|
||||||
|
import PlaceTypeSelect from "../components/PlaceTypeSelect.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeCrud: PlaceCrud;
|
||||||
|
parent?: PlaceSchema;
|
||||||
|
placeTypes?: FlatPlaceTypeSchema[];
|
||||||
|
element?: EditPlaceSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaceModalForm = {
|
||||||
|
placeType: PlaceTypeSchema | null;
|
||||||
|
placeNumber: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaceModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const isEditing = "element" in innerProps;
|
||||||
|
const { parent, placeCrud, placeTypes } = innerProps;
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
context.closeContextModal(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialValues: PlaceModalForm = {
|
||||||
|
placeType: null,
|
||||||
|
placeNumber: innerProps.element?.number ?? null,
|
||||||
|
};
|
||||||
|
const form = useForm<PlaceModalForm>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
placeType: placeType => !isEditing && !placeType && "Необходимо указать тип",
|
||||||
|
placeNumber: placeNumber => isEditing && !placeNumber && "Необходимо ввести номер",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (values: PlaceModalForm) => {
|
||||||
|
if (isEditing) {
|
||||||
|
const place = innerProps.element!;
|
||||||
|
placeCrud.onChange({
|
||||||
|
id: place.id,
|
||||||
|
placeTypeId: place.placeTypeId,
|
||||||
|
number: values.placeNumber!,
|
||||||
|
parentId: place.parentId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!values.placeType) return;
|
||||||
|
placeCrud.onCreate({
|
||||||
|
placeTypeId: values.placeType.id,
|
||||||
|
parentId: parent?.id || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||||
|
<Stack>
|
||||||
|
{!isEditing && (
|
||||||
|
<PlaceTypeSelect
|
||||||
|
label={"Тип места на складе"}
|
||||||
|
{...form.getInputProps("placeType")}
|
||||||
|
data={placeTypes ?? []}
|
||||||
|
defaultValue={(form.getValues().placeType ?? "") as FlatPlaceTypeSchema & string}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isEditing && (
|
||||||
|
<NumberInput
|
||||||
|
label={"Номер места на складе"}
|
||||||
|
{...form.getInputProps("placeNumber")}
|
||||||
|
defaultValue={form.getValues().placeNumber ?? ""}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button variant={"default"} type={"submit"}>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceModal;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { PlaceSchema } from "../../../../../../client";
|
||||||
|
import { Stack } from "@mantine/core";
|
||||||
|
import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
|
||||||
|
import { IconBoxAlignBottom, IconQrcode } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
place: PlaceSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SelectPlaceQrType = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const { place } = innerProps;
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
context.closeContextModal(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateQrCode = (isShort: boolean) => {
|
||||||
|
const pdfWindow = window.open(
|
||||||
|
`${import.meta.env.VITE_API_URL}/wms/place/pdf/${place.id}/${isShort}`,
|
||||||
|
);
|
||||||
|
if (!pdfWindow) return;
|
||||||
|
pdfWindow.print();
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<InlineButton onClick={() => generateQrCode(false)}>
|
||||||
|
<IconQrcode />
|
||||||
|
Целая наклейка
|
||||||
|
</InlineButton>
|
||||||
|
<InlineButton onClick={() => generateQrCode(true)}>
|
||||||
|
<IconBoxAlignBottom />
|
||||||
|
Половина наклейки
|
||||||
|
</InlineButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectPlaceQrType;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { PlaceTypeSchema } from "../../../../../../client";
|
||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { IconChevronRight, IconSpace } from "@tabler/icons-react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "../../../OrganizationalStructureTab/components/DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
import PlaceTypeActions from "./PlaceTypeActions.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeType: PlaceTypeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaceType = ({ placeType }: Props) => {
|
||||||
|
const [placeTypeIds, setPlaceTypeIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Место",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, name }) => (
|
||||||
|
<>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: placeTypeIds?.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconSpace className={classes.icon} />
|
||||||
|
<span>{name}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (placeType) => (
|
||||||
|
<PlaceTypeActions placeType={placeType} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={placeType.children?.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: placeTypeIds, onRecordIdsChange: setPlaceTypeIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<PlaceType placeType={record} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceType;
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { PlaceTypeSchema } from "../../../../../../client";
|
||||||
|
import { IconEdit, IconPlaylistAdd, IconTrash } from "@tabler/icons-react";
|
||||||
|
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { useWarehouseManagementTabContext } from "../contexts/WarehouseManagementTabContext.tsx";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeType: PlaceTypeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaceTypeActions = ({ placeType }: Props) => {
|
||||||
|
const { placeTypeCrud } = useWarehouseManagementTabContext();
|
||||||
|
|
||||||
|
const getAction = (
|
||||||
|
label: string,
|
||||||
|
func: () => void,
|
||||||
|
icon: ReactNode,
|
||||||
|
disabled: boolean = false,
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label={label} key={label}>
|
||||||
|
<ActionIcon
|
||||||
|
disabled={disabled}
|
||||||
|
variant={"default"}
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
func();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
getAction(
|
||||||
|
"Добавить",
|
||||||
|
() => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "placeTypeModal",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
placeTypeCrud,
|
||||||
|
parent: placeType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
<IconPlaylistAdd />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Редактировать",
|
||||||
|
() => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "placeTypeModal",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
placeTypeCrud,
|
||||||
|
element: placeType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
<IconEdit />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Удалить",
|
||||||
|
() => placeTypeCrud.onDelete && placeTypeCrud.onDelete(placeType),
|
||||||
|
<IconTrash />,
|
||||||
|
placeType.placesCount !== 0 || placeType.children?.length !== 0,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex gap={"md"} mx={"md"} direction={"row"}>
|
||||||
|
{...actions}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceTypeActions;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Button, Group, Stack } from "@mantine/core";
|
||||||
|
import { useWarehouseManagementTabContext } from "../contexts/WarehouseManagementTabContext.tsx";
|
||||||
|
import { IconChevronRight, IconSpace } from "@tabler/icons-react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "../../../OrganizationalStructureTab/components/DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { useState } from "react";
|
||||||
|
import PlaceType from "./PlaceType.tsx";
|
||||||
|
import PlaceTypeActions from "./PlaceTypeActions.tsx";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
|
||||||
|
const PlaceTypesEditor = () => {
|
||||||
|
const { placeTypes, placeTypeCrud } = useWarehouseManagementTabContext();
|
||||||
|
const [placeTypeIds, setPlaceTypeIds] = useState<number[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack w={"100%"} mt={"md"}>
|
||||||
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "placeTypeModal",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
placeTypeCrud,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
withTableBorder
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Место",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, name }) => (
|
||||||
|
<>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: placeTypeIds?.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconSpace className={classes.icon} />
|
||||||
|
<span>{name}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (placeType) => (
|
||||||
|
<PlaceTypeActions placeType={placeType} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={placeTypes.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: placeTypeIds, onRecordIdsChange: setPlaceTypeIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<PlaceType placeType={record} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceTypesEditor;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
export enum WmsPage {
|
||||||
|
PLACE,
|
||||||
|
PLACE_TYPE,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = Omit<SegmentedControlProps, "data">;
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
label: "Места на складе",
|
||||||
|
value: WmsPage.PLACE.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Типы мест на складе",
|
||||||
|
value: WmsPage.PLACE_TYPE.toString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const WmsSegmentedControl: FC<Props> = props => {
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import React, { createContext, FC, useContext } from "react";
|
||||||
|
import { EditPlaceSchema, PlaceSchema, PlaceTypeSchema, WmsService } from "../../../../../../client";
|
||||||
|
import usePlaceTypesList from "../hooks/usePlaceTypesList.tsx";
|
||||||
|
import usePlaceTypesCrud, { PlaceTypeCrud } from "../hooks/usePlaceTypesCrud.tsx";
|
||||||
|
import usePlacesList from "../../place/hooks/usePlacesList.tsx";
|
||||||
|
import usePlacesCrud, { PlaceCrud } from "../../place/hooks/usePlacesCrud.tsx";
|
||||||
|
import { modals } from "@mantine/modals";
|
||||||
|
|
||||||
|
type WarehouseManagementTabContextState = {
|
||||||
|
refetchPlaceTypes: () => void;
|
||||||
|
placeTypeCrud: PlaceTypeCrud;
|
||||||
|
placeTypes: PlaceTypeSchema[];
|
||||||
|
refetchPlaces: () => void;
|
||||||
|
placeCrud: PlaceCrud;
|
||||||
|
places: PlaceSchema[];
|
||||||
|
onCreatePlace: (place?: PlaceSchema) => void;
|
||||||
|
onEditPlace: (place?: EditPlaceSchema) => void;
|
||||||
|
generateQrCode: (place: PlaceSchema) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WarehouseManagementTabContext = createContext<WarehouseManagementTabContextState | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const useWarehouseManagementTabContextState = () => {
|
||||||
|
const { objects: placeTypes, refetch: refetchPlaceTypes } = usePlaceTypesList();
|
||||||
|
const { objects: places, refetch: refetchPlaces } = usePlacesList();
|
||||||
|
|
||||||
|
const placeTypeCrud: PlaceTypeCrud = usePlaceTypesCrud({
|
||||||
|
fetchPlaceTypes: refetchPlaceTypes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const placeCrud: PlaceCrud = usePlacesCrud({
|
||||||
|
fetchPlaces: refetchPlaces,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onCreatePlace = (place?: PlaceSchema) => {
|
||||||
|
WmsService.getFlatPlaceTypes({
|
||||||
|
parentPlaceTypeId: place?.placeTypeId || -1,
|
||||||
|
})
|
||||||
|
.then(({ placeTypes }) => {
|
||||||
|
if (placeTypes.length === 1) {
|
||||||
|
placeCrud.onCreate({
|
||||||
|
parentId: place?.id || null,
|
||||||
|
placeTypeId: placeTypes[0].id,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "placeModal",
|
||||||
|
withCloseButton: false,
|
||||||
|
innerProps: {
|
||||||
|
placeCrud,
|
||||||
|
parent: place,
|
||||||
|
placeTypes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditPlace = (place?: EditPlaceSchema) => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "placeModal",
|
||||||
|
withCloseButton: false,
|
||||||
|
title: "Редактирование",
|
||||||
|
innerProps: {
|
||||||
|
placeCrud,
|
||||||
|
element: place,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateQrCode = (place: PlaceSchema) => {
|
||||||
|
modals.openContextModal({
|
||||||
|
modal: "selectPlaceQrType",
|
||||||
|
withCloseButton: false,
|
||||||
|
centered: true,
|
||||||
|
innerProps: {
|
||||||
|
place,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
placeTypes,
|
||||||
|
placeTypeCrud,
|
||||||
|
refetchPlaceTypes,
|
||||||
|
places,
|
||||||
|
placeCrud,
|
||||||
|
refetchPlaces,
|
||||||
|
onCreatePlace,
|
||||||
|
onEditPlace,
|
||||||
|
generateQrCode,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type WarehouseManagementTabContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WarehouseManagementTabContextProvider: FC<WarehouseManagementTabContextProviderProps> = ({ children }) => {
|
||||||
|
const state = useWarehouseManagementTabContextState();
|
||||||
|
return (
|
||||||
|
<WarehouseManagementTabContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</WarehouseManagementTabContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWarehouseManagementTabContext = () => {
|
||||||
|
const context = useContext(WarehouseManagementTabContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useWarehouseManagementTabContext must be used within a WarehouseManagementTabContextProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { WmsService } from "../../../../../../client";
|
||||||
|
import ObjectList from "../../../../../../hooks/objectList.tsx";
|
||||||
|
|
||||||
|
const useFlatPlaceTypesList = (parentPlaceTypeId: number = -1) =>
|
||||||
|
ObjectList({
|
||||||
|
queryFn: () => WmsService.getFlatPlaceTypes({ parentPlaceTypeId }),
|
||||||
|
getObjectsFn: response => response.placeTypes,
|
||||||
|
queryKey: "getFlatPlaceTypes",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useFlatPlaceTypesList;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { useCRUD } from "../../../../../../hooks/useCRUD.tsx";
|
||||||
|
import { BasePlaceTypeSchema, FlatPlaceTypeSchema, WmsService } from "../../../../../../client";
|
||||||
|
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type PlaceTypeCrud = {
|
||||||
|
onCreate: (element: BasePlaceTypeSchema) => void,
|
||||||
|
onDelete: (element: FlatPlaceTypeSchema) => void,
|
||||||
|
onChange: (element: FlatPlaceTypeSchema) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
fetchPlaceTypes: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usePlaceTypesCrud = ({ fetchPlaceTypes }: Props): PlaceTypeCrud => {
|
||||||
|
return useCRUD<FlatPlaceTypeSchema, BasePlaceTypeSchema>({
|
||||||
|
onChange: (placeType: FlatPlaceTypeSchema) => {
|
||||||
|
WmsService.editPlaceType({
|
||||||
|
requestBody: { placeType },
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaceTypes();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
onDelete: (placeType: FlatPlaceTypeSchema) => {
|
||||||
|
WmsService.deletePlaceType({
|
||||||
|
placeTypeId: placeType.id,
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaceTypes();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
onCreate: (placeType: BasePlaceTypeSchema) => {
|
||||||
|
WmsService.createPlaceType({
|
||||||
|
requestBody: {
|
||||||
|
placeType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchPlaceTypes();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default usePlaceTypesCrud;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { WmsService } from "../../../../../../client";
|
||||||
|
import ObjectList from "../../../../../../hooks/objectList.tsx";
|
||||||
|
|
||||||
|
const usePlaceTypesList = () =>
|
||||||
|
ObjectList({
|
||||||
|
queryFn: WmsService.getPlaceTypes,
|
||||||
|
getObjectsFn: response => response.placeTypes,
|
||||||
|
queryKey: "getAllPlaceTypes",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default usePlaceTypesList;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
|
import { BasePlaceTypeSchema, PlaceTypeSchema } from "../../../../../../client";
|
||||||
|
import { PlaceTypeCrud } from "../hooks/usePlaceTypesCrud.tsx";
|
||||||
|
import { Button, Stack, TextInput } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
placeTypeCrud: PlaceTypeCrud;
|
||||||
|
element?: PlaceTypeSchema & BasePlaceTypeSchema;
|
||||||
|
parent?: PlaceTypeSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaceTypeModalForm = {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaceTypeModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps,
|
||||||
|
}: ContextModalProps<Props>) => {
|
||||||
|
const { element, parent, placeTypeCrud } = innerProps;
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
context.closeContextModal(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialValues: PlaceTypeModalForm = {
|
||||||
|
name: innerProps.element?.name ?? "",
|
||||||
|
};
|
||||||
|
const form = useForm<PlaceTypeModalForm>({
|
||||||
|
initialValues,
|
||||||
|
validate: {
|
||||||
|
name: name => !name && "Необходимо указать название",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isChanged = (): boolean => {
|
||||||
|
return initialValues.name !== form.values.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (values: PlaceTypeModalForm) => {
|
||||||
|
if (element) {
|
||||||
|
if (isChanged()) {
|
||||||
|
placeTypeCrud.onChange({
|
||||||
|
name: values.name ?? "",
|
||||||
|
id: element?.id,
|
||||||
|
parentId: element?.parentId ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
placeTypeCrud.onCreate({
|
||||||
|
name: values.name ?? "",
|
||||||
|
parentId: parent?.id ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={form.onSubmit(values => onSubmit(values))}>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
label={"Название"}
|
||||||
|
{...form.getInputProps("name")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button variant={"default"} type={"submit"}>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceTypeModal;
|
||||||
@@ -7,7 +7,7 @@ import { processSelectedCells } from "../../../../../shared/lib/interpolateCells
|
|||||||
import { TimeTrackingData } from "../../../../../client";
|
import { TimeTrackingData } from "../../../../../client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { IMaskInput } from "react-imask";
|
import { IMaskInput } from "react-imask";
|
||||||
import { floatHoursToHoursAndMinutes } from "../../../../../types/utils.ts";
|
import { dateToString, floatHoursToHoursAndMinutes } from "../../../../../types/utils.ts";
|
||||||
|
|
||||||
export type EmployeeData = {
|
export type EmployeeData = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -20,21 +20,17 @@ export type EmployeeData = {
|
|||||||
[key: string]: number | string;
|
[key: string]: number | string;
|
||||||
};
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
month: Date;
|
|
||||||
data: EmployeeData[];
|
data: EmployeeData[];
|
||||||
onUpdate: (date: Date, userId: number, value: string) => void;
|
onUpdate: (date: Date, userId: number, value: string) => void;
|
||||||
selectedCells: string[];
|
selectedCells: string[];
|
||||||
setSelectedCells: (cells: string[]) => void;
|
setSelectedCells: (cells: string[]) => void;
|
||||||
selectedBoundaries: [Date | null, Date | null];
|
|
||||||
range: dayjs.Dayjs[];
|
range: dayjs.Dayjs[];
|
||||||
};
|
};
|
||||||
const useWorkTableColumns = ({
|
const useWorkTableColumns = ({
|
||||||
month,
|
|
||||||
onUpdate,
|
onUpdate,
|
||||||
data,
|
data,
|
||||||
selectedCells,
|
selectedCells,
|
||||||
setSelectedCells,
|
setSelectedCells,
|
||||||
selectedBoundaries,
|
|
||||||
range,
|
range,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const totalAmount = useMemo(() => {
|
const totalAmount = useMemo(() => {
|
||||||
@@ -48,7 +44,7 @@ const useWorkTableColumns = ({
|
|||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [data, month, selectedCells, selectedBoundaries]);
|
}, [data, selectedCells, range]);
|
||||||
const getBorderStyles = (cellId: string) => {
|
const getBorderStyles = (cellId: string) => {
|
||||||
if (selectedCells.length <= 1) return {};
|
if (selectedCells.length <= 1) return {};
|
||||||
if (selectedCells[0] === cellId)
|
if (selectedCells[0] === cellId)
|
||||||
@@ -86,8 +82,8 @@ const useWorkTableColumns = ({
|
|||||||
|
|
||||||
...range.map(date => ({
|
...range.map(date => ({
|
||||||
size: 100,
|
size: 100,
|
||||||
accessorKey: date.date().toString(),
|
accessorKey: dateToString(date.toDate()) ?? "",
|
||||||
header: date.date().toString(),
|
header: dateToString(date.toDate()) ?? "",
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableColumnActions: false,
|
enableColumnActions: false,
|
||||||
Header: (
|
Header: (
|
||||||
@@ -130,7 +126,7 @@ const useWorkTableColumns = ({
|
|||||||
row.original.userId,
|
row.original.userId,
|
||||||
value,
|
value,
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction={"column"}>
|
<Flex direction={"column"}>
|
||||||
@@ -178,7 +174,7 @@ const useWorkTableColumns = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[month, selectedCells, selectedBoundaries, totalAmount],
|
[selectedCells, range, totalAmount],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,45 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TimeTrackingRecord, TimeTrackingService } from "../../../../../client";
|
import { TimeTrackingRecord, TimeTrackingService } from "../../../../../client";
|
||||||
import {
|
import { getDefaultEndDate } from "../../WorkShiftsPlanning/utils/utils.tsx";
|
||||||
dateWithoutTimezone,
|
import { dateToString } from "../../../../../types/utils.ts";
|
||||||
getDatesInMonth,
|
|
||||||
} from "../../../../../shared/lib/date.ts";
|
|
||||||
import { last } from "lodash";
|
|
||||||
|
|
||||||
const getDateBoundaries = (month: Date) => {
|
|
||||||
return [
|
|
||||||
getDatesInMonth(month)[0].toDate(),
|
|
||||||
last(getDatesInMonth(month))?.toDate(),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
const useWorkTableState = () => {
|
const useWorkTableState = () => {
|
||||||
const [month, setMonth] = useState<Date>(
|
const [dateRange, setDateRange] = useState<
|
||||||
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
|
[Date | null, Date | null]
|
||||||
);
|
>([new Date(), getDefaultEndDate()]);
|
||||||
const [trackingRecords, setTrackingRecords] = useState<
|
const [trackingRecords, setTrackingRecords] = useState<
|
||||||
TimeTrackingRecord[]
|
TimeTrackingRecord[]
|
||||||
>([]);
|
>([]);
|
||||||
const [dateBoundaries, setDateBoundaries] = useState(
|
|
||||||
getDateBoundaries(month)
|
|
||||||
);
|
|
||||||
|
|
||||||
const refetch = async () => {
|
const refetch = () => {
|
||||||
return TimeTrackingService.getTimeTrackingRecords({
|
const ending = "T00:00:00";
|
||||||
requestBody: {
|
const dateFrom = dateToString(dateRange[0]);
|
||||||
date: dateWithoutTimezone(month),
|
const dateTo = dateToString(dateRange[1]);
|
||||||
userIds: [],
|
if (!(dateFrom && dateTo)) return;
|
||||||
},
|
|
||||||
}).then(response => setTrackingRecords(response.records));
|
TimeTrackingService
|
||||||
|
.getTimeTrackingRecords({
|
||||||
|
requestBody: {
|
||||||
|
dateFrom: dateFrom + ending,
|
||||||
|
dateTo: dateTo + ending,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
console.log(response.records);
|
||||||
|
setTrackingRecords(response.records);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
refetch();
|
||||||
refetch().then(_ => {
|
}, [dateRange]);
|
||||||
setDateBoundaries(getDateBoundaries(month));
|
|
||||||
});
|
|
||||||
}, [month]);
|
|
||||||
return {
|
return {
|
||||||
month,
|
dateRange,
|
||||||
setMonth,
|
setDateRange,
|
||||||
refetch,
|
refetch,
|
||||||
trackingRecords,
|
trackingRecords,
|
||||||
dateBoundaries,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,44 @@
|
|||||||
import { ActionIcon, Flex, MultiSelect, rem, Tooltip } from "@mantine/core";
|
import { ActionIcon, Flex, MultiSelect, rem, Tooltip } from "@mantine/core";
|
||||||
import { DatePickerInput, MonthPickerInput } from "@mantine/dates";
|
import { DatePickerInput } from "@mantine/dates";
|
||||||
import useWorkTableState from "../hooks/useWorkTableState.tsx";
|
import useWorkTableState from "../hooks/useWorkTableState.tsx";
|
||||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useWorkTableColumns, { EmployeeData } from "../hooks/useWorkTableColumns.tsx";
|
import useWorkTableColumns, { EmployeeData } from "../hooks/useWorkTableColumns.tsx";
|
||||||
import { TimeTrackingRecord, TimeTrackingService, UserSchema } from "../../../../../client";
|
import { TimeTrackingRecord, TimeTrackingService, UserSchema } from "../../../../../client";
|
||||||
import { dateWithoutTimezone, getDatesBetween, getDatesInMonth } from "../../../../../shared/lib/date.ts";
|
import { getDatesBetween } from "../../../../../shared/lib/date.ts";
|
||||||
import useUsersList from "../../../hooks/useUsersList.tsx";
|
import useUsersList from "../../../hooks/useUsersList.tsx";
|
||||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
import { PaySchemeType } from "../../../../../shared/enums/PaySchemeType.ts";
|
import { PaySchemeType } from "../../../../../shared/enums/PaySchemeType.ts";
|
||||||
import { IconEyeOff } from "@tabler/icons-react";
|
import { IconEyeOff } from "@tabler/icons-react";
|
||||||
import { MRT_TableOptions } from "mantine-react-table";
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
import { difference, omit } from "lodash";
|
import { difference, omit } from "lodash";
|
||||||
import { strTimeToFloatHours } from "../../../../../types/utils.ts";
|
import { dateToString, strTimeToFloatHours } from "../../../../../types/utils.ts";
|
||||||
import { useListState } from "@mantine/hooks";
|
import { useListState } from "@mantine/hooks";
|
||||||
|
|
||||||
const WorkTimeTable = () => {
|
const WorkTimeTable = () => {
|
||||||
const [data, setData] = useState<EmployeeData[]>([]);
|
const [data, setData] = useState<EmployeeData[]>([]);
|
||||||
const { dateBoundaries, month, setMonth, trackingRecords, refetch } =
|
const { dateRange, setDateRange, trackingRecords, refetch } = useWorkTableState();
|
||||||
useWorkTableState();
|
|
||||||
const [shownUsers, shownUsersHandlers] = useListState<UserSchema>([]);
|
const [shownUsers, shownUsersHandlers] = useListState<UserSchema>([]);
|
||||||
const [selectedBoundaries, setSelectedBoundaries] = useState<
|
|
||||||
[Date | null, Date | null]
|
|
||||||
>([null, null]);
|
|
||||||
|
|
||||||
const users = useUsersList().objects.filter(
|
const users = useUsersList().objects.filter(
|
||||||
user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY,
|
user => user.payRate?.payrollScheme.key === PaySchemeType.HOURLY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRange = () => {
|
const getRange = () => {
|
||||||
const startDate = selectedBoundaries[0];
|
if (!(dateRange.length === 2 && dateRange[0] && dateRange[1])) return [];
|
||||||
const endDate = selectedBoundaries[1];
|
const startDate = dateRange[0];
|
||||||
if (startDate && endDate) {
|
const endDate = dateRange[1];
|
||||||
return getDatesBetween(startDate, endDate);
|
return getDatesBetween(startDate, endDate);
|
||||||
} else {
|
|
||||||
return getDatesInMonth(month);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const range = getRange();
|
const range = getRange();
|
||||||
|
|
||||||
const transformTrackingRecordsToData = (
|
const transformTrackingRecordsToData = (
|
||||||
trackingRecords: TimeTrackingRecord[],
|
trackingRecords: TimeTrackingRecord[],
|
||||||
): EmployeeData[] => {
|
): EmployeeData[] => {
|
||||||
if (!month) return [];
|
if (!(dateRange.length === 2 && dateRange[0] && dateRange[1])) return [];
|
||||||
const rangeDays = range.map(r => r.date());
|
const dateFrom = dateRange[0];
|
||||||
|
const dateTo = dateRange[1];
|
||||||
|
const dates = getDatesBetween(dateFrom, dateTo);
|
||||||
|
|
||||||
trackingRecords = trackingRecords.map(tr => ({
|
|
||||||
...tr,
|
|
||||||
data: tr.data.filter(d =>
|
|
||||||
rangeDays.includes(new Date(d.date).getDate()),
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
const existingUserIds = trackingRecords.map(tr => tr.user.id);
|
const existingUserIds = trackingRecords.map(tr => tr.user.id);
|
||||||
const firstResult = trackingRecords.map(record => ({
|
const firstResult = trackingRecords.map(record => ({
|
||||||
name: `${record.user.firstName} ${record.user.secondName}`,
|
name: `${record.user.firstName} ${record.user.secondName}`,
|
||||||
@@ -59,14 +47,14 @@ const WorkTimeTable = () => {
|
|||||||
totalAmount: record.totalAmount,
|
totalAmount: record.totalAmount,
|
||||||
data: record.data,
|
data: record.data,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
getDatesInMonth(month).reduce((acc, day) => {
|
dates.reduce((acc, day) => {
|
||||||
return acc.set(day.date().toString(), 0);
|
return acc.set(dateToString(day.toDate()) ?? "", 0);
|
||||||
}, new Map<string, number>()),
|
}, new Map<string, number>()),
|
||||||
),
|
),
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
record.data.reduce((acc, recordData) => {
|
record.data.reduce((acc, recordData) => {
|
||||||
return acc.set(
|
return acc.set(
|
||||||
new Date(recordData.date).getDate().toString(),
|
recordData.date,
|
||||||
recordData.hours,
|
recordData.hours,
|
||||||
);
|
);
|
||||||
}, new Map<string, number>()),
|
}, new Map<string, number>()),
|
||||||
@@ -80,7 +68,7 @@ const WorkTimeTable = () => {
|
|||||||
comment: user.comment,
|
comment: user.comment,
|
||||||
totalAmount: 0,
|
totalAmount: 0,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
getDatesInMonth(month).reduce((acc, day) => {
|
dates.reduce((acc, day) => {
|
||||||
return acc.set(day.date().toString(), 0);
|
return acc.set(day.date().toString(), 0);
|
||||||
}, new Map<string, number>()),
|
}, new Map<string, number>()),
|
||||||
),
|
),
|
||||||
@@ -93,13 +81,12 @@ const WorkTimeTable = () => {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
.concat(restUsersResult)
|
.concat(restUsersResult)
|
||||||
.filter(r => shownUserIds.includes(r.userId));
|
.filter(r => shownUserIds.includes(r.userId));
|
||||||
const firstDate = selectedBoundaries[0];
|
|
||||||
const lastDate = selectedBoundaries[1];
|
if (dateFrom && dateTo) {
|
||||||
if (firstDate && lastDate) {
|
const allDays = dates.map(d =>
|
||||||
const allDays = getDatesInMonth(month).map(d =>
|
|
||||||
d.date().toString(),
|
d.date().toString(),
|
||||||
);
|
);
|
||||||
const allowedDays = getDatesBetween(firstDate, lastDate).map(d =>
|
const allowedDays = getDatesBetween(dateFrom, dateTo).map(d =>
|
||||||
d.date().toString(),
|
d.date().toString(),
|
||||||
);
|
);
|
||||||
const omitDays = difference(allDays, allowedDays);
|
const omitDays = difference(allDays, allowedDays);
|
||||||
@@ -117,10 +104,7 @@ const WorkTimeTable = () => {
|
|||||||
const user = users.find(user => user.id === userId);
|
const user = users.find(user => user.id === userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
console.log(value);
|
|
||||||
|
|
||||||
const hours = strTimeToFloatHours(value);
|
const hours = strTimeToFloatHours(value);
|
||||||
console.log(hours);
|
|
||||||
if (hours === -1) return;
|
if (hours === -1) return;
|
||||||
|
|
||||||
setData(prevState =>
|
setData(prevState =>
|
||||||
@@ -128,12 +112,13 @@ const WorkTimeTable = () => {
|
|||||||
if (record.userId !== userId) return record;
|
if (record.userId !== userId) return record;
|
||||||
record[date.getDate()] = value;
|
record[date.getDate()] = value;
|
||||||
return record;
|
return record;
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ending = "T00:00:00";
|
||||||
TimeTrackingService.updateTimeTrackingRecord({
|
TimeTrackingService.updateTimeTrackingRecord({
|
||||||
requestBody: {
|
requestBody: {
|
||||||
date: dateWithoutTimezone(date),
|
date: dateToString(date) + ending,
|
||||||
hours,
|
hours,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
},
|
},
|
||||||
@@ -141,13 +126,11 @@ const WorkTimeTable = () => {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
notifications.guess(ok, { message });
|
notifications.guess(ok, { message });
|
||||||
}
|
}
|
||||||
await refetch();
|
refetch();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = useWorkTableColumns({
|
const columns = useWorkTableColumns({
|
||||||
month,
|
|
||||||
selectedBoundaries,
|
|
||||||
data,
|
data,
|
||||||
onUpdate: optimisticUpdate,
|
onUpdate: optimisticUpdate,
|
||||||
selectedCells: [],
|
selectedCells: [],
|
||||||
@@ -158,11 +141,7 @@ const WorkTimeTable = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData(transformTrackingRecordsToData(trackingRecords));
|
setData(transformTrackingRecordsToData(trackingRecords));
|
||||||
}, [trackingRecords, shownUsers, selectedBoundaries]);
|
}, [trackingRecords, shownUsers, dateRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedBoundaries([null, null]);
|
|
||||||
}, [month]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -193,55 +172,45 @@ const WorkTimeTable = () => {
|
|||||||
/>
|
/>
|
||||||
<Flex gap={rem(10)}>
|
<Flex gap={rem(10)}>
|
||||||
<DatePickerInput
|
<DatePickerInput
|
||||||
styles={{
|
placeholder={"Выберите временной промежуток"}
|
||||||
input: {
|
|
||||||
textAlign: "center",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
miw={rem(80)}
|
|
||||||
valueFormat={"DD"}
|
|
||||||
type={"range"}
|
type={"range"}
|
||||||
minDate={dateBoundaries[0]}
|
value={dateRange}
|
||||||
maxDate={dateBoundaries[1]}
|
onChange={(value) => {
|
||||||
value={selectedBoundaries}
|
setDateRange(value);
|
||||||
onChange={setSelectedBoundaries}
|
}}
|
||||||
placeholder={"Даты"}
|
|
||||||
/>
|
|
||||||
<MonthPickerInput
|
|
||||||
allowDeselect={false}
|
|
||||||
onChange={event => event && setMonth(event)}
|
|
||||||
value={month}
|
|
||||||
placeholder={"Выберите месяц"}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex>
|
<Flex>
|
||||||
<BaseTable
|
{dateRange[1] !== null && (
|
||||||
data={data}
|
<BaseTable
|
||||||
columns={columns}
|
data={data}
|
||||||
restProps={
|
columns={columns}
|
||||||
{
|
restProps={
|
||||||
enableColumnActions: false,
|
{
|
||||||
enableSorting: false,
|
enableColumnActions: false,
|
||||||
enableRowActions: true,
|
enableSorting: false,
|
||||||
renderRowActions: ({ row }) => (
|
enableRowActions: true,
|
||||||
<Flex gap="md">
|
renderRowActions: ({ row }) => (
|
||||||
<Tooltip label="Скрыть">
|
<Flex gap="md">
|
||||||
<ActionIcon
|
<Tooltip label="Скрыть">
|
||||||
onClick={() => {
|
<ActionIcon
|
||||||
shownUsersHandlers.filter(
|
onClick={() => {
|
||||||
user => user.id !== row.original.userId,
|
shownUsersHandlers.filter(
|
||||||
);
|
user => user.id !== row.original.userId,
|
||||||
}}
|
);
|
||||||
variant={"default"}>
|
}}
|
||||||
<IconEyeOff />
|
variant={"default"}>
|
||||||
</ActionIcon>
|
<IconEyeOff />
|
||||||
</Tooltip>
|
</ActionIcon>
|
||||||
</Flex>
|
</Tooltip>
|
||||||
),
|
</Flex>
|
||||||
} as MRT_TableOptions<EmployeeData>
|
),
|
||||||
}
|
} as MRT_TableOptions<EmployeeData>
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
import { useParams } from "@tanstack/react-router";
|
import { useParams } from "@tanstack/react-router";
|
||||||
import { CardPageContextProvider, useCardPageContext } from "../../CardsPage/contexts/CardPageContext.tsx";
|
import { CardPageContextProvider, useCardPageContext } from "../../CardsPage/contexts/CardPageContext.tsx";
|
||||||
import ProductAndServiceTab from "../../../modules/cardModules/cardEditorTabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
import ProductAndServiceTab
|
||||||
|
from "../../../modules/cardModules/cardEditorTabs/ProductAndServiceTab/ProductAndServiceTab.tsx";
|
||||||
import React, { FC, useEffect } from "react";
|
import React, { FC, useEffect } from "react";
|
||||||
import { CardService } from "../../../client";
|
import { CardService } from "../../../client";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store.ts";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
cardId: number;
|
cardId: number;
|
||||||
};
|
};
|
||||||
const CardPageContent: FC<Props> = ({ cardId }) => {
|
const CardPageContent: FC<Props> = ({ cardId }) => {
|
||||||
const { setSelectedCard } = useCardPageContext();
|
const { setSelectedCard } = useCardPageContext();
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
CardService.getCardById({ cardId }).then(card => {
|
if (authState.isAuthorized) {
|
||||||
setSelectedCard(card);
|
CardService.getCardById({ cardId }).then(card => {
|
||||||
});
|
setSelectedCard(card);
|
||||||
}, []);
|
});
|
||||||
|
}
|
||||||
|
}, [authState.isAuthorized]);
|
||||||
|
|
||||||
return <ProductAndServiceTab />;
|
return <ProductAndServiceTab />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ActionIcon, Flex, rem, Text } from "@mantine/core";
|
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
||||||
import { IconEdit, IconMenu2, IconMenuDeep, IconPlus } from "@tabler/icons-react";
|
import { IconEdit, IconLogout, IconMenu2, IconMenuDeep, IconPlus } from "@tabler/icons-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import styles from "../../ui/CardsPage.module.css";
|
import styles from "../../ui/CardsPage.module.css";
|
||||||
import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
|
import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
|
||||||
@@ -11,9 +11,12 @@ import ObjectSelect from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
|||||||
import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
|
import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
|
||||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
|
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../../redux/store.ts";
|
import { RootState, useAppDispatch } from "../../../../redux/store.ts";
|
||||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
|
import classes from "../../../../components/Navbar/Navbar.module.css";
|
||||||
|
import { logout } from "../../../../features/authSlice.ts";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
displayMode: DisplayMode;
|
displayMode: DisplayMode;
|
||||||
@@ -28,7 +31,9 @@ const CardsPageHeader = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { openProjectsEditor } = useProjectsEditorContext();
|
const { openProjectsEditor } = useProjectsEditorContext();
|
||||||
const { selectedProject, setSelectedProject, projects, refetchProjects } = useProjectsContext();
|
const { selectedProject, setSelectedProject, projects, refetchProjects } = useProjectsContext();
|
||||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
const { role: userRole, isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const isAdmin = userRole === "admin";
|
const isAdmin = userRole === "admin";
|
||||||
|
|
||||||
const handleCreateClick = () => {
|
const handleCreateClick = () => {
|
||||||
@@ -42,6 +47,31 @@ const CardsPageHeader = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onLogoutClick = () => {
|
||||||
|
dispatch(logout());
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLogoutButton = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
display={"flex"}
|
||||||
|
label={"Выйти"}
|
||||||
|
position="right"
|
||||||
|
transitionProps={{ duration: 0 }}>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => onLogoutClick()}
|
||||||
|
className={classes.link}>
|
||||||
|
<IconLogout
|
||||||
|
style={{ width: rem(20), height: rem(20) }}
|
||||||
|
stroke={1.5}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getHeaderInputsBoard = () => {
|
const getHeaderInputsBoard = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -66,12 +96,16 @@ const CardsPageHeader = ({
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<ObjectSelect
|
{isDealsViewer ? (
|
||||||
placeholder={"Выберите проект"}
|
getLogoutButton()
|
||||||
data={projects}
|
) : (
|
||||||
value={selectedProject}
|
<ObjectSelect
|
||||||
onChange={setSelectedProject}
|
placeholder={"Выберите проект"}
|
||||||
/>
|
data={projects}
|
||||||
|
value={selectedProject}
|
||||||
|
onChange={setSelectedProject}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,26 +11,26 @@ type DndContextState = {
|
|||||||
dragState: DragState,
|
dragState: DragState,
|
||||||
onDragStart: (start: DragStart) => void,
|
onDragStart: (start: DragStart) => void,
|
||||||
onDragEnd: (result: DropResult) => Promise<void>,
|
onDragEnd: (result: DropResult) => Promise<void>,
|
||||||
|
refetchSummaries: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DndContext = createContext<DndContextState | undefined>(undefined);
|
const DndContext = createContext<DndContextState | undefined>(undefined);
|
||||||
|
|
||||||
type DndContextProps = {
|
type DndContextProps = {
|
||||||
summariesRaw: CardSummary[];
|
summaries: CardSummary[];
|
||||||
refetchSummaries: () => void;
|
refetchSummaries: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useDndContextState = ({
|
const useDndContextState = ({
|
||||||
summariesRaw,
|
summaries,
|
||||||
refetchSummaries,
|
refetchSummaries,
|
||||||
}: DndContextProps) => {
|
}: DndContextProps) => {
|
||||||
const [dragState, setDragState] = useState<DragState>(DragState.DRAG_ENDED);
|
const [dragState, setDragState] = useState<DragState>(DragState.DRAG_ENDED);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
summaries,
|
|
||||||
onCardDragEnd,
|
onCardDragEnd,
|
||||||
} = useCardsDnd({
|
} = useCardsDnd({
|
||||||
summariesRaw,
|
summaries,
|
||||||
refetchSummaries,
|
refetchSummaries,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ const useDndContextState = ({
|
|||||||
dragState,
|
dragState,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
|
refetchSummaries,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import CardStatusSelect from "../../../../../../components/CardStatusSelect/Card
|
|||||||
import CardAttributeFields from "../../../../../../components/CardAttributeFields/CardAttributeFields.tsx";
|
import CardAttributeFields from "../../../../../../components/CardAttributeFields/CardAttributeFields.tsx";
|
||||||
import getAttributesFromCard from "../../../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
|
import getAttributesFromCard from "../../../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
|
||||||
import CardTagsInput from "../../../../components/CardTagsInput/CardTagsInput.tsx";
|
import CardTagsInput from "../../../../components/CardTagsInput/CardTagsInput.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
card: CardSchema;
|
card: CardSchema;
|
||||||
@@ -43,6 +45,7 @@ const Content: FC<Props> = ({ card }) => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
|
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
|
||||||
const [cardTags, setCardTags] = useState<string[]>(card.tags?.map(tag => tag.name) ?? []);
|
const [cardTags, setCardTags] = useState<string[]>(card.tags?.map(tag => tag.name) ?? []);
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
|
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
|
||||||
return {
|
return {
|
||||||
@@ -159,6 +162,7 @@ const Content: FC<Props> = ({ card }) => {
|
|||||||
placeholder={"Название сделки"}
|
placeholder={"Название сделки"}
|
||||||
label={"Название сделки"}
|
label={"Название сделки"}
|
||||||
{...form.getInputProps("name")}
|
{...form.getInputProps("name")}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
disabled
|
disabled
|
||||||
@@ -178,11 +182,13 @@ const Content: FC<Props> = ({ card }) => {
|
|||||||
project={project}
|
project={project}
|
||||||
{...form.getInputProps("board")}
|
{...form.getInputProps("board")}
|
||||||
label={"Доска"}
|
label={"Доска"}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<CardStatusSelect
|
<CardStatusSelect
|
||||||
board={form.values.board}
|
board={form.values.board}
|
||||||
{...form.getInputProps("status")}
|
{...form.getInputProps("status")}
|
||||||
label={"Статус"}
|
label={"Статус"}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
<Textarea
|
<Textarea
|
||||||
h={rem(120)}
|
h={rem(120)}
|
||||||
@@ -191,73 +197,78 @@ const Content: FC<Props> = ({ card }) => {
|
|||||||
input: { height: "90%" },
|
input: { height: "90%" },
|
||||||
}}
|
}}
|
||||||
label={"Коментарий"}
|
label={"Коментарий"}
|
||||||
placeholder={"Введите коментарий"}
|
placeholder={isDealsViewer ? "" : "Введите коментарий"}
|
||||||
{...form.getInputProps("comment")}
|
{...form.getInputProps("comment")}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
{project && project?.tags.length > 0 && (
|
{project && project?.tags.length > 0 && (
|
||||||
<CardTagsInput
|
<CardTagsInput
|
||||||
value={cardTags}
|
value={cardTags}
|
||||||
onChange={setCardTags}
|
onChange={setCardTags}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{project && (
|
{project && (
|
||||||
<CardAttributeFields
|
<CardAttributeFields
|
||||||
project={project}
|
project={project}
|
||||||
form={form}
|
form={form}
|
||||||
|
readOnly={isDealsViewer}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</Stack>
|
</Stack>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Flex
|
{!isDealsViewer && (
|
||||||
mt={"md"}
|
|
||||||
gap={rem(10)}
|
|
||||||
align={"center"}
|
|
||||||
justify={"flex-end"}>
|
|
||||||
<Flex
|
<Flex
|
||||||
align={"center"}
|
mt={"md"}
|
||||||
gap={rem(10)}
|
gap={rem(10)}
|
||||||
justify={"center"}>
|
|
||||||
<ButtonCopyControlled
|
|
||||||
onCopyClick={onCopyGuestUrlClick}
|
|
||||||
onCopiedLabel={
|
|
||||||
"Ссылка скопирована в буфер обмена"
|
|
||||||
}
|
|
||||||
copied={clipboard.copied}
|
|
||||||
>
|
|
||||||
Ссылка на редактирование
|
|
||||||
</ButtonCopyControlled>
|
|
||||||
<Flex gap={rem(10)}>
|
|
||||||
<Checkbox
|
|
||||||
label={"Завершена"}
|
|
||||||
{...form.getInputProps("isCompleted", { type: "checkbox" })}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
label={"Удалена"}
|
|
||||||
{...form.getInputProps("isDeleted", { type: "checkbox" })}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
<Divider orientation={"vertical"} />
|
|
||||||
<Group
|
|
||||||
align={"center"}
|
align={"center"}
|
||||||
justify={"center"}>
|
justify={"flex-end"}>
|
||||||
<Button
|
<Flex
|
||||||
color={"red"}
|
align={"center"}
|
||||||
type={"reset"}
|
gap={rem(10)}
|
||||||
disabled={isEqualValues()}
|
justify={"center"}>
|
||||||
onClick={cancelChanges}>
|
<ButtonCopyControlled
|
||||||
Отменить изменения
|
onCopyClick={onCopyGuestUrlClick}
|
||||||
</Button>
|
onCopiedLabel={
|
||||||
<Button
|
"Ссылка скопирована в буфер обмена"
|
||||||
variant={"default"}
|
}
|
||||||
type={"submit"}
|
copied={clipboard.copied}
|
||||||
disabled={isEqualValues()}>
|
>
|
||||||
Сохранить изменения
|
Ссылка на редактирование
|
||||||
</Button>
|
</ButtonCopyControlled>
|
||||||
</Group>
|
<Flex gap={rem(10)}>
|
||||||
</Flex>
|
<Checkbox
|
||||||
|
label={"Завершена"}
|
||||||
|
{...form.getInputProps("isCompleted", { type: "checkbox" })}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={"Удалена"}
|
||||||
|
{...form.getInputProps("isDeleted", { type: "checkbox" })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Divider orientation={"vertical"} />
|
||||||
|
<Group
|
||||||
|
align={"center"}
|
||||||
|
justify={"center"}>
|
||||||
|
<Button
|
||||||
|
color={"red"}
|
||||||
|
type={"reset"}
|
||||||
|
disabled={isEqualValues()}
|
||||||
|
onClick={cancelChanges}>
|
||||||
|
Отменить изменения
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
type={"submit"}
|
||||||
|
disabled={isEqualValues()}>
|
||||||
|
Сохранить изменения
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BoardSchema, BoardService } from "../../../client";
|
import { BoardSchema, BoardService } from "../../../client";
|
||||||
import { useProjectsContext } from "../../../contexts/ProjectsContext.tsx";
|
import { useProjectsContext } from "../../../contexts/ProjectsContext.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store.ts";
|
||||||
|
|
||||||
|
|
||||||
const useBoards = () => {
|
const useBoards = () => {
|
||||||
const { selectedProject } = useProjectsContext();
|
const { selectedProject } = useProjectsContext();
|
||||||
const [boards, setBoards] = useState<BoardSchema[]>([]);
|
const [boards, setBoards] = useState<BoardSchema[]>([]);
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
|
const filterBoards = (boards: BoardSchema[]): BoardSchema[] => {
|
||||||
|
if (isDealsViewer) {
|
||||||
|
const SALES_DEPARTMENT_FF = 5;
|
||||||
|
return boards.filter(board => board.id !== SALES_DEPARTMENT_FF);
|
||||||
|
}
|
||||||
|
return boards;
|
||||||
|
};
|
||||||
|
|
||||||
const refetchBoards = () => {
|
const refetchBoards = () => {
|
||||||
if (!selectedProject) return;
|
if (!selectedProject) return;
|
||||||
@@ -14,7 +25,7 @@ const useBoards = () => {
|
|||||||
projectId: selectedProject.id,
|
projectId: selectedProject.id,
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
setBoards(data.boards);
|
setBoards(filterBoards(data.boards));
|
||||||
})
|
})
|
||||||
.catch(e => console.log(e));
|
.catch(e => console.log(e));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { useDisclosure } from "@mantine/hooks";
|
|||||||
import InlineButton from "../../../components/InlineButton/InlineButton.tsx";
|
import InlineButton from "../../../components/InlineButton/InlineButton.tsx";
|
||||||
import { IconFilter } from "@tabler/icons-react";
|
import { IconFilter } from "@tabler/icons-react";
|
||||||
import BoardSelect from "../../../components/BoardSelect/BoardSelect.tsx";
|
import BoardSelect from "../../../components/BoardSelect/BoardSelect.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: UseFormReturnType<CardsPageState>;
|
form: UseFormReturnType<CardsPageState>;
|
||||||
@@ -18,6 +20,7 @@ type Props = {
|
|||||||
|
|
||||||
const CardsTableFiltersModal = ({ form, projects }: Props) => {
|
const CardsTableFiltersModal = ({ form, projects }: Props) => {
|
||||||
const [opened, { open, close }] = useDisclosure();
|
const [opened, { open, close }] = useDisclosure();
|
||||||
|
const { isDealsViewer } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -60,15 +63,17 @@ const CardsTableFiltersModal = ({ form, projects }: Props) => {
|
|||||||
placeholder={"Выберите маркетплейс"}
|
placeholder={"Выберите маркетплейс"}
|
||||||
{...form.getInputProps("marketplace")}
|
{...form.getInputProps("marketplace")}
|
||||||
/>
|
/>
|
||||||
<ClientSelectNew
|
{!isDealsViewer && (
|
||||||
onClear={() =>
|
<ClientSelectNew
|
||||||
form.setFieldValue("client", null)
|
onClear={() =>
|
||||||
}
|
form.setFieldValue("client", null)
|
||||||
clearable
|
}
|
||||||
searchable
|
clearable
|
||||||
placeholder={"Выберите клиента"}
|
searchable
|
||||||
{...form.getInputProps("client")}
|
placeholder={"Выберите клиента"}
|
||||||
/>
|
{...form.getInputProps("client")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -17,17 +17,22 @@ import ProjectEditDrawer from "../drawers/ProjectEditDrawer/ProjectEditDrawer.ts
|
|||||||
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
|
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
|
||||||
import { DndContextProvider } from "../contexts/DndContext.tsx";
|
import { DndContextProvider } from "../contexts/DndContext.tsx";
|
||||||
import useCardSummaries from "../hooks/useCardSummaries.tsx";
|
import useCardSummaries from "../hooks/useCardSummaries.tsx";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store.ts";
|
||||||
|
|
||||||
export const CardsPage: FC = () => {
|
export const CardsPage: FC = () => {
|
||||||
const { form } = useCardsTableForm();
|
const { form } = useCardsTableForm();
|
||||||
const { dealId } = useParams({ strict: false });
|
const { dealId } = useParams({ strict: false });
|
||||||
const { summaries, fetchSummaries } = useCardSummaries({ full: false });
|
const { summaries, fetchSummaries } = useCardSummaries({ full: false });
|
||||||
|
const authState = useSelector((state: RootState) => state.auth);
|
||||||
|
|
||||||
const [displayMode, setDisplayMode] = useState<DisplayMode>(DisplayMode.BOARD);
|
const [displayMode, setDisplayMode] = useState<DisplayMode>(DisplayMode.BOARD);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchSummaries();
|
if (authState.isAuthorized) {
|
||||||
}, []);
|
fetchSummaries();
|
||||||
|
}
|
||||||
|
}, [authState.isAuthorized]);
|
||||||
|
|
||||||
const tableBody = useMemo(() => {
|
const tableBody = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@@ -38,7 +43,7 @@ export const CardsPage: FC = () => {
|
|||||||
const boardsBody = useMemo(() => {
|
const boardsBody = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<DndContextProvider
|
<DndContextProvider
|
||||||
summariesRaw={summaries}
|
summaries={summaries}
|
||||||
refetchSummaries={fetchSummaries}
|
refetchSummaries={fetchSummaries}
|
||||||
>
|
>
|
||||||
<Boards />
|
<Boards />
|
||||||
|
|||||||
32
src/pages/CardsPage/utils/isDealPaid.ts
Normal file
32
src/pages/CardsPage/utils/isDealPaid.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { CardSchema, CardSummary } from "../../../client";
|
||||||
|
|
||||||
|
const isDealPaid = (deal: CardSummary | CardSchema | undefined): boolean => {
|
||||||
|
if ((!deal?.billRequests || deal?.billRequests?.length === 0) && (!deal?.group?.billRequests || deal?.group?.billRequests?.length === 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deal.billRequests && deal.billRequests?.length !== 0) {
|
||||||
|
for (let i = 0; i < deal.billRequests.length; i++) {
|
||||||
|
if (!deal.billRequests[i].paid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deal.group?.billRequests && deal.group?.billRequests.length !== 0) {
|
||||||
|
for (let i = 0; i < deal.group.billRequests.length; i++) {
|
||||||
|
if (!deal.group.billRequests[i].paid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isDealPaid;
|
||||||
|
|
||||||
|
export const isDealLocked = (deal: CardSummary | CardSchema | undefined): boolean => {
|
||||||
|
return !!(
|
||||||
|
(deal?.billRequests && deal?.billRequests?.length !== 0) ||
|
||||||
|
(deal?.group?.billRequests && deal?.group?.billRequests?.length !== 0)
|
||||||
|
);
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user