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,50 +65,63 @@ const Status = ({ summaries, status, board, dragState, index, refetch }: Props)
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
const header = (
|
||||||
<Droppable
|
<Stack h={"100%"} gap={0} justify={"space-between"}>
|
||||||
isDropDisabled={isDropDisabled}
|
<Stack gap={0}>
|
||||||
droppableId={"status-" + status.id.toString()}
|
<Title
|
||||||
direction={"horizontal"}
|
style={{ textAlign: "center" }}
|
||||||
>
|
size={"h4"}>
|
||||||
{provided => (
|
{status.name}
|
||||||
<div
|
</Title>
|
||||||
{...provided.droppableProps}
|
<Text style={{ textAlign: "center", textWrap: "nowrap" }}>
|
||||||
ref={provided.innerRef}
|
{getDealsText()}
|
||||||
>
|
</Text>
|
||||||
<Draggable
|
</Stack>
|
||||||
draggableId={"status-" + status.id.toString()}
|
<Divider
|
||||||
index={status.ordinalNumber}
|
size={"xl"}
|
||||||
>
|
color={getColumnColor(index)}
|
||||||
{(provided) => (
|
/>
|
||||||
<div
|
</Stack>
|
||||||
{...provided.draggableProps}
|
);
|
||||||
{...provided.dragHandleProps}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
|
|
||||||
className={styles["header-statuses"]}
|
return (
|
||||||
onContextMenu={contextMenu()}
|
<div
|
||||||
>
|
key={index}
|
||||||
<Title
|
ref={el => (divRefs.current[index] = el)}
|
||||||
style={{ textAlign: "center" }}
|
>
|
||||||
size={"h4"}>
|
<Droppable
|
||||||
{status.name}
|
isDropDisabled={isDropDisabled}
|
||||||
</Title>
|
droppableId={"status-" + status.id.toString()}
|
||||||
<Text style={{ textAlign: "center", textWrap: "nowrap" }}>
|
direction={"horizontal"}
|
||||||
{getDealsText()}
|
>
|
||||||
</Text>
|
{provided => (
|
||||||
<Divider
|
<div
|
||||||
size={"xl"}
|
{...provided.droppableProps}
|
||||||
my={10}
|
ref={provided.innerRef}
|
||||||
color={getColumnColor(index)}
|
style={{ height: "100%" }}
|
||||||
/>
|
>
|
||||||
</div>
|
<Draggable
|
||||||
)}
|
draggableId={"status-" + status.id.toString()}
|
||||||
</Draggable>
|
index={status.ordinalNumber}
|
||||||
{provided.placeholder}
|
>
|
||||||
</div>
|
{(provided) => (
|
||||||
)}
|
<div
|
||||||
</Droppable>
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
|
||||||
|
className={styles["header-statuses"]}
|
||||||
|
onContextMenu={contextMenu()}
|
||||||
|
>
|
||||||
|
{header}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</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,19 +68,21 @@ const Statuses = ({
|
|||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
>
|
>
|
||||||
<Flex
|
<EqualHeightsContextProvider statuses={statuses}>
|
||||||
justify={"space-between"}
|
<Flex
|
||||||
direction={"column"}
|
justify={"space-between"}
|
||||||
>
|
direction={"column"}
|
||||||
<Flex className={styles["statuses"]}>
|
>
|
||||||
{selectedBoard &&
|
<Flex className={styles["statuses"]}>
|
||||||
statuses.map(((status: StatusSchema, index: number) => {
|
{selectedBoard &&
|
||||||
return statusDndColumn(status, index);
|
statuses.map(((status: StatusSchema, index: number) => {
|
||||||
}))
|
return statusDndColumn(status, index);
|
||||||
}
|
}))
|
||||||
|
}
|
||||||
|
</Flex>
|
||||||
|
<CardsDndFooter dragState={dragState} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<CardsDndFooter dragState={dragState} />
|
</EqualHeightsContextProvider>
|
||||||
</Flex>
|
|
||||||
</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