feat: added tags for cards, aligned status headers
This commit is contained in:
		@@ -25,7 +25,7 @@
 | 
				
			|||||||
        "@mantine/modals": "^7.11.2",
 | 
					        "@mantine/modals": "^7.11.2",
 | 
				
			||||||
        "@mantine/notifications": "^7.11.2",
 | 
					        "@mantine/notifications": "^7.11.2",
 | 
				
			||||||
        "@reduxjs/toolkit": "^2.2.6",
 | 
					        "@reduxjs/toolkit": "^2.2.6",
 | 
				
			||||||
        "@tabler/icons-react": "^3.11.0",
 | 
					        "@tabler/icons-react": "3.11.0",
 | 
				
			||||||
        "@tanstack/react-query": "^5.51.9",
 | 
					        "@tanstack/react-query": "^5.51.9",
 | 
				
			||||||
        "@tanstack/react-router": "^1.45.6",
 | 
					        "@tanstack/react-router": "^1.45.6",
 | 
				
			||||||
        "@tanstack/router-devtools": "^1.45.6",
 | 
					        "@tanstack/router-devtools": "^1.45.6",
 | 
				
			||||||
@@ -75,10 +75,10 @@
 | 
				
			|||||||
        "postcss": "^8.4.39",
 | 
					        "postcss": "^8.4.39",
 | 
				
			||||||
        "postcss-preset-mantine": "^1.16.0",
 | 
					        "postcss-preset-mantine": "^1.16.0",
 | 
				
			||||||
        "postcss-simple-vars": "^7.0.1",
 | 
					        "postcss-simple-vars": "^7.0.1",
 | 
				
			||||||
        "sass": "^1.77.8",
 | 
					        "sass": "^1.81.1",
 | 
				
			||||||
        "typescript": "^5.5.3",
 | 
					        "typescript": "^5.5.3",
 | 
				
			||||||
        "typescript-eslint": "^7.16.1",
 | 
					        "typescript-eslint": "^7.16.1",
 | 
				
			||||||
        "vite": "^5.3.4",
 | 
					        "vite": "^6.0.1",
 | 
				
			||||||
        "yarn-upgrade-all": "^0.7.2"
 | 
					        "yarn-upgrade-all": "^0.7.2"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "packageManager": "yarn@4.1.0"
 | 
					    "packageManager": "yarn@4.1.0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ export type { BarcodeTemplateUpdateRequest } from './models/BarcodeTemplateUpdat
 | 
				
			|||||||
export type { BarcodeTemplateUpdateResponse } from './models/BarcodeTemplateUpdateResponse';
 | 
					export type { BarcodeTemplateUpdateResponse } from './models/BarcodeTemplateUpdateResponse';
 | 
				
			||||||
export type { BaseAttributeSchema } from './models/BaseAttributeSchema';
 | 
					export type { BaseAttributeSchema } from './models/BaseAttributeSchema';
 | 
				
			||||||
export type { BaseBoardSchema } from './models/BaseBoardSchema';
 | 
					export type { BaseBoardSchema } from './models/BaseBoardSchema';
 | 
				
			||||||
 | 
					export type { BaseCardTagSchema } from './models/BaseCardTagSchema';
 | 
				
			||||||
export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
 | 
					export type { BaseEnumListSchema } from './models/BaseEnumListSchema';
 | 
				
			||||||
export type { BaseEnumSchema } from './models/BaseEnumSchema';
 | 
					export type { BaseEnumSchema } from './models/BaseEnumSchema';
 | 
				
			||||||
export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
 | 
					export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
 | 
				
			||||||
@@ -101,6 +102,7 @@ export type { CardStatusHistorySchema } from './models/CardStatusHistorySchema';
 | 
				
			|||||||
export type { CardSummary } from './models/CardSummary';
 | 
					export type { CardSummary } from './models/CardSummary';
 | 
				
			||||||
export type { CardSummaryReorderRequest } from './models/CardSummaryReorderRequest';
 | 
					export type { CardSummaryReorderRequest } from './models/CardSummaryReorderRequest';
 | 
				
			||||||
export type { CardSummaryResponse } from './models/CardSummaryResponse';
 | 
					export type { CardSummaryResponse } from './models/CardSummaryResponse';
 | 
				
			||||||
 | 
					export type { CardTagSchema } from './models/CardTagSchema';
 | 
				
			||||||
export type { CardUpdateGeneralInfoRequest } from './models/CardUpdateGeneralInfoRequest';
 | 
					export type { CardUpdateGeneralInfoRequest } from './models/CardUpdateGeneralInfoRequest';
 | 
				
			||||||
export type { CardUpdateGeneralInfoResponse } from './models/CardUpdateGeneralInfoResponse';
 | 
					export type { CardUpdateGeneralInfoResponse } from './models/CardUpdateGeneralInfoResponse';
 | 
				
			||||||
export type { CardUpdateProductQuantityRequest } from './models/CardUpdateProductQuantityRequest';
 | 
					export type { CardUpdateProductQuantityRequest } from './models/CardUpdateProductQuantityRequest';
 | 
				
			||||||
@@ -169,6 +171,8 @@ export type { CreateShippingWarehouseRequest } from './models/CreateShippingWare
 | 
				
			|||||||
export type { CreateShippingWarehouseResponse } from './models/CreateShippingWarehouseResponse';
 | 
					export type { CreateShippingWarehouseResponse } from './models/CreateShippingWarehouseResponse';
 | 
				
			||||||
export type { CreateStatusRequest } from './models/CreateStatusRequest';
 | 
					export type { CreateStatusRequest } from './models/CreateStatusRequest';
 | 
				
			||||||
export type { CreateStatusResponse } from './models/CreateStatusResponse';
 | 
					export type { CreateStatusResponse } from './models/CreateStatusResponse';
 | 
				
			||||||
 | 
					export type { CreateTagRequest } from './models/CreateTagRequest';
 | 
				
			||||||
 | 
					export type { CreateTagResponse } from './models/CreateTagResponse';
 | 
				
			||||||
export type { CreateTaskResponse } from './models/CreateTaskResponse';
 | 
					export type { CreateTaskResponse } from './models/CreateTaskResponse';
 | 
				
			||||||
export type { CreateTransactionTagRequest } from './models/CreateTransactionTagRequest';
 | 
					export type { CreateTransactionTagRequest } from './models/CreateTransactionTagRequest';
 | 
				
			||||||
export type { CreateUserRequest } from './models/CreateUserRequest';
 | 
					export type { CreateUserRequest } from './models/CreateUserRequest';
 | 
				
			||||||
@@ -196,6 +200,7 @@ export type { DeleteShippingProductResponse } from './models/DeleteShippingProdu
 | 
				
			|||||||
export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
 | 
					export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWarehouseRequest';
 | 
				
			||||||
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
 | 
					export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
 | 
				
			||||||
export type { DeleteStatusResponse } from './models/DeleteStatusResponse';
 | 
					export type { DeleteStatusResponse } from './models/DeleteStatusResponse';
 | 
				
			||||||
 | 
					export type { DeleteTagResponse } from './models/DeleteTagResponse';
 | 
				
			||||||
export type { DeleteTransactionResponse } from './models/DeleteTransactionResponse';
 | 
					export type { DeleteTransactionResponse } from './models/DeleteTransactionResponse';
 | 
				
			||||||
export type { DeleteTransactionTagResponse } from './models/DeleteTransactionTagResponse';
 | 
					export type { DeleteTransactionTagResponse } from './models/DeleteTransactionTagResponse';
 | 
				
			||||||
export type { DeleteUserRequest } from './models/DeleteUserRequest';
 | 
					export type { DeleteUserRequest } from './models/DeleteUserRequest';
 | 
				
			||||||
@@ -346,6 +351,8 @@ export type { StartPauseByShiftIdResponse } from './models/StartPauseByShiftIdRe
 | 
				
			|||||||
export type { StartPauseByUserIdResponse } from './models/StartPauseByUserIdResponse';
 | 
					export type { StartPauseByUserIdResponse } from './models/StartPauseByUserIdResponse';
 | 
				
			||||||
export type { StartShiftResponse } from './models/StartShiftResponse';
 | 
					export type { StartShiftResponse } from './models/StartShiftResponse';
 | 
				
			||||||
export type { StatusSchema } from './models/StatusSchema';
 | 
					export type { StatusSchema } from './models/StatusSchema';
 | 
				
			||||||
 | 
					export type { SwitchTagRequest } from './models/SwitchTagRequest';
 | 
				
			||||||
 | 
					export type { SwitchTagResponse } from './models/SwitchTagResponse';
 | 
				
			||||||
export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest';
 | 
					export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest';
 | 
				
			||||||
export type { TaskInfoResponse } from './models/TaskInfoResponse';
 | 
					export type { TaskInfoResponse } from './models/TaskInfoResponse';
 | 
				
			||||||
export type { TimeTrackingData } from './models/TimeTrackingData';
 | 
					export type { TimeTrackingData } from './models/TimeTrackingData';
 | 
				
			||||||
@@ -393,6 +400,8 @@ export type { UpdateStatusOrderRequest } from './models/UpdateStatusOrderRequest
 | 
				
			|||||||
export type { UpdateStatusOrderResponse } from './models/UpdateStatusOrderResponse';
 | 
					export type { UpdateStatusOrderResponse } from './models/UpdateStatusOrderResponse';
 | 
				
			||||||
export type { UpdateStatusRequest } from './models/UpdateStatusRequest';
 | 
					export type { UpdateStatusRequest } from './models/UpdateStatusRequest';
 | 
				
			||||||
export type { UpdateStatusResponse } from './models/UpdateStatusResponse';
 | 
					export type { UpdateStatusResponse } from './models/UpdateStatusResponse';
 | 
				
			||||||
 | 
					export type { UpdateTagRequest } from './models/UpdateTagRequest';
 | 
				
			||||||
 | 
					export type { UpdateTagResponse } from './models/UpdateTagResponse';
 | 
				
			||||||
export type { UpdateTimeTrackingRecordRequest } from './models/UpdateTimeTrackingRecordRequest';
 | 
					export type { UpdateTimeTrackingRecordRequest } from './models/UpdateTimeTrackingRecordRequest';
 | 
				
			||||||
export type { UpdateTimeTrackingRecordResponse } from './models/UpdateTimeTrackingRecordResponse';
 | 
					export type { UpdateTimeTrackingRecordResponse } from './models/UpdateTimeTrackingRecordResponse';
 | 
				
			||||||
export type { UpdateTransactionRequest } from './models/UpdateTransactionRequest';
 | 
					export type { UpdateTransactionRequest } from './models/UpdateTransactionRequest';
 | 
				
			||||||
@@ -421,6 +430,7 @@ export { BillingService } from './services/BillingService';
 | 
				
			|||||||
export { BoardService } from './services/BoardService';
 | 
					export { BoardService } from './services/BoardService';
 | 
				
			||||||
export { CardService } from './services/CardService';
 | 
					export { CardService } from './services/CardService';
 | 
				
			||||||
export { CardGroupService } from './services/CardGroupService';
 | 
					export { CardGroupService } from './services/CardGroupService';
 | 
				
			||||||
 | 
					export { CardTagService } from './services/CardTagService';
 | 
				
			||||||
export { ClientService } from './services/ClientService';
 | 
					export { ClientService } from './services/ClientService';
 | 
				
			||||||
export { DepartmentService } from './services/DepartmentService';
 | 
					export { DepartmentService } from './services/DepartmentService';
 | 
				
			||||||
export { MarketplaceService } from './services/MarketplaceService';
 | 
					export { MarketplaceService } from './services/MarketplaceService';
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								src/client/models/BaseCardTagSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/BaseCardTagSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type BaseCardTagSchema = {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    projectId: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,7 +12,7 @@ export type CardGeneralInfoSchema = {
 | 
				
			|||||||
    manager?: (UserSchema | null);
 | 
					    manager?: (UserSchema | null);
 | 
				
			||||||
    boardId: number;
 | 
					    boardId: number;
 | 
				
			||||||
    statusId: number;
 | 
					    statusId: number;
 | 
				
			||||||
    isServicesProfitAccounted: boolean;
 | 
					 | 
				
			||||||
    clientId: (number | null);
 | 
					    clientId: (number | null);
 | 
				
			||||||
 | 
					    tags: Array<string>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import type { CardGroupSchema } from './CardGroupSchema';
 | 
				
			|||||||
import type { CardProductSchema } from './CardProductSchema';
 | 
					import type { CardProductSchema } from './CardProductSchema';
 | 
				
			||||||
import type { CardServiceSchema } from './CardServiceSchema';
 | 
					import type { CardServiceSchema } from './CardServiceSchema';
 | 
				
			||||||
import type { CardStatusHistorySchema } from './CardStatusHistorySchema';
 | 
					import type { CardStatusHistorySchema } from './CardStatusHistorySchema';
 | 
				
			||||||
 | 
					import type { CardTagSchema } from './CardTagSchema';
 | 
				
			||||||
import type { ClientSchema } from './ClientSchema';
 | 
					import type { ClientSchema } from './ClientSchema';
 | 
				
			||||||
import type { PalletSchema } from './PalletSchema';
 | 
					import type { PalletSchema } from './PalletSchema';
 | 
				
			||||||
import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
 | 
					import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema';
 | 
				
			||||||
@@ -39,6 +40,7 @@ export type CardSchema = {
 | 
				
			|||||||
    pallets?: Array<PalletSchema>;
 | 
					    pallets?: Array<PalletSchema>;
 | 
				
			||||||
    boxes?: Array<BoxSchema>;
 | 
					    boxes?: Array<BoxSchema>;
 | 
				
			||||||
    employees?: Array<CardEmployeesSchema>;
 | 
					    employees?: Array<CardEmployeesSchema>;
 | 
				
			||||||
 | 
					    tags?: Array<CardTagSchema>;
 | 
				
			||||||
    attributes: Array<CardAttributeSchema>;
 | 
					    attributes: Array<CardAttributeSchema>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import type { BaseMarketplaceSchema } from './BaseMarketplaceSchema';
 | 
				
			|||||||
import type { BoardSchema } from './BoardSchema';
 | 
					import type { BoardSchema } from './BoardSchema';
 | 
				
			||||||
import type { CardBillRequestSchema } from './CardBillRequestSchema';
 | 
					import type { CardBillRequestSchema } from './CardBillRequestSchema';
 | 
				
			||||||
import type { CardGroupSchema } from './CardGroupSchema';
 | 
					import type { CardGroupSchema } from './CardGroupSchema';
 | 
				
			||||||
 | 
					import type { CardTagSchema } from './CardTagSchema';
 | 
				
			||||||
import type { StatusSchema } from './StatusSchema';
 | 
					import type { StatusSchema } from './StatusSchema';
 | 
				
			||||||
export type CardSummary = {
 | 
					export type CardSummary = {
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
@@ -18,6 +19,7 @@ export type CardSummary = {
 | 
				
			|||||||
    rank: number;
 | 
					    rank: number;
 | 
				
			||||||
    baseMarketplace?: (BaseMarketplaceSchema | null);
 | 
					    baseMarketplace?: (BaseMarketplaceSchema | null);
 | 
				
			||||||
    totalProducts: number;
 | 
					    totalProducts: number;
 | 
				
			||||||
 | 
					    tags: Array<CardTagSchema>;
 | 
				
			||||||
    shipmentWarehouseId: (number | null);
 | 
					    shipmentWarehouseId: (number | null);
 | 
				
			||||||
    shipmentWarehouseName: (string | null);
 | 
					    shipmentWarehouseName: (string | null);
 | 
				
			||||||
    billRequest?: (CardBillRequestSchema | null);
 | 
					    billRequest?: (CardBillRequestSchema | null);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/client/models/CardTagSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/CardTagSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type CardTagSchema = {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    projectId: number;
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/CreateTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/CreateTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { BaseCardTagSchema } from './BaseCardTagSchema';
 | 
				
			||||||
 | 
					export type CreateTagRequest = {
 | 
				
			||||||
 | 
					    tag: BaseCardTagSchema;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/CreateTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/CreateTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type CreateTagResponse = {
 | 
				
			||||||
 | 
					    ok: boolean;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/DeleteTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/DeleteTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type DeleteTagResponse = {
 | 
				
			||||||
 | 
					    ok: boolean;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -3,12 +3,14 @@
 | 
				
			|||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
import type { AttributeSchema } from './AttributeSchema';
 | 
					import type { AttributeSchema } from './AttributeSchema';
 | 
				
			||||||
 | 
					import type { CardTagSchema } from './CardTagSchema';
 | 
				
			||||||
import type { ModuleSchema } from './ModuleSchema';
 | 
					import type { ModuleSchema } from './ModuleSchema';
 | 
				
			||||||
export type FullProjectSchema = {
 | 
					export type FullProjectSchema = {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    attributes: Array<AttributeSchema>;
 | 
					    attributes: Array<AttributeSchema>;
 | 
				
			||||||
    modules: Array<ModuleSchema>;
 | 
					    modules: Array<ModuleSchema>;
 | 
				
			||||||
 | 
					    tags: Array<CardTagSchema>;
 | 
				
			||||||
    boardsCount: number;
 | 
					    boardsCount: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ export type GetProfitChartDataRequest = {
 | 
				
			|||||||
    projectId: number;
 | 
					    projectId: number;
 | 
				
			||||||
    boardId: number;
 | 
					    boardId: number;
 | 
				
			||||||
    cardStatusId: number;
 | 
					    cardStatusId: number;
 | 
				
			||||||
 | 
					    cardTagId: number;
 | 
				
			||||||
    managerId: number;
 | 
					    managerId: number;
 | 
				
			||||||
    expenseTagId: number;
 | 
					    expenseTagId: number;
 | 
				
			||||||
    incomeTagId: number;
 | 
					    incomeTagId: number;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ export type GetProfitTableDataRequest = {
 | 
				
			|||||||
    projectId: number;
 | 
					    projectId: number;
 | 
				
			||||||
    boardId: number;
 | 
					    boardId: number;
 | 
				
			||||||
    cardStatusId: number;
 | 
					    cardStatusId: number;
 | 
				
			||||||
 | 
					    cardTagId: number;
 | 
				
			||||||
    managerId: number;
 | 
					    managerId: number;
 | 
				
			||||||
    expenseTagId: number;
 | 
					    expenseTagId: number;
 | 
				
			||||||
    incomeTagId: number;
 | 
					    incomeTagId: number;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,5 +4,6 @@
 | 
				
			|||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
export type ProductsAndServicesGeneralInfoSchema = {
 | 
					export type ProductsAndServicesGeneralInfoSchema = {
 | 
				
			||||||
    shippingWarehouse?: (string | null);
 | 
					    shippingWarehouse?: (string | null);
 | 
				
			||||||
 | 
					    isServicesProfitAccounted: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,4 +2,4 @@
 | 
				
			|||||||
/* istanbul ignore file */
 | 
					/* istanbul ignore file */
 | 
				
			||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
export type ProfitTableGroupBy = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
 | 
					export type ProfitTableGroupBy = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,13 @@
 | 
				
			|||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
import type { AttributeSchema } from './AttributeSchema';
 | 
					import type { AttributeSchema } from './AttributeSchema';
 | 
				
			||||||
 | 
					import type { CardTagSchema } from './CardTagSchema';
 | 
				
			||||||
import type { ModuleSchema } from './ModuleSchema';
 | 
					import type { ModuleSchema } from './ModuleSchema';
 | 
				
			||||||
export type ProjectSchema = {
 | 
					export type ProjectSchema = {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    attributes: Array<AttributeSchema>;
 | 
					    attributes: Array<AttributeSchema>;
 | 
				
			||||||
    modules: Array<ModuleSchema>;
 | 
					    modules: Array<ModuleSchema>;
 | 
				
			||||||
 | 
					    tags: Array<CardTagSchema>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/client/models/SwitchTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/SwitchTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type SwitchTagRequest = {
 | 
				
			||||||
 | 
					    tagId: number;
 | 
				
			||||||
 | 
					    cardId?: (number | null);
 | 
				
			||||||
 | 
					    groupId?: (number | null);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/SwitchTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/SwitchTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type SwitchTagResponse = {
 | 
				
			||||||
 | 
					    ok: boolean;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/UpdateTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/UpdateTagRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { CardTagSchema } from './CardTagSchema';
 | 
				
			||||||
 | 
					export type UpdateTagRequest = {
 | 
				
			||||||
 | 
					    tag: CardTagSchema;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/client/models/UpdateTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/UpdateTagResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type UpdateTagResponse = {
 | 
				
			||||||
 | 
					    ok: boolean;
 | 
				
			||||||
 | 
					    message: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										97
									
								
								src/client/services/CardTagService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/client/services/CardTagService.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					import type { CreateTagRequest } from '../models/CreateTagRequest';
 | 
				
			||||||
 | 
					import type { CreateTagResponse } from '../models/CreateTagResponse';
 | 
				
			||||||
 | 
					import type { DeleteTagResponse } from '../models/DeleteTagResponse';
 | 
				
			||||||
 | 
					import type { SwitchTagRequest } from '../models/SwitchTagRequest';
 | 
				
			||||||
 | 
					import type { SwitchTagResponse } from '../models/SwitchTagResponse';
 | 
				
			||||||
 | 
					import type { UpdateTagRequest } from '../models/UpdateTagRequest';
 | 
				
			||||||
 | 
					import type { UpdateTagResponse } from '../models/UpdateTagResponse';
 | 
				
			||||||
 | 
					import type { CancelablePromise } from '../core/CancelablePromise';
 | 
				
			||||||
 | 
					import { OpenAPI } from '../core/OpenAPI';
 | 
				
			||||||
 | 
					import { request as __request } from '../core/request';
 | 
				
			||||||
 | 
					export class CardTagService {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create Tag
 | 
				
			||||||
 | 
					     * @returns CreateTagResponse Successful Response
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static createTag({
 | 
				
			||||||
 | 
					        requestBody,
 | 
				
			||||||
 | 
					    }: {
 | 
				
			||||||
 | 
					        requestBody: CreateTagRequest,
 | 
				
			||||||
 | 
					    }): CancelablePromise<CreateTagResponse> {
 | 
				
			||||||
 | 
					        return __request(OpenAPI, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            url: '/card-tag/',
 | 
				
			||||||
 | 
					            body: requestBody,
 | 
				
			||||||
 | 
					            mediaType: 'application/json',
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Validation Error`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update Tag
 | 
				
			||||||
 | 
					     * @returns UpdateTagResponse Successful Response
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static updateTag({
 | 
				
			||||||
 | 
					        requestBody,
 | 
				
			||||||
 | 
					    }: {
 | 
				
			||||||
 | 
					        requestBody: UpdateTagRequest,
 | 
				
			||||||
 | 
					    }): CancelablePromise<UpdateTagResponse> {
 | 
				
			||||||
 | 
					        return __request(OpenAPI, {
 | 
				
			||||||
 | 
					            method: 'PATCH',
 | 
				
			||||||
 | 
					            url: '/card-tag/',
 | 
				
			||||||
 | 
					            body: requestBody,
 | 
				
			||||||
 | 
					            mediaType: 'application/json',
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Validation Error`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Delete Tag
 | 
				
			||||||
 | 
					     * @returns DeleteTagResponse Successful Response
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static deleteTag({
 | 
				
			||||||
 | 
					        cardTagId,
 | 
				
			||||||
 | 
					    }: {
 | 
				
			||||||
 | 
					        cardTagId: number,
 | 
				
			||||||
 | 
					    }): CancelablePromise<DeleteTagResponse> {
 | 
				
			||||||
 | 
					        return __request(OpenAPI, {
 | 
				
			||||||
 | 
					            method: 'DELETE',
 | 
				
			||||||
 | 
					            url: '/card-tag/{card_tag_id}',
 | 
				
			||||||
 | 
					            path: {
 | 
				
			||||||
 | 
					                'card_tag_id': cardTagId,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Validation Error`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Switch Tag
 | 
				
			||||||
 | 
					     * @returns SwitchTagResponse Successful Response
 | 
				
			||||||
 | 
					     * @throws ApiError
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static switchTag({
 | 
				
			||||||
 | 
					        requestBody,
 | 
				
			||||||
 | 
					    }: {
 | 
				
			||||||
 | 
					        requestBody: SwitchTagRequest,
 | 
				
			||||||
 | 
					    }): CancelablePromise<SwitchTagResponse> {
 | 
				
			||||||
 | 
					        return __request(OpenAPI, {
 | 
				
			||||||
 | 
					            method: 'POST',
 | 
				
			||||||
 | 
					            url: '/card-tag/switch',
 | 
				
			||||||
 | 
					            body: requestBody,
 | 
				
			||||||
 | 
					            mediaType: 'application/json',
 | 
				
			||||||
 | 
					            errors: {
 | 
				
			||||||
 | 
					                422: `Validation Error`,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,6 +7,7 @@ import { useDebouncedValue } from "@mantine/hooks";
 | 
				
			|||||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
					import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
				
			||||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import CardTags from "../CardTags/CardTags.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    cards: CardSummary[];
 | 
					    cards: CardSummary[];
 | 
				
			||||||
@@ -75,6 +76,7 @@ export const CardGroupView: FC<Props> = ({ cards, group }) => {
 | 
				
			|||||||
                    />
 | 
					                    />
 | 
				
			||||||
                ))}
 | 
					                ))}
 | 
				
			||||||
            </Flex>
 | 
					            </Flex>
 | 
				
			||||||
 | 
					            <CardTags groupId={group.id} tags={cards[0].tags}/>
 | 
				
			||||||
            {isServicesAndProductsIncluded && (
 | 
					            {isServicesAndProductsIncluded && (
 | 
				
			||||||
                <Flex
 | 
					                <Flex
 | 
				
			||||||
                    p={rem(10)}
 | 
					                    p={rem(10)}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import { useContextMenu } from "mantine-contextmenu";
 | 
				
			|||||||
import useCardSummaryState from "./useCardSummaryState.tsx";
 | 
					import useCardSummaryState from "./useCardSummaryState.tsx";
 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
				
			||||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					import CardTags from "../CardTags/CardTags.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    cardSummary: CardSummary;
 | 
					    cardSummary: CardSummary;
 | 
				
			||||||
@@ -26,7 +27,7 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
 | 
				
			|||||||
    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
					    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
				
			||||||
    const isClientIncluded = isModuleInProject(Modules.CLIENTS, selectedProject);
 | 
					    const isClientIncluded = isModuleInProject(Modules.CLIENTS, selectedProject);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onDealSummaryClick = () => {
 | 
					    const onCardSummaryClick = () => {
 | 
				
			||||||
        CardService.getCardById({ cardId: cardSummary.id }).then(card => {
 | 
					        CardService.getCardById({ cardId: cardSummary.id }).then(card => {
 | 
				
			||||||
            setSelectedCard(card);
 | 
					            setSelectedCard(card);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -59,7 +60,7 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
 | 
				
			|||||||
                    icon: <IconTrash />,
 | 
					                    icon: <IconTrash />,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            ])}
 | 
					            ])}
 | 
				
			||||||
            onClick={() => onDealSummaryClick()}
 | 
					            onClick={() => onCardSummaryClick()}
 | 
				
			||||||
            className={styles["container"]}
 | 
					            className={styles["container"]}
 | 
				
			||||||
            style={{ backgroundColor: color }}
 | 
					            style={{ backgroundColor: color }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
@@ -95,6 +96,9 @@ const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
 | 
				
			|||||||
                        </Text>
 | 
					                        </Text>
 | 
				
			||||||
                    </Flex>
 | 
					                    </Flex>
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
 | 
					                {!cardSummary.group?.id && (
 | 
				
			||||||
 | 
					                    <CardTags cardId={cardSummary.id} tags={cardSummary.tags}/>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
                <Flex align={"center"} justify={"space-between"}>
 | 
					                <Flex align={"center"} justify={"space-between"}>
 | 
				
			||||||
                    <Flex align={"center"} gap={rem(5)}>
 | 
					                    <Flex align={"center"} gap={rem(5)}>
 | 
				
			||||||
                        <CopyButton value={cardSummary.id.toString()}>
 | 
					                        <CopyButton value={cardSummary.id.toString()}>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/components/Dnd/Cards/CardTags/CardTags.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/Dnd/Cards/CardTags/CardTags.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					.add-tag-button {
 | 
				
			||||||
 | 
					    background-color: var(--mantine-color-dark-6);
 | 
				
			||||||
 | 
					    border: 1px gray dashed;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    width: 1.5rem;
 | 
				
			||||||
 | 
					    height: 1.5rem;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.add-tag-button:hover {
 | 
				
			||||||
 | 
					    border-color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.add-tag-button-icon {
 | 
				
			||||||
 | 
					    color: gray;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.add-tag-button-icon:hover {
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										98
									
								
								src/components/Dnd/Cards/CardTags/CardTags.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/components/Dnd/Cards/CardTags/CardTags.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import { Center, Checkbox, Group, Menu, Pill, rem, Stack, Text } from "@mantine/core";
 | 
				
			||||||
 | 
					import { CardTagSchema, CardTagService } from "../../../../client";
 | 
				
			||||||
 | 
					import { IconPlus } from "@tabler/icons-react";
 | 
				
			||||||
 | 
					import styles from "./CardTags.module.css";
 | 
				
			||||||
 | 
					import classNames from "classnames";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = {
 | 
				
			||||||
 | 
					    cardId?: number;
 | 
				
			||||||
 | 
					    groupId?: number;
 | 
				
			||||||
 | 
					    tags: CardTagSchema[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTags = ({ tags, cardId, groupId }: Props) => {
 | 
				
			||||||
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
 | 
					    const { refetchCards } = useCardPageContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (selectedProject?.tags.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onTagClick = (tag: CardTagSchema, event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
 | 
				
			||||||
 | 
					        event.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CardTagService.switchTag({
 | 
				
			||||||
 | 
					            requestBody: {
 | 
				
			||||||
 | 
					                cardId: cardId ?? null,
 | 
				
			||||||
 | 
					                groupId: groupId ?? null,
 | 
				
			||||||
 | 
					                tagId: tag.id,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) notifications.error({ message });
 | 
				
			||||||
 | 
					                refetchCards();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const addTagButton = (
 | 
				
			||||||
 | 
					        <Menu withArrow>
 | 
				
			||||||
 | 
					            <Menu.Target>
 | 
				
			||||||
 | 
					                <button
 | 
				
			||||||
 | 
					                    onClick={e => e.stopPropagation()}
 | 
				
			||||||
 | 
					                    className={classNames(styles["add-tag-button"])}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <Center>
 | 
				
			||||||
 | 
					                        <IconPlus
 | 
				
			||||||
 | 
					                            size={"1.2em"}
 | 
				
			||||||
 | 
					                            className={classNames(styles["add-tag-button-icon"])}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </Center>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </Menu.Target>
 | 
				
			||||||
 | 
					            <Menu.Dropdown>
 | 
				
			||||||
 | 
					                <Stack
 | 
				
			||||||
 | 
					                    p={"xs"}
 | 
				
			||||||
 | 
					                    gap={"sm"}
 | 
				
			||||||
 | 
					                    onClick={e => e.stopPropagation()}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    {selectedProject?.tags.map(tag => (
 | 
				
			||||||
 | 
					                        <Group key={tag.id} wrap={"nowrap"}>
 | 
				
			||||||
 | 
					                            <Checkbox
 | 
				
			||||||
 | 
					                                checked={!!tags.find(cardTag => cardTag.id === tag.id)}
 | 
				
			||||||
 | 
					                                onClick={(event) => onTagClick(tag, event)}
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                            <Text>{tag.name}</Text>
 | 
				
			||||||
 | 
					                        </Group>
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                </Stack>
 | 
				
			||||||
 | 
					            </Menu.Dropdown>
 | 
				
			||||||
 | 
					        </Menu>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const pills = tags.map(tag => {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <Pill
 | 
				
			||||||
 | 
					                key={tag.id}
 | 
				
			||||||
 | 
					                style={{
 | 
				
			||||||
 | 
					                    color: "gray",
 | 
				
			||||||
 | 
					                    backgroundColor: "var(--mantine-color-dark-6)",
 | 
				
			||||||
 | 
					                    border: "1px solid gray",
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                {tag.name}
 | 
				
			||||||
 | 
					            </Pill>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Group gap={rem(4)}>
 | 
				
			||||||
 | 
					            {addTagButton}
 | 
				
			||||||
 | 
					            {pills}
 | 
				
			||||||
 | 
					        </Group>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CardTags;
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import styles from "../../../../pages/CardsPage/ui/CardsPage.module.css";
 | 
					import styles from "../../../../pages/CardsPage/ui/CardsPage.module.css";
 | 
				
			||||||
import { Divider, Text, Title } from "@mantine/core";
 | 
					import { Divider, Stack, Text, Title } from "@mantine/core";
 | 
				
			||||||
import getColumnColor from "../../Cards/CardsDndColumn/utils/getColumnColor.ts";
 | 
					import getColumnColor from "../../Cards/CardsDndColumn/utils/getColumnColor.ts";
 | 
				
			||||||
import { BoardSchema, CardSummary, StatusSchema } from "../../../../client";
 | 
					import { BoardSchema, CardSummary, StatusSchema } from "../../../../client";
 | 
				
			||||||
import { getPluralForm } from "../../../../shared/lib/utils.ts";
 | 
					import { getPluralForm } from "../../../../shared/lib/utils.ts";
 | 
				
			||||||
@@ -10,6 +10,7 @@ import { useContextMenu } from "mantine-contextmenu";
 | 
				
			|||||||
import { IconEdit, IconTrash } from "@tabler/icons-react";
 | 
					import { IconEdit, IconTrash } from "@tabler/icons-react";
 | 
				
			||||||
import useStatus from "./hooks/useStatus.tsx";
 | 
					import useStatus from "./hooks/useStatus.tsx";
 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../modules/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import { useEqualHeightsContext } from "./contexts/EqualHeightContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
@@ -25,6 +26,10 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
 | 
				
			|||||||
    const isDropDisabled = dragState === DragState.DRAG_CARD;
 | 
					    const isDropDisabled = dragState === DragState.DRAG_CARD;
 | 
				
			||||||
    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, board?.project);
 | 
					    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, board?.project);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					        divRefs,
 | 
				
			||||||
 | 
					    } = useEqualHeightsContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
        onEditStatusClick,
 | 
					        onEditStatusClick,
 | 
				
			||||||
        onDeleteStatusClick,
 | 
					        onDeleteStatusClick,
 | 
				
			||||||
@@ -60,7 +65,30 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const header = (
 | 
				
			||||||
 | 
					        <Stack h={"100%"} gap={0} justify={"space-between"}>
 | 
				
			||||||
 | 
					            <Stack gap={0}>
 | 
				
			||||||
 | 
					                <Title
 | 
				
			||||||
 | 
					                    style={{ textAlign: "center" }}
 | 
				
			||||||
 | 
					                    size={"h4"}>
 | 
				
			||||||
 | 
					                    {status.name}
 | 
				
			||||||
 | 
					                </Title>
 | 
				
			||||||
 | 
					                <Text style={{ textAlign: "center", textWrap: "nowrap" }}>
 | 
				
			||||||
 | 
					                    {getDealsText()}
 | 
				
			||||||
 | 
					                </Text>
 | 
				
			||||||
 | 
					            </Stack>
 | 
				
			||||||
 | 
					            <Divider
 | 
				
			||||||
 | 
					                size={"xl"}
 | 
				
			||||||
 | 
					                color={getColumnColor(index)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </Stack>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					            key={index}
 | 
				
			||||||
 | 
					            ref={el => (divRefs.current[index] = el)}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
            <Droppable
 | 
					            <Droppable
 | 
				
			||||||
                isDropDisabled={isDropDisabled}
 | 
					                isDropDisabled={isDropDisabled}
 | 
				
			||||||
                droppableId={"status-" + status.id.toString()}
 | 
					                droppableId={"status-" + status.id.toString()}
 | 
				
			||||||
@@ -70,6 +98,7 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
 | 
				
			|||||||
                    <div
 | 
					                    <div
 | 
				
			||||||
                        {...provided.droppableProps}
 | 
					                        {...provided.droppableProps}
 | 
				
			||||||
                        ref={provided.innerRef}
 | 
					                        ref={provided.innerRef}
 | 
				
			||||||
 | 
					                        style={{ height: "100%" }}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        <Draggable
 | 
					                        <Draggable
 | 
				
			||||||
                            draggableId={"status-" + status.id.toString()}
 | 
					                            draggableId={"status-" + status.id.toString()}
 | 
				
			||||||
@@ -84,19 +113,7 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
 | 
				
			|||||||
                                    className={styles["header-statuses"]}
 | 
					                                    className={styles["header-statuses"]}
 | 
				
			||||||
                                    onContextMenu={contextMenu()}
 | 
					                                    onContextMenu={contextMenu()}
 | 
				
			||||||
                                >
 | 
					                                >
 | 
				
			||||||
                                <Title
 | 
					                                    {header}
 | 
				
			||||||
                                    style={{ textAlign: "center" }}
 | 
					 | 
				
			||||||
                                    size={"h4"}>
 | 
					 | 
				
			||||||
                                    {status.name}
 | 
					 | 
				
			||||||
                                </Title>
 | 
					 | 
				
			||||||
                                <Text style={{ textAlign: "center", textWrap: "nowrap" }}>
 | 
					 | 
				
			||||||
                                    {getDealsText()}
 | 
					 | 
				
			||||||
                                </Text>
 | 
					 | 
				
			||||||
                                <Divider
 | 
					 | 
				
			||||||
                                    size={"xl"}
 | 
					 | 
				
			||||||
                                    my={10}
 | 
					 | 
				
			||||||
                                    color={getColumnColor(index)}
 | 
					 | 
				
			||||||
                                />
 | 
					 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            )}
 | 
					                            )}
 | 
				
			||||||
                        </Draggable>
 | 
					                        </Draggable>
 | 
				
			||||||
@@ -104,6 +121,7 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
            </Droppable>
 | 
					            </Droppable>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import React, { createContext, FC, useContext, useEffect, useRef, useState } from "react";
 | 
				
			||||||
 | 
					import { StatusSchema } from "../../../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EqualHeightsContextState = {
 | 
				
			||||||
 | 
					    divRefs: React.MutableRefObject<(HTMLDivElement | null)[]>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EqualHeightsContext = createContext<EqualHeightsContextState | undefined>(
 | 
				
			||||||
 | 
					    undefined,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EqualHeightsContextProps = {
 | 
				
			||||||
 | 
					    statuses: StatusSchema[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useEqualHeightsContextState = ({ statuses }: EqualHeightsContextProps) => {
 | 
				
			||||||
 | 
					    const divRefs = useRef<(HTMLDivElement | null)[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const updateHeights = () => {
 | 
				
			||||||
 | 
					        divRefs.current.forEach(div => {
 | 
				
			||||||
 | 
					            if (div) {
 | 
				
			||||||
 | 
					                div.style.minHeight = "auto";
 | 
				
			||||||
 | 
					                div.style.height = "auto";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const heights = divRefs.current.map(div => div?.offsetHeight || 0);
 | 
				
			||||||
 | 
					        const newMaxHeight = Math.max(...heights);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        divRefs.current.forEach(div => {
 | 
				
			||||||
 | 
					            if (div) {
 | 
				
			||||||
 | 
					                div.style.minHeight = newMaxHeight + "px";
 | 
				
			||||||
 | 
					                div.style.height = newMaxHeight + "px";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [windowWidth, setWindowWidth] = useState(window.innerWidth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        const handleResize = () => {
 | 
				
			||||||
 | 
					            setWindowWidth(window.innerWidth);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        window.addEventListener("resize", handleResize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return () => {
 | 
				
			||||||
 | 
					            window.removeEventListener("resize", handleResize);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        updateHeights();
 | 
				
			||||||
 | 
					    }, [windowWidth, divRefs.current.length, statuses]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        divRefs,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type EqualHeightsContextProviderProps = {
 | 
				
			||||||
 | 
					    children: React.ReactNode;
 | 
				
			||||||
 | 
					} & EqualHeightsContextProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EqualHeightsContextProvider: FC<EqualHeightsContextProviderProps> = ({ children, statuses }) => {
 | 
				
			||||||
 | 
					    const state = useEqualHeightsContextState({ statuses });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <EqualHeightsContext.Provider value={state}>
 | 
				
			||||||
 | 
					            {children}
 | 
				
			||||||
 | 
					        </EqualHeightsContext.Provider>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useEqualHeightsContext = () => {
 | 
				
			||||||
 | 
					    const context = useContext(EqualHeightsContext);
 | 
				
			||||||
 | 
					    if (!context) {
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            "useEqualHeightsContext must be used within a EqualHeightsContextProvider",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return context;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -6,6 +6,7 @@ import { Flex, rem, Stack } from "@mantine/core";
 | 
				
			|||||||
import { DragDropContext } from "@hello-pangea/dnd";
 | 
					import { DragDropContext } from "@hello-pangea/dnd";
 | 
				
			||||||
import useDnd from "../../../../pages/CardsPage/hooks/useDnd.tsx";
 | 
					import useDnd from "../../../../pages/CardsPage/hooks/useDnd.tsx";
 | 
				
			||||||
import Status from "../Status/Status.tsx";
 | 
					import Status from "../Status/Status.tsx";
 | 
				
			||||||
 | 
					import { EqualHeightsContextProvider } from "../Status/contexts/EqualHeightContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
@@ -67,6 +68,7 @@ const Statuses = ({
 | 
				
			|||||||
            onDragStart={onDragStart}
 | 
					            onDragStart={onDragStart}
 | 
				
			||||||
            onDragEnd={onDragEnd}
 | 
					            onDragEnd={onDragEnd}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					            <EqualHeightsContextProvider statuses={statuses}>
 | 
				
			||||||
                <Flex
 | 
					                <Flex
 | 
				
			||||||
                    justify={"space-between"}
 | 
					                    justify={"space-between"}
 | 
				
			||||||
                    direction={"column"}
 | 
					                    direction={"column"}
 | 
				
			||||||
@@ -80,6 +82,7 @@ const Statuses = ({
 | 
				
			|||||||
                    </Flex>
 | 
					                    </Flex>
 | 
				
			||||||
                    <CardsDndFooter dragState={dragState} />
 | 
					                    <CardsDndFooter dragState={dragState} />
 | 
				
			||||||
                </Flex>
 | 
					                </Flex>
 | 
				
			||||||
 | 
					            </EqualHeightsContextProvider>
 | 
				
			||||||
        </DragDropContext>
 | 
					        </DragDropContext>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								src/components/Selects/CardTagSelect/CardTagSelect.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/components/Selects/CardTagSelect/CardTagSelect.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import { FC } from "react";
 | 
				
			||||||
 | 
					import { CardTagSchema, ProjectSchema } from "../../../client";
 | 
				
			||||||
 | 
					import ObjectSelect, { ObjectSelectProps } from "../../ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OtherProps = {
 | 
				
			||||||
 | 
					    project: ProjectSchema | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SelectProps = Omit<ObjectSelectProps<CardTagSchema | null>, "data" | "getLabelFn" | "getValueFn">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = OtherProps & SelectProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTagSelect: FC<Props> = ({ project, ...props }) => {
 | 
				
			||||||
 | 
					    const onClear = () => props.onChange(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <ObjectSelect
 | 
				
			||||||
 | 
					            data={project?.tags ?? []}
 | 
				
			||||||
 | 
					            searchable
 | 
				
			||||||
 | 
					            placeholder={"Выберите тег"}
 | 
				
			||||||
 | 
					            onClear={onClear}
 | 
				
			||||||
 | 
					            {...props}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export default CardTagSelect;
 | 
				
			||||||
@@ -38,6 +38,7 @@ import StatusModal from "../pages/CardsPage/modals/StatusModal/StatusModal.tsx";
 | 
				
			|||||||
import AttributeModal from "../pages/AdminPage/tabs/Attributes/modals/AttributeModal.tsx";
 | 
					import AttributeModal from "../pages/AdminPage/tabs/Attributes/modals/AttributeModal.tsx";
 | 
				
			||||||
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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const modals = {
 | 
					export const modals = {
 | 
				
			||||||
    enterDeadline: EnterDeadlineModal,
 | 
					    enterDeadline: EnterDeadlineModal,
 | 
				
			||||||
@@ -77,4 +78,5 @@ export const modals = {
 | 
				
			|||||||
    statusModal: StatusModal,
 | 
					    statusModal: StatusModal,
 | 
				
			||||||
    attributeModal: AttributeModal,
 | 
					    attributeModal: AttributeModal,
 | 
				
			||||||
    createProjectModal: CreateProjectModal,
 | 
					    createProjectModal: CreateProjectModal,
 | 
				
			||||||
 | 
					    cardTagModal: CardTagModal,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { TagsInput, TagsInputProps } from "@mantine/core";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = Omit<TagsInputProps, "data">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTagsInput = (props: Props) => {
 | 
				
			||||||
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <TagsInput
 | 
				
			||||||
 | 
					            {...props}
 | 
				
			||||||
 | 
					            data={selectedProject?.tags.map(tag => tag.name)}
 | 
				
			||||||
 | 
					            label={"Теги"}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CardTagsInput;
 | 
				
			||||||
@@ -42,7 +42,7 @@ const CardEditDrawer: FC = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const getTabPanel = (value: string, component: ReactNode) => {
 | 
					    const getTabPanel = (value: string, component: ReactNode) => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <Tabs.Panel value={value}>
 | 
					            <Tabs.Panel key={value} value={value}>
 | 
				
			||||||
                <motion.div
 | 
					                <motion.div
 | 
				
			||||||
                    initial={{ opacity: 0 }}
 | 
					                    initial={{ opacity: 0 }}
 | 
				
			||||||
                    animate={{ opacity: 1 }}
 | 
					                    animate={{ opacity: 1 }}
 | 
				
			||||||
@@ -61,6 +61,7 @@ const CardEditDrawer: FC = () => {
 | 
				
			|||||||
    const getTabs = () => {
 | 
					    const getTabs = () => {
 | 
				
			||||||
        const moduleTabs = modules.map(module => (
 | 
					        const moduleTabs = modules.map(module => (
 | 
				
			||||||
            <Tabs.Tab
 | 
					            <Tabs.Tab
 | 
				
			||||||
 | 
					                key={module.info.key}
 | 
				
			||||||
                value={module.info.key}
 | 
					                value={module.info.key}
 | 
				
			||||||
                leftSection={module.info.icon}
 | 
					                leftSection={module.info.icon}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,10 @@ import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyCon
 | 
				
			|||||||
import { useClipboard } from "@mantine/hooks";
 | 
					import { useClipboard } from "@mantine/hooks";
 | 
				
			||||||
import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
 | 
					import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
 | 
				
			||||||
import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
 | 
					import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
 | 
				
			||||||
import CardStatusSelect from "../../../../../../components/DealStatusSelect/CardStatusSelect.tsx";
 | 
					import CardStatusSelect from "../../../../../../components/CardStatusSelect/CardStatusSelect.tsx";
 | 
				
			||||||
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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    card: CardSchema;
 | 
					    card: CardSchema;
 | 
				
			||||||
@@ -41,6 +42,7 @@ const Content: FC<Props> = ({ card }) => {
 | 
				
			|||||||
    const clipboard = useClipboard();
 | 
					    const clipboard = useClipboard();
 | 
				
			||||||
    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 getInitialValues = (card: CardSchema): CardGeneralFormType => {
 | 
					    const getInitialValues = (card: CardSchema): CardGeneralFormType => {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
@@ -82,6 +84,7 @@ const Content: FC<Props> = ({ card }) => {
 | 
				
			|||||||
                    boardId: values.board.id,
 | 
					                    boardId: values.board.id,
 | 
				
			||||||
                    clientId: values.client?.id ?? null,
 | 
					                    clientId: values.client?.id ?? null,
 | 
				
			||||||
                    attributes,
 | 
					                    attributes,
 | 
				
			||||||
 | 
					                    tags: cardTags,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        }).then(({ ok, message }) => {
 | 
					        }).then(({ ok, message }) => {
 | 
				
			||||||
@@ -125,6 +128,20 @@ const Content: FC<Props> = ({ card }) => {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cancelChanges = () => {
 | 
				
			||||||
 | 
					        form.reset();
 | 
				
			||||||
 | 
					        setCardTags(card.tags?.map(tag => tag.name) ?? []);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isEqualValues = () => {
 | 
				
			||||||
 | 
					        const initialCardTagsSet = new Set(card.tags?.map(tag => tag.name) ?? []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const tagsEqual = initialCardTagsSet.size === cardTags.length &&
 | 
				
			||||||
 | 
					            cardTags.every(element => initialCardTagsSet.has(element));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return isEqual(initialValues, form.values) && tagsEqual;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <form onSubmit={form.onSubmit(values => handleSubmit(values))}>
 | 
					        <form onSubmit={form.onSubmit(values => handleSubmit(values))}>
 | 
				
			||||||
            <Flex
 | 
					            <Flex
 | 
				
			||||||
@@ -177,6 +194,12 @@ const Content: FC<Props> = ({ card }) => {
 | 
				
			|||||||
                                    placeholder={"Введите коментарий"}
 | 
					                                    placeholder={"Введите коментарий"}
 | 
				
			||||||
                                    {...form.getInputProps("comment")}
 | 
					                                    {...form.getInputProps("comment")}
 | 
				
			||||||
                                />
 | 
					                                />
 | 
				
			||||||
 | 
					                                {project && project?.tags.length > 0 && (
 | 
				
			||||||
 | 
					                                    <CardTagsInput
 | 
				
			||||||
 | 
					                                        value={cardTags}
 | 
				
			||||||
 | 
					                                        onChange={setCardTags}
 | 
				
			||||||
 | 
					                                    />
 | 
				
			||||||
 | 
					                                )}
 | 
				
			||||||
                                {project && (
 | 
					                                {project && (
 | 
				
			||||||
                                    <CardAttributeFields
 | 
					                                    <CardAttributeFields
 | 
				
			||||||
                                        project={project}
 | 
					                                        project={project}
 | 
				
			||||||
@@ -223,14 +246,14 @@ const Content: FC<Props> = ({ card }) => {
 | 
				
			|||||||
                        <Button
 | 
					                        <Button
 | 
				
			||||||
                            color={"red"}
 | 
					                            color={"red"}
 | 
				
			||||||
                            type={"reset"}
 | 
					                            type={"reset"}
 | 
				
			||||||
                            disabled={isEqual(initialValues, form.values)}
 | 
					                            disabled={isEqualValues()}
 | 
				
			||||||
                            onClick={() => form.reset()}>
 | 
					                            onClick={cancelChanges}>
 | 
				
			||||||
                            Отменить изменения
 | 
					                            Отменить изменения
 | 
				
			||||||
                        </Button>
 | 
					                        </Button>
 | 
				
			||||||
                        <Button
 | 
					                        <Button
 | 
				
			||||||
                            variant={"default"}
 | 
					                            variant={"default"}
 | 
				
			||||||
                            type={"submit"}
 | 
					                            type={"submit"}
 | 
				
			||||||
                            disabled={isEqual(initialValues, form.values)}>
 | 
					                            disabled={isEqualValues()}>
 | 
				
			||||||
                            Сохранить изменения
 | 
					                            Сохранить изменения
 | 
				
			||||||
                        </Button>
 | 
					                        </Button>
 | 
				
			||||||
                    </Group>
 | 
					                    </Group>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { Box, Drawer, rem, Tabs } from "@mantine/core";
 | 
					import { Box, Drawer, rem, Tabs } from "@mantine/core";
 | 
				
			||||||
import { IconHexagons, IconSettings, IconSubtask } from "@tabler/icons-react";
 | 
					import { IconHexagons, IconSettings, IconSubtask, IconTags } from "@tabler/icons-react";
 | 
				
			||||||
import { ReactNode } from "react";
 | 
					import { ReactNode } from "react";
 | 
				
			||||||
import { motion } from "framer-motion";
 | 
					import { motion } from "framer-motion";
 | 
				
			||||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
					import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
				
			||||||
import General from "./tabs/General/General.tsx";
 | 
					import General from "./tabs/General/General.tsx";
 | 
				
			||||||
import Attributes from "./tabs/Attributes/Attributes.tsx";
 | 
					import Attributes from "./tabs/Attributes/Attributes.tsx";
 | 
				
			||||||
import Modules from "./tabs/Modules/Modules.tsx";
 | 
					import Modules from "./tabs/Modules/Modules.tsx";
 | 
				
			||||||
 | 
					import Tags from "./tabs/Tags/Tags.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectEditDrawer = () => {
 | 
					const ProjectEditDrawer = () => {
 | 
				
			||||||
@@ -67,11 +68,17 @@ const ProjectEditDrawer = () => {
 | 
				
			|||||||
                        leftSection={<IconSubtask />}>
 | 
					                        leftSection={<IconSubtask />}>
 | 
				
			||||||
                        Атрибуты
 | 
					                        Атрибуты
 | 
				
			||||||
                    </Tabs.Tab>
 | 
					                    </Tabs.Tab>
 | 
				
			||||||
 | 
					                    <Tabs.Tab
 | 
				
			||||||
 | 
					                        value={"tags"}
 | 
				
			||||||
 | 
					                        leftSection={<IconTags />}>
 | 
				
			||||||
 | 
					                        Теги
 | 
				
			||||||
 | 
					                    </Tabs.Tab>
 | 
				
			||||||
                </Tabs.List>
 | 
					                </Tabs.List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {getTabPanel("general", <General />)}
 | 
					                {getTabPanel("general", <General />)}
 | 
				
			||||||
                {getTabPanel("attributes", <Attributes />)}
 | 
					                {getTabPanel("attributes", <Attributes />)}
 | 
				
			||||||
                {getTabPanel("modules", <Modules />)}
 | 
					                {getTabPanel("modules", <Modules />)}
 | 
				
			||||||
 | 
					                {getTabPanel("tags", <Tags />)}
 | 
				
			||||||
            </Tabs>
 | 
					            </Tabs>
 | 
				
			||||||
        </Drawer>
 | 
					        </Drawer>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					import { ActionIcon, Flex, Group, rem, Stack, Tooltip } from "@mantine/core";
 | 
				
			||||||
 | 
					import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
 | 
				
			||||||
 | 
					import tagsTableColumns from "./hooks/tagsTableColumns.tsx";
 | 
				
			||||||
 | 
					import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
 | 
				
			||||||
 | 
					import { CardTagSchema } from "../../../../../../client";
 | 
				
			||||||
 | 
					import { MRT_TableOptions } from "mantine-react-table";
 | 
				
			||||||
 | 
					import useTags from "./hooks/useTags.tsx";
 | 
				
			||||||
 | 
					import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Tags = () => {
 | 
				
			||||||
 | 
					    const columns = tagsTableColumns();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					        project,
 | 
				
			||||||
 | 
					        onDeleteClick,
 | 
				
			||||||
 | 
					        onChangeClick,
 | 
				
			||||||
 | 
					        onCreateClick,
 | 
				
			||||||
 | 
					    } = useTags();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Stack gap={rem(10)}>
 | 
				
			||||||
 | 
					            <Group>
 | 
				
			||||||
 | 
					                <InlineButton onClick={onCreateClick}>
 | 
				
			||||||
 | 
					                    <IconPlus />
 | 
				
			||||||
 | 
					                    Создать
 | 
				
			||||||
 | 
					                </InlineButton>
 | 
				
			||||||
 | 
					            </Group>
 | 
				
			||||||
 | 
					            <BaseTable
 | 
				
			||||||
 | 
					                data={project?.tags}
 | 
				
			||||||
 | 
					                columns={columns}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                restProps={
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        enableSorting: false,
 | 
				
			||||||
 | 
					                        enableColumnActions: false,
 | 
				
			||||||
 | 
					                        enableRowActions: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        renderRowActions: ({ row }) => {
 | 
				
			||||||
 | 
					                            return (
 | 
				
			||||||
 | 
					                                <Flex gap="md">
 | 
				
			||||||
 | 
					                                    <Tooltip label="Удалить">
 | 
				
			||||||
 | 
					                                        <ActionIcon
 | 
				
			||||||
 | 
					                                            onClick={() => onDeleteClick(row.original)}
 | 
				
			||||||
 | 
					                                            variant={"default"}>
 | 
				
			||||||
 | 
					                                            <IconTrash />
 | 
				
			||||||
 | 
					                                        </ActionIcon>
 | 
				
			||||||
 | 
					                                    </Tooltip>
 | 
				
			||||||
 | 
					                                    <Tooltip label="Редактировать">
 | 
				
			||||||
 | 
					                                        <ActionIcon
 | 
				
			||||||
 | 
					                                            onClick={() => onChangeClick(row.original)}
 | 
				
			||||||
 | 
					                                            variant={"default"}>
 | 
				
			||||||
 | 
					                                            <IconEdit />
 | 
				
			||||||
 | 
					                                        </ActionIcon>
 | 
				
			||||||
 | 
					                                    </Tooltip>
 | 
				
			||||||
 | 
					                                </Flex>
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    } as MRT_TableOptions<CardTagSchema>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </Stack>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Tags;
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { useMemo } from "react";
 | 
				
			||||||
 | 
					import { MRT_ColumnDef } from "mantine-react-table";
 | 
				
			||||||
 | 
					import { CardTagSchema } from "../../../../../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useTagsTableColumns = () => {
 | 
				
			||||||
 | 
					    return useMemo<MRT_ColumnDef<CardTagSchema>[]>(
 | 
				
			||||||
 | 
					        () => [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                header: "Название",
 | 
				
			||||||
 | 
					                accessorKey: "name",
 | 
				
			||||||
 | 
					                size: 1000,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useTagsTableColumns;
 | 
				
			||||||
@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    CancelablePromise,
 | 
				
			||||||
 | 
					    CardTagSchema,
 | 
				
			||||||
 | 
					    CardTagService,
 | 
				
			||||||
 | 
					    CreateTagResponse,
 | 
				
			||||||
 | 
					    DeleteTagResponse,
 | 
				
			||||||
 | 
					    UpdateTagResponse,
 | 
				
			||||||
 | 
					} from "../../../../../../../client";
 | 
				
			||||||
 | 
					import { notifications } from "../../../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import { modals } from "@mantine/modals";
 | 
				
			||||||
 | 
					import { Text } from "@mantine/core";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					import { useCardPageContext } from "../../../../../contexts/CardPageContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useTags = () => {
 | 
				
			||||||
 | 
					    const { selectedProject: project, refetchProjects } = useProjectsContext();
 | 
				
			||||||
 | 
					    const { refetchCards } = useCardPageContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const processResponse = (
 | 
				
			||||||
 | 
					        response: CancelablePromise<DeleteTagResponse | UpdateTagResponse | CreateTagResponse>,
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					        response
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) {
 | 
				
			||||||
 | 
					                    notifications.error({ message });
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                refetchProjects();
 | 
				
			||||||
 | 
					                refetchCards();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onDelete = (tag: CardTagSchema) => {
 | 
				
			||||||
 | 
					        const response = CardTagService.deleteTag({
 | 
				
			||||||
 | 
					            cardTagId: tag.id,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        processResponse(response);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onDeleteClick = (tag: CardTagSchema) => {
 | 
				
			||||||
 | 
					        modals.openConfirmModal({
 | 
				
			||||||
 | 
					            title: "Удаление тега",
 | 
				
			||||||
 | 
					            children: (
 | 
				
			||||||
 | 
					                <Text size="sm">
 | 
				
			||||||
 | 
					                    Вы уверены что хотите удалить тег {tag.name}
 | 
				
			||||||
 | 
					                </Text>
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            labels: { confirm: "Да", cancel: "Нет" },
 | 
				
			||||||
 | 
					            confirmProps: { color: "red" },
 | 
				
			||||||
 | 
					            onConfirm: () => onDelete(tag),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onChange = (tag: CardTagSchema) => {
 | 
				
			||||||
 | 
					        const response = CardTagService.updateTag({
 | 
				
			||||||
 | 
					            requestBody: { tag },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        processResponse(response);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onChangeClick = (tag: CardTagSchema) => {
 | 
				
			||||||
 | 
					        modals.openContextModal({
 | 
				
			||||||
 | 
					            modal: "cardTagModal",
 | 
				
			||||||
 | 
					            innerProps: {
 | 
				
			||||||
 | 
					                element: tag,
 | 
				
			||||||
 | 
					                onChange,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            withCloseButton: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onCreate = (tag: CardTagSchema) => {
 | 
				
			||||||
 | 
					        if (!project) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const response = CardTagService.createTag({
 | 
				
			||||||
 | 
					            requestBody: {
 | 
				
			||||||
 | 
					                tag: {
 | 
				
			||||||
 | 
					                    name: tag.name,
 | 
				
			||||||
 | 
					                    projectId: project.id,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        processResponse(response);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onCreateClick = () => {
 | 
				
			||||||
 | 
					        modals.openContextModal({
 | 
				
			||||||
 | 
					            modal: "cardTagModal",
 | 
				
			||||||
 | 
					            innerProps: {
 | 
				
			||||||
 | 
					                onCreate,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            withCloseButton: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        project,
 | 
				
			||||||
 | 
					        onDeleteClick,
 | 
				
			||||||
 | 
					        onChangeClick,
 | 
				
			||||||
 | 
					        onCreateClick,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useTags;
 | 
				
			||||||
@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					import { ContextModalProps } from "@mantine/modals";
 | 
				
			||||||
 | 
					import BaseFormModal, {
 | 
				
			||||||
 | 
					    CreateEditFormProps,
 | 
				
			||||||
 | 
					} from "../../../../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
 | 
				
			||||||
 | 
					import { CardTagSchema } from "../../../../../../../client";
 | 
				
			||||||
 | 
					import { useForm } from "@mantine/form";
 | 
				
			||||||
 | 
					import { TextInput } from "@mantine/core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = CreateEditFormProps<CardTagSchema>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTagModal = ({
 | 
				
			||||||
 | 
					                          context,
 | 
				
			||||||
 | 
					                          id,
 | 
				
			||||||
 | 
					                          innerProps,
 | 
				
			||||||
 | 
					                      }: ContextModalProps<Props>) => {
 | 
				
			||||||
 | 
					    const isEditing = "element" in innerProps;
 | 
				
			||||||
 | 
					    const initialValue: Partial<CardTagSchema> = isEditing
 | 
				
			||||||
 | 
					        ? innerProps.element
 | 
				
			||||||
 | 
					        : {
 | 
				
			||||||
 | 
					            name: "",
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const form = useForm<Partial<CardTagSchema>>({
 | 
				
			||||||
 | 
					        initialValues: initialValue,
 | 
				
			||||||
 | 
					        validate: {
 | 
				
			||||||
 | 
					            name: name => !name && "Необходимо указать название тега",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <BaseFormModal
 | 
				
			||||||
 | 
					            form={form}
 | 
				
			||||||
 | 
					            closeOnSubmit
 | 
				
			||||||
 | 
					            onClose={() => context.closeContextModal(id)}
 | 
				
			||||||
 | 
					            {...innerProps}>
 | 
				
			||||||
 | 
					            <BaseFormModal.Body>
 | 
				
			||||||
 | 
					                <TextInput
 | 
				
			||||||
 | 
					                    label={"Название"}
 | 
				
			||||||
 | 
					                    placeholder={"Введите название тега"}
 | 
				
			||||||
 | 
					                    {...form.getInputProps("name")}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </BaseFormModal.Body>
 | 
				
			||||||
 | 
					        </BaseFormModal>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CardTagModal;
 | 
				
			||||||
@@ -3,7 +3,7 @@ import { Flex, Modal, NumberInput, rem } from "@mantine/core";
 | 
				
			|||||||
import { UseFormReturnType } from "@mantine/form";
 | 
					import { UseFormReturnType } from "@mantine/form";
 | 
				
			||||||
import { CardsPageState } from "../hooks/useCardsPageState.tsx";
 | 
					import { CardsPageState } from "../hooks/useCardsPageState.tsx";
 | 
				
			||||||
import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
					import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
import CardStatusSelect from "../../../components/DealStatusSelect/CardStatusSelect.tsx";
 | 
					import CardStatusSelect from "../../../components/CardStatusSelect/CardStatusSelect.tsx";
 | 
				
			||||||
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
					import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
				
			||||||
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
 | 
					import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
 | 
				
			||||||
import { useDisclosure } from "@mantine/hooks";
 | 
					import { useDisclosure } from "@mantine/hooks";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@
 | 
				
			|||||||
    align-items: stretch;
 | 
					    align-items: stretch;
 | 
				
			||||||
    text-align: center;
 | 
					    text-align: center;
 | 
				
			||||||
    flex-direction: column;
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.delete {
 | 
					.delete {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,15 @@ import useAllTransactionTagsList from "../../../AdminPage/hooks/useAllTransactio
 | 
				
			|||||||
type IsIncome = {
 | 
					type IsIncome = {
 | 
				
			||||||
    isIncome: boolean;
 | 
					    isIncome: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = Omit<
 | 
					type Props = Omit<
 | 
				
			||||||
    ObjectSelectProps<TransactionTagSchema | null>,
 | 
					    ObjectSelectProps<TransactionTagSchema | null>,
 | 
				
			||||||
    "data" | "getValueFn" | "getLabelFn"
 | 
					    "data" | "getValueFn" | "getLabelFn"
 | 
				
			||||||
> & IsIncome;
 | 
					> & IsIncome;
 | 
				
			||||||
const TransactionTagSelect: FC<Props> = props => {
 | 
					
 | 
				
			||||||
 | 
					const TransactionTagSelect: FC<Props> = ({ isIncome, ...props }) => {
 | 
				
			||||||
    let { objects: tags } = useAllTransactionTagsList();
 | 
					    let { objects: tags } = useAllTransactionTagsList();
 | 
				
			||||||
    tags = tags.filter(tag => tag.isIncome === props.isIncome);
 | 
					    tags = tags.filter(tag => tag.isIncome === isIncome);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ObjectSelect
 | 
					        <ObjectSelect
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import { Checkbox, CheckboxProps, Divider, Stack, Text } from "@mantine/core";
 | 
				
			|||||||
import ClientSelectNew from "../../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
 | 
					import ClientSelectNew from "../../../../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    BaseMarketplaceSchema,
 | 
					    BaseMarketplaceSchema,
 | 
				
			||||||
    BoardSchema,
 | 
					    BoardSchema, CardTagSchema,
 | 
				
			||||||
    ClientSchema,
 | 
					    ClientSchema,
 | 
				
			||||||
    ProjectSchema,
 | 
					    ProjectSchema,
 | 
				
			||||||
    StatusSchema,
 | 
					    StatusSchema,
 | 
				
			||||||
@@ -13,12 +13,14 @@ import {
 | 
				
			|||||||
import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
					import { ObjectSelectProps } from "../../../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
import BaseMarketplaceSelect
 | 
					import BaseMarketplaceSelect
 | 
				
			||||||
    from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
					    from "../../../../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
				
			||||||
import CardStatusSelect from "../../../../../../components/DealStatusSelect/CardStatusSelect.tsx";
 | 
					import CardStatusSelect from "../../../../../../components/CardStatusSelect/CardStatusSelect.tsx";
 | 
				
			||||||
import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
 | 
					import { ProfitTableSegmentedControl } from "../ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
 | 
				
			||||||
import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx";
 | 
					import ManagerSelect from "../../../../../../components/ManagerSelect/ManagerSelect.tsx";
 | 
				
			||||||
import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx";
 | 
					import TransactionTagSelect from "../../../../components/ExpenseTagSelect/TransactionTagSelect.tsx";
 | 
				
			||||||
import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
 | 
					import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
 | 
				
			||||||
import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
 | 
					import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
 | 
				
			||||||
 | 
					import CardTagSelect from "../../../../../../components/Selects/CardTagSelect/CardTagSelect.tsx";
 | 
				
			||||||
 | 
					import { useEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SelectProps<T> = Omit<
 | 
					type SelectProps<T> = Omit<
 | 
				
			||||||
@@ -41,6 +43,8 @@ type FiltersProps = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    statusSelectProps?: Omit<ObjectSelectProps<StatusSchema | null>, "data">;
 | 
					    statusSelectProps?: Omit<ObjectSelectProps<StatusSchema | null>, "data">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cardTagSelectProps?: Omit<ObjectSelectProps<CardTagSchema | null>, "data">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    managerSelectProps?: SelectProps<UserSchema | null | undefined>;
 | 
					    managerSelectProps?: SelectProps<UserSchema | null | undefined>;
 | 
				
			||||||
    onManagerClear?: () => void;
 | 
					    onManagerClear?: () => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,6 +82,21 @@ export const Filters = (props: FiltersProps) => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (props.boardSelectProps?.onClear) {
 | 
				
			||||||
 | 
					            props.boardSelectProps.onClear();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (props.cardTagSelectProps && !props.projectSelectProps?.value) {
 | 
				
			||||||
 | 
					            props.cardTagSelectProps.onChange(null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [props.projectSelectProps?.value]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (props.statusSelectProps?.onClear) {
 | 
				
			||||||
 | 
					            props.statusSelectProps.onClear();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [props.boardSelectProps?.value]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Stack mb={"lg"}>
 | 
					        <Stack mb={"lg"}>
 | 
				
			||||||
            <Divider />
 | 
					            <Divider />
 | 
				
			||||||
@@ -104,6 +123,7 @@ export const Filters = (props: FiltersProps) => {
 | 
				
			|||||||
                    project={props.projectSelectProps?.value ?? null}
 | 
					                    project={props.projectSelectProps?.value ?? null}
 | 
				
			||||||
                    {...props.boardSelectProps}
 | 
					                    {...props.boardSelectProps}
 | 
				
			||||||
                    clearable
 | 
					                    clearable
 | 
				
			||||||
 | 
					                    disabled={!props.projectSelectProps?.value}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            {props.statusSelectProps &&
 | 
					            {props.statusSelectProps &&
 | 
				
			||||||
@@ -111,6 +131,16 @@ export const Filters = (props: FiltersProps) => {
 | 
				
			|||||||
                    board={props.boardSelectProps?.value ?? null}
 | 
					                    board={props.boardSelectProps?.value ?? null}
 | 
				
			||||||
                    {...props.statusSelectProps}
 | 
					                    {...props.statusSelectProps}
 | 
				
			||||||
                    clearable
 | 
					                    clearable
 | 
				
			||||||
 | 
					                    disabled={!props.boardSelectProps?.value}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            {props.cardTagSelectProps &&
 | 
				
			||||||
 | 
					                <CardTagSelect
 | 
				
			||||||
 | 
					                    project={props.projectSelectProps?.value ?? null}
 | 
				
			||||||
 | 
					                    {...props.cardTagSelectProps}
 | 
				
			||||||
 | 
					                    clearable
 | 
				
			||||||
 | 
					                    searchable
 | 
				
			||||||
 | 
					                    disabled={!props.projectSelectProps?.value}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            {props.clientSelectProps &&
 | 
					            {props.clientSelectProps &&
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ export const useProfitTableColumns = ({ groupTableBy, statuses }: Props) => {
 | 
				
			|||||||
        [GroupStatisticsTable.BY_MARKETPLACES]: "Маркетплейс",
 | 
					        [GroupStatisticsTable.BY_MARKETPLACES]: "Маркетплейс",
 | 
				
			||||||
        [GroupStatisticsTable.BY_WAREHOUSES]: "Склад отгрузки",
 | 
					        [GroupStatisticsTable.BY_WAREHOUSES]: "Склад отгрузки",
 | 
				
			||||||
        [GroupStatisticsTable.BY_MANAGERS]: "Менеджер",
 | 
					        [GroupStatisticsTable.BY_MANAGERS]: "Менеджер",
 | 
				
			||||||
 | 
					        [GroupStatisticsTable.BY_TAGS]: "Тег",
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const getConditionalColumns = (): MRT_ColumnDef<ProfitTableDataItem>[] => {
 | 
					    const getConditionalColumns = (): MRT_ColumnDef<ProfitTableDataItem>[] => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ export enum GroupStatisticsTable {
 | 
				
			|||||||
    BY_WAREHOUSES,
 | 
					    BY_WAREHOUSES,
 | 
				
			||||||
    BY_MARKETPLACES,
 | 
					    BY_MARKETPLACES,
 | 
				
			||||||
    BY_MANAGERS,
 | 
					    BY_MANAGERS,
 | 
				
			||||||
 | 
					    BY_TAGS,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ControlProps = Omit<SegmentedControlProps, "data">;
 | 
					type ControlProps = Omit<SegmentedControlProps, "data">;
 | 
				
			||||||
@@ -22,7 +23,7 @@ type OtherProps = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type Props = ControlProps & OtherProps;
 | 
					type Props = ControlProps & OtherProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ProfitTableSegmentedControl: FC<Props> = props => {
 | 
					export const ProfitTableSegmentedControl: FC<Props> = ({ selectedBoard, selectedProject, ...props}) => {
 | 
				
			||||||
    const data: (string | SegmentedControlItem)[] = [
 | 
					    const data: (string | SegmentedControlItem)[] = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            label: "По датам",
 | 
					            label: "По датам",
 | 
				
			||||||
@@ -39,12 +40,17 @@ export const ProfitTableSegmentedControl: FC<Props> = props => {
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            label: "По доскам",
 | 
					            label: "По доскам",
 | 
				
			||||||
            value: GroupStatisticsTable.BY_BOARDS.toString(),
 | 
					            value: GroupStatisticsTable.BY_BOARDS.toString(),
 | 
				
			||||||
            disabled: !props.selectedProject,
 | 
					            disabled: !selectedProject,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            label: "По статусам",
 | 
					            label: "По статусам",
 | 
				
			||||||
            value: GroupStatisticsTable.BY_STATUSES.toString(),
 | 
					            value: GroupStatisticsTable.BY_STATUSES.toString(),
 | 
				
			||||||
            disabled: !props.selectedBoard,
 | 
					            disabled: !selectedBoard,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            label: "По тегам",
 | 
				
			||||||
 | 
					            value: GroupStatisticsTable.BY_TAGS.toString(),
 | 
				
			||||||
 | 
					            disabled: !selectedProject,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            label: "По складам отгрузки",
 | 
					            label: "По складам отгрузки",
 | 
				
			||||||
@@ -67,15 +73,15 @@ export const ProfitTableSegmentedControl: FC<Props> = props => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (props.value === GroupStatisticsTable.BY_STATUSES.toString()) {
 | 
					        if (props.value === GroupStatisticsTable.BY_STATUSES.toString()) {
 | 
				
			||||||
            if (!props.selectedProject) {
 | 
					            if (!selectedProject) {
 | 
				
			||||||
                setGrouping(GroupStatisticsTable.BY_PROJECTS);
 | 
					                setGrouping(GroupStatisticsTable.BY_PROJECTS);
 | 
				
			||||||
            } else if (!props.selectedBoard) {
 | 
					            } else if (!selectedBoard) {
 | 
				
			||||||
                setGrouping(GroupStatisticsTable.BY_BOARDS);
 | 
					                setGrouping(GroupStatisticsTable.BY_BOARDS);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (props.value === GroupStatisticsTable.BY_BOARDS.toString() && !props.selectedProject) {
 | 
					        } else if (props.value === GroupStatisticsTable.BY_BOARDS.toString() && !selectedProject) {
 | 
				
			||||||
            setGrouping(GroupStatisticsTable.BY_PROJECTS);
 | 
					            setGrouping(GroupStatisticsTable.BY_PROJECTS);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [props.selectedBoard, props.selectedProject]);
 | 
					    }, [selectedBoard, selectedProject]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <SegmentedControl
 | 
					        <SegmentedControl
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,10 +29,11 @@ const useProfitTabContextState = () => {
 | 
				
			|||||||
            project: null,
 | 
					            project: null,
 | 
				
			||||||
            board: null,
 | 
					            board: null,
 | 
				
			||||||
            status: null,
 | 
					            status: null,
 | 
				
			||||||
 | 
					            cardTag: null,
 | 
				
			||||||
            manager: null,
 | 
					            manager: null,
 | 
				
			||||||
            expenseTag: null,
 | 
					            expenseTag: null,
 | 
				
			||||||
            incomeTag: null,
 | 
					            incomeTag: null,
 | 
				
			||||||
            isCompletedOnly: true,
 | 
					            isCompletedOnly: false,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    const [isChartLoading, setIsChartLoading] = useState(false);
 | 
					    const [isChartLoading, setIsChartLoading] = useState(false);
 | 
				
			||||||
@@ -53,6 +54,7 @@ const useProfitTabContextState = () => {
 | 
				
			|||||||
            projectId: form.values.project?.id ?? -1,
 | 
					            projectId: form.values.project?.id ?? -1,
 | 
				
			||||||
            boardId: form.values.board?.id ?? -1,
 | 
					            boardId: form.values.board?.id ?? -1,
 | 
				
			||||||
            cardStatusId: form.values.status?.id ?? -1,
 | 
					            cardStatusId: form.values.status?.id ?? -1,
 | 
				
			||||||
 | 
					            cardTagId: form.values.cardTag?.id ?? -1,
 | 
				
			||||||
            managerId: form.values.manager?.id ?? -1,
 | 
					            managerId: form.values.manager?.id ?? -1,
 | 
				
			||||||
            expenseTagId: form.values.expenseTag?.id ?? -1,
 | 
					            expenseTagId: form.values.expenseTag?.id ?? -1,
 | 
				
			||||||
            incomeTagId: form.values.incomeTag?.id ?? -1,
 | 
					            incomeTagId: form.values.incomeTag?.id ?? -1,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ export const ProfitFiltersModal = ({ form }: Props) => {
 | 
				
			|||||||
                    projectSelectProps={form.getInputProps("project")}
 | 
					                    projectSelectProps={form.getInputProps("project")}
 | 
				
			||||||
                    boardSelectProps={form.getInputProps("board")}
 | 
					                    boardSelectProps={form.getInputProps("board")}
 | 
				
			||||||
                    statusSelectProps={form.getInputProps("status")}
 | 
					                    statusSelectProps={form.getInputProps("status")}
 | 
				
			||||||
 | 
					                    cardTagSelectProps={form.getInputProps("cardTag")}
 | 
				
			||||||
                    managerSelectProps={form.getInputProps("manager")}
 | 
					                    managerSelectProps={form.getInputProps("manager")}
 | 
				
			||||||
                    onManagerClear={() => form.setFieldValue("manager", null)}
 | 
					                    onManagerClear={() => form.setFieldValue("manager", null)}
 | 
				
			||||||
                    isCompletedOnlyCheckboxProps={form.getInputProps("isCompletedOnly", { type: "checkbox" })}
 | 
					                    isCompletedOnlyCheckboxProps={form.getInputProps("isCompletedOnly", { type: "checkbox" })}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import {
 | 
				
			|||||||
    GroupStatisticsTable,
 | 
					    GroupStatisticsTable,
 | 
				
			||||||
} from "../tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
 | 
					} from "../tabs/ProfitTab/components/ProfitTableSegmentedControl/ProfitTableSegmentedControl.tsx";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    BaseMarketplaceSchema, BoardSchema,
 | 
					    BaseMarketplaceSchema, BoardSchema, CardTagSchema,
 | 
				
			||||||
    ClientSchema,
 | 
					    ClientSchema,
 | 
				
			||||||
    ProjectSchema,
 | 
					    ProjectSchema,
 | 
				
			||||||
    StatusSchema,
 | 
					    StatusSchema,
 | 
				
			||||||
@@ -18,6 +18,7 @@ export interface FormFilters {
 | 
				
			|||||||
    project: ProjectSchema | null;
 | 
					    project: ProjectSchema | null;
 | 
				
			||||||
    board: BoardSchema | null;
 | 
					    board: BoardSchema | null;
 | 
				
			||||||
    status: StatusSchema | null;
 | 
					    status: StatusSchema | null;
 | 
				
			||||||
 | 
					    cardTag: CardTagSchema | null;
 | 
				
			||||||
    manager: UserSchema | null;
 | 
					    manager: UserSchema | null;
 | 
				
			||||||
    isCompletedOnly: boolean;
 | 
					    isCompletedOnly: boolean;
 | 
				
			||||||
    expenseTag: TransactionTagSchema | null;
 | 
					    expenseTag: TransactionTagSchema | null;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user