diff --git a/package.json b/package.json index f7524e6..160813a 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "react-imask": "^7.6.1", "react-redux": "^9.1.2", "react-to-print": "^2.15.1", + "react-virtuoso": "^4.12.6", "reactflow": "^11.11.4", "recharts": "^2.13.3", "zod": "^3.23.8" diff --git a/src/client/index.ts b/src/client/index.ts index a1df743..f10d6ba 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -31,6 +31,7 @@ export type { BaseCardTagSchema } from './models/BaseCardTagSchema'; export type { BaseEnumListSchema } from './models/BaseEnumListSchema'; export type { BaseEnumSchema } from './models/BaseEnumSchema'; export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema'; +export type { BaseMessageSchema } from './models/BaseMessageSchema'; export type { BaseProjectSchema } from './models/BaseProjectSchema'; export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema'; export type { BaseStatusSchema } from './models/BaseStatusSchema'; @@ -39,6 +40,7 @@ export type { BillPaymentStatus } from './models/BillPaymentStatus'; export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest'; export type { BoardSchema } from './models/BoardSchema'; export type { Body_parse_deals_excel } from './models/Body_parse_deals_excel'; +export type { Body_send_messages_with_files } from './models/Body_send_messages_with_files'; export type { Body_upload_passport_image } from './models/Body_upload_passport_image'; export type { Body_upload_product_barcode_image } from './models/Body_upload_product_barcode_image'; export type { Body_upload_product_image } from './models/Body_upload_product_image'; @@ -114,6 +116,7 @@ export type { CardUpdateServiceQuantityRequest } from './models/CardUpdateServic export type { CardUpdateServiceQuantityResponse } from './models/CardUpdateServiceQuantityResponse'; export type { CardUpdateServiceRequest } from './models/CardUpdateServiceRequest'; export type { CardUpdateServiceResponse } from './models/CardUpdateServiceResponse'; +export type { ChatSchema } from './models/ChatSchema'; export type { CityBreakdownFromExcelSchema } from './models/CityBreakdownFromExcelSchema'; export type { ClientCreateRequest } from './models/ClientCreateRequest'; export type { ClientCreateResponse } from './models/ClientCreateResponse'; @@ -142,6 +145,8 @@ export type { CreateCardBillResponse } from './models/CreateCardBillResponse'; export type { CreateCardGroupRequest } from './models/CreateCardGroupRequest'; export type { CreateCardsFromExcelRequest } from './models/CreateCardsFromExcelRequest'; export type { CreateCardsFromExcelResponse } from './models/CreateCardsFromExcelResponse'; +export type { CreateChatRequest } from './models/CreateChatRequest'; +export type { CreateChatResponse } from './models/CreateChatResponse'; export type { CreateDepartmentRequest } from './models/CreateDepartmentRequest'; export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse'; export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest'; @@ -185,6 +190,7 @@ export type { DeleteDepartmentResponse } from './models/DeleteDepartmentResponse export type { DeleteDepartmentSectionResponse } from './models/DeleteDepartmentSectionResponse'; export type { DeleteMarketplaceRequest } from './models/DeleteMarketplaceRequest'; export type { DeleteMarketplaceResponse } from './models/DeleteMarketplaceResponse'; +export type { DeleteMessageResponse } from './models/DeleteMessageResponse'; export type { DeletePalletResponse } from './models/DeletePalletResponse'; export type { DeletePaymentRecordRequest } from './models/DeletePaymentRecordRequest'; export type { DeletePaymentRecordResponse } from './models/DeletePaymentRecordResponse'; @@ -211,6 +217,9 @@ export type { DepartmentSchema } from './models/DepartmentSchema'; export type { DepartmentSectionBaseSchema } from './models/DepartmentSectionBaseSchema'; export type { DepartmentSectionBriefSchema } from './models/DepartmentSectionBriefSchema'; export type { DepartmentSectionSchema } from './models/DepartmentSectionSchema'; +export type { EditMessageRequest } from './models/EditMessageRequest'; +export type { EditMessageResponse } from './models/EditMessageResponse'; +export type { EditMessageSchema } from './models/EditMessageSchema'; export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse'; export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse'; export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse'; @@ -243,11 +252,15 @@ export type { GetBoardsResponse } from './models/GetBoardsResponse'; export type { GetCardBillById } from './models/GetCardBillById'; export type { GetCardProductsBarcodesPdfRequest } from './models/GetCardProductsBarcodesPdfRequest'; export type { GetCardProductsBarcodesPdfResponse } from './models/GetCardProductsBarcodesPdfResponse'; +export type { GetChatRequest } from './models/GetChatRequest'; +export type { GetChatResponse } from './models/GetChatResponse'; export type { GetClientMarketplacesRequest } from './models/GetClientMarketplacesRequest'; export type { GetClientMarketplacesResponse } from './models/GetClientMarketplacesResponse'; export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectionsResponse'; export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse'; export type { GetManagersResponse } from './models/GetManagersResponse'; +export type { GetMessagesRequest } from './models/GetMessagesRequest'; +export type { GetMessagesResponse } from './models/GetMessagesResponse'; export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse'; export type { GetPlannedWorkShiftsResponse } from './models/GetPlannedWorkShiftsResponse'; export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest'; @@ -270,12 +283,15 @@ export type { GetWorkShiftsPlanningDataRequest } from './models/GetWorkShiftsPla export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse'; export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema'; export type { HTTPValidationError } from './models/HTTPValidationError'; +export type { LoadMessagesResponse } from './models/LoadMessagesResponse'; export type { LoadReceiptRequest } from './models/LoadReceiptRequest'; export type { LoadReceiptResponse } from './models/LoadReceiptResponse'; export type { ManageEmployeeRequest } from './models/ManageEmployeeRequest'; export type { ManageEmployeeResponse } from './models/ManageEmployeeResponse'; export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema'; export type { MarketplaceSchema } from './models/MarketplaceSchema'; +export type { MessageFileSchema } from './models/MessageFileSchema'; +export type { MessageSchema } from './models/MessageSchema'; export type { ModuleSchema } from './models/ModuleSchema'; export type { NotificationChannel } from './models/NotificationChannel'; export type { OptionalShippingWarehouseSchema } from './models/OptionalShippingWarehouseSchema'; @@ -324,10 +340,15 @@ export type { ProjectGeneralInfoSchema } from './models/ProjectGeneralInfoSchema export type { ProjectSchema } from './models/ProjectSchema'; export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema'; export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema'; +export type { RepeatSendingMessageSchema } from './models/RepeatSendingMessageSchema'; +export type { RepeatSendingTextMessageRequest } from './models/RepeatSendingTextMessageRequest'; +export type { RepeatSendingTextMessageResponse } from './models/RepeatSendingTextMessageResponse'; export type { ResidualBoxSchema } from './models/ResidualBoxSchema'; export type { ResidualPalletSchema } from './models/ResidualPalletSchema'; export type { ResidualProductSchema } from './models/ResidualProductSchema'; export type { RoleSchema } from './models/RoleSchema'; +export type { SendTextMessageRequest } from './models/SendTextMessageRequest'; +export type { SendTextMessageResponse } from './models/SendTextMessageResponse'; export type { ServiceCategoryReorderRequest } from './models/ServiceCategoryReorderRequest'; export type { ServiceCategoryReorderResponse } from './models/ServiceCategoryReorderResponse'; export type { ServiceCategorySchema } from './models/ServiceCategorySchema'; @@ -359,6 +380,8 @@ export type { SwitchTagRequest } from './models/SwitchTagRequest'; export type { SwitchTagResponse } from './models/SwitchTagResponse'; export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest'; export type { TaskInfoResponse } from './models/TaskInfoResponse'; +export type { TgGroupSchema } from './models/TgGroupSchema'; +export type { TgUserSchema } from './models/TgUserSchema'; export type { TimeTrackingData } from './models/TimeTrackingData'; export type { TimeTrackingRecord } from './models/TimeTrackingRecord'; export type { TransactionSchemaBase } from './models/TransactionSchemaBase'; @@ -435,6 +458,7 @@ export { BoardService } from './services/BoardService'; export { CardService } from './services/CardService'; export { CardGroupService } from './services/CardGroupService'; export { CardTagService } from './services/CardTagService'; +export { ChatService } from './services/ChatService'; export { ClientService } from './services/ClientService'; export { DepartmentService } from './services/DepartmentService'; export { MarketplaceService } from './services/MarketplaceService'; diff --git a/src/client/models/BaseMessageSchema.ts b/src/client/models/BaseMessageSchema.ts new file mode 100644 index 0000000..ac9cd04 --- /dev/null +++ b/src/client/models/BaseMessageSchema.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type BaseMessageSchema = { + text: string; + chatId: number; +}; + diff --git a/src/client/models/Body_send_messages_with_files.ts b/src/client/models/Body_send_messages_with_files.ts new file mode 100644 index 0000000..7ae84d3 --- /dev/null +++ b/src/client/models/Body_send_messages_with_files.ts @@ -0,0 +1,8 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type Body_send_messages_with_files = { + files: Array; +}; + diff --git a/src/client/models/CardSchema.ts b/src/client/models/CardSchema.ts index 0759de2..f66884b 100644 --- a/src/client/models/CardSchema.ts +++ b/src/client/models/CardSchema.ts @@ -12,6 +12,7 @@ import type { CardProductSchema } from './CardProductSchema'; import type { CardServiceSchema } from './CardServiceSchema'; import type { CardStatusHistorySchema } from './CardStatusHistorySchema'; import type { CardTagSchema } from './CardTagSchema'; +import type { ChatSchema } from './ChatSchema'; import type { ClientSchema } from './ClientSchema'; import type { PalletSchema } from './PalletSchema'; import type { ShippingWarehouseSchema } from './ShippingWarehouseSchema'; @@ -42,5 +43,6 @@ export type CardSchema = { employees?: Array; tags?: Array; attributes: Array; + chat: (ChatSchema | null); }; diff --git a/src/client/models/ChatSchema.ts b/src/client/models/ChatSchema.ts new file mode 100644 index 0000000..d5aef89 --- /dev/null +++ b/src/client/models/ChatSchema.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { TgGroupSchema } from './TgGroupSchema'; +export type ChatSchema = { + id: number; + clientId: (number | null); + cardId: (number | null); + tgGroup: (TgGroupSchema | null); +}; + diff --git a/src/client/models/ClientDetailedSchema.ts b/src/client/models/ClientDetailedSchema.ts index a183a2a..f166c2b 100644 --- a/src/client/models/ClientDetailedSchema.ts +++ b/src/client/models/ClientDetailedSchema.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema'; +import type { ChatSchema } from './ChatSchema'; import type { ClientDetailsSchema } from './ClientDetailsSchema'; import type { ResidualBoxSchema } from './ResidualBoxSchema'; import type { ResidualPalletSchema } from './ResidualPalletSchema'; @@ -13,6 +14,7 @@ export type ClientDetailedSchema = { barcodeTemplate?: (BarcodeTemplateSchema | null); comment?: (string | null); details?: (ClientDetailsSchema | null); + chat?: (ChatSchema | null); pallets?: Array; boxes?: Array; }; diff --git a/src/client/models/ClientSchema.ts b/src/client/models/ClientSchema.ts index b59f6af..3c74eb5 100644 --- a/src/client/models/ClientSchema.ts +++ b/src/client/models/ClientSchema.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { BarcodeTemplateSchema } from './BarcodeTemplateSchema'; +import type { ChatSchema } from './ChatSchema'; import type { ClientDetailsSchema } from './ClientDetailsSchema'; export type ClientSchema = { id: number; @@ -11,5 +12,6 @@ export type ClientSchema = { barcodeTemplate?: (BarcodeTemplateSchema | null); comment?: (string | null); details?: (ClientDetailsSchema | null); + chat?: (ChatSchema | null); }; diff --git a/src/client/models/CreateChatRequest.ts b/src/client/models/CreateChatRequest.ts new file mode 100644 index 0000000..2169420 --- /dev/null +++ b/src/client/models/CreateChatRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateChatRequest = { + clientId: number; + cardId: (number | null); +}; + diff --git a/src/client/models/CreateChatResponse.ts b/src/client/models/CreateChatResponse.ts new file mode 100644 index 0000000..9fbbaed --- /dev/null +++ b/src/client/models/CreateChatResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type CreateChatResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/DeleteMessageResponse.ts b/src/client/models/DeleteMessageResponse.ts new file mode 100644 index 0000000..7dfef39 --- /dev/null +++ b/src/client/models/DeleteMessageResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type DeleteMessageResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/EditMessageRequest.ts b/src/client/models/EditMessageRequest.ts new file mode 100644 index 0000000..83116f2 --- /dev/null +++ b/src/client/models/EditMessageRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { EditMessageSchema } from './EditMessageSchema'; +export type EditMessageRequest = { + message: EditMessageSchema; +}; + diff --git a/src/client/models/EditMessageResponse.ts b/src/client/models/EditMessageResponse.ts new file mode 100644 index 0000000..8ad1d1a --- /dev/null +++ b/src/client/models/EditMessageResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type EditMessageResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/EditMessageSchema.ts b/src/client/models/EditMessageSchema.ts new file mode 100644 index 0000000..044ce39 --- /dev/null +++ b/src/client/models/EditMessageSchema.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type EditMessageSchema = { + text: string; + chatId: number; + id: number; +}; + diff --git a/src/client/models/GetChatRequest.ts b/src/client/models/GetChatRequest.ts new file mode 100644 index 0000000..7d3340f --- /dev/null +++ b/src/client/models/GetChatRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type GetChatRequest = { + clientId: number; + cardId: (number | null); +}; + diff --git a/src/client/models/GetChatResponse.ts b/src/client/models/GetChatResponse.ts new file mode 100644 index 0000000..73c192d --- /dev/null +++ b/src/client/models/GetChatResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { ChatSchema } from './ChatSchema'; +export type GetChatResponse = { + chat: (ChatSchema | null); +}; + diff --git a/src/client/models/GetMessagesRequest.ts b/src/client/models/GetMessagesRequest.ts new file mode 100644 index 0000000..b2efe10 --- /dev/null +++ b/src/client/models/GetMessagesRequest.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type GetMessagesRequest = { + chatId: number; + offset: number; + limit: number; +}; + diff --git a/src/client/models/GetMessagesResponse.ts b/src/client/models/GetMessagesResponse.ts new file mode 100644 index 0000000..afc29d7 --- /dev/null +++ b/src/client/models/GetMessagesResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { MessageSchema } from './MessageSchema'; +export type GetMessagesResponse = { + messages: Array; +}; + diff --git a/src/client/models/LoadMessagesResponse.ts b/src/client/models/LoadMessagesResponse.ts new file mode 100644 index 0000000..e5a0d02 --- /dev/null +++ b/src/client/models/LoadMessagesResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type LoadMessagesResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/MessageFileSchema.ts b/src/client/models/MessageFileSchema.ts new file mode 100644 index 0000000..81b54ea --- /dev/null +++ b/src/client/models/MessageFileSchema.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type MessageFileSchema = { + id: number; + filePath: string; + type: string; + fileName: string; + fileSize: number; +}; + diff --git a/src/client/models/MessageSchema.ts b/src/client/models/MessageSchema.ts new file mode 100644 index 0000000..2fd8a49 --- /dev/null +++ b/src/client/models/MessageSchema.ts @@ -0,0 +1,19 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { MessageFileSchema } from './MessageFileSchema'; +import type { TgUserSchema } from './TgUserSchema'; +import type { UserSchema } from './UserSchema'; +export type MessageSchema = { + text: string; + chatId: number; + id: number; + createdAt: string; + tgSender: (TgUserSchema | null); + crmSender: (UserSchema | null); + status: string; + isEdited: boolean; + file?: (MessageFileSchema | null); +}; + diff --git a/src/client/models/RepeatSendingMessageSchema.ts b/src/client/models/RepeatSendingMessageSchema.ts new file mode 100644 index 0000000..e1e68da --- /dev/null +++ b/src/client/models/RepeatSendingMessageSchema.ts @@ -0,0 +1,10 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type RepeatSendingMessageSchema = { + text: string; + chatId: number; + id: number; +}; + diff --git a/src/client/models/RepeatSendingTextMessageRequest.ts b/src/client/models/RepeatSendingTextMessageRequest.ts new file mode 100644 index 0000000..0aedc81 --- /dev/null +++ b/src/client/models/RepeatSendingTextMessageRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { RepeatSendingMessageSchema } from './RepeatSendingMessageSchema'; +export type RepeatSendingTextMessageRequest = { + message: RepeatSendingMessageSchema; +}; + diff --git a/src/client/models/RepeatSendingTextMessageResponse.ts b/src/client/models/RepeatSendingTextMessageResponse.ts new file mode 100644 index 0000000..8cbd44d --- /dev/null +++ b/src/client/models/RepeatSendingTextMessageResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type RepeatSendingTextMessageResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/SendTextMessageRequest.ts b/src/client/models/SendTextMessageRequest.ts new file mode 100644 index 0000000..c6d0925 --- /dev/null +++ b/src/client/models/SendTextMessageRequest.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseMessageSchema } from './BaseMessageSchema'; +export type SendTextMessageRequest = { + message: BaseMessageSchema; +}; + diff --git a/src/client/models/SendTextMessageResponse.ts b/src/client/models/SendTextMessageResponse.ts new file mode 100644 index 0000000..b611d07 --- /dev/null +++ b/src/client/models/SendTextMessageResponse.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type SendTextMessageResponse = { + ok: boolean; + message: string; +}; + diff --git a/src/client/models/TgGroupSchema.ts b/src/client/models/TgGroupSchema.ts new file mode 100644 index 0000000..c2186d4 --- /dev/null +++ b/src/client/models/TgGroupSchema.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type TgGroupSchema = { + tgGroupId: number; + tgInviteLink: string; +}; + diff --git a/src/client/models/TgUserSchema.ts b/src/client/models/TgUserSchema.ts new file mode 100644 index 0000000..ac123b9 --- /dev/null +++ b/src/client/models/TgUserSchema.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type TgUserSchema = { + id: number; + firstName: string; + lastName: string; + username: string; +}; + diff --git a/src/client/services/ChatService.ts b/src/client/services/ChatService.ts new file mode 100644 index 0000000..80da7aa --- /dev/null +++ b/src/client/services/ChatService.ts @@ -0,0 +1,214 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { Body_send_messages_with_files } from '../models/Body_send_messages_with_files'; +import type { CreateChatRequest } from '../models/CreateChatRequest'; +import type { CreateChatResponse } from '../models/CreateChatResponse'; +import type { DeleteMessageResponse } from '../models/DeleteMessageResponse'; +import type { EditMessageRequest } from '../models/EditMessageRequest'; +import type { EditMessageResponse } from '../models/EditMessageResponse'; +import type { GetChatRequest } from '../models/GetChatRequest'; +import type { GetChatResponse } from '../models/GetChatResponse'; +import type { GetMessagesRequest } from '../models/GetMessagesRequest'; +import type { GetMessagesResponse } from '../models/GetMessagesResponse'; +import type { LoadMessagesResponse } from '../models/LoadMessagesResponse'; +import type { RepeatSendingTextMessageRequest } from '../models/RepeatSendingTextMessageRequest'; +import type { RepeatSendingTextMessageResponse } from '../models/RepeatSendingTextMessageResponse'; +import type { SendTextMessageRequest } from '../models/SendTextMessageRequest'; +import type { SendTextMessageResponse } from '../models/SendTextMessageResponse'; +import type { CancelablePromise } from '../core/CancelablePromise'; +import { OpenAPI } from '../core/OpenAPI'; +import { request as __request } from '../core/request'; +export class ChatService { + /** + * Send Text Message + * @returns SendTextMessageResponse Successful Response + * @throws ApiError + */ + public static sendTextMessage({ + requestBody, + }: { + requestBody: SendTextMessageRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/message', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Edit Message + * @returns EditMessageResponse Successful Response + * @throws ApiError + */ + public static editMessage({ + requestBody, + }: { + requestBody: EditMessageRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PATCH', + url: '/chat/message', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Repeat Sending Text Message + * @returns RepeatSendingTextMessageResponse Successful Response + * @throws ApiError + */ + public static repeatSendingTextMessage({ + requestBody, + }: { + requestBody: RepeatSendingTextMessageRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/message/repeat', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Send Messages With Files + * @returns LoadMessagesResponse Successful Response + * @throws ApiError + */ + public static sendMessagesWithFiles({ + chatId, + caption, + formData, + }: { + chatId: number, + caption: string, + formData: Body_send_messages_with_files, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/message/files', + query: { + 'chat_id': chatId, + 'caption': caption, + }, + formData: formData, + mediaType: 'multipart/form-data', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Delete Message + * @returns DeleteMessageResponse Successful Response + * @throws ApiError + */ + public static deleteMessage({ + messageId, + }: { + messageId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/chat/message/{message_id}', + path: { + 'message_id': messageId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Chat + * @returns GetChatResponse Successful Response + * @throws ApiError + */ + public static getChat({ + requestBody, + }: { + requestBody: GetChatRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Create Chat + * @returns CreateChatResponse Successful Response + * @throws ApiError + */ + public static createChat({ + requestBody, + }: { + requestBody: CreateChatRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/create', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Messages + * @returns GetMessagesResponse Successful Response + * @throws ApiError + */ + public static getMessages({ + requestBody, + }: { + requestBody: GetMessagesRequest, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/chat/messages', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * Get Tg File + * @returns any Successful Response + * @throws ApiError + */ + public static getTgFileChatTgFileFileIdGet({ + fileId, + }: { + fileId: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/chat/tg-file/{file_id}', + path: { + 'file_id': fileId, + }, + errors: { + 422: `Validation Error`, + }, + }); + } +} diff --git a/src/components/ActionIconCopy/ActionIconCopy.tsx b/src/components/ActionIconCopy/ActionIconCopy.tsx new file mode 100644 index 0000000..1181dc5 --- /dev/null +++ b/src/components/ActionIconCopy/ActionIconCopy.tsx @@ -0,0 +1,41 @@ +import { ActionIcon, rem, Tooltip } from "@mantine/core"; +import { IconCheck, IconCopy } from "@tabler/icons-react"; +import { FC } from "react"; +import { useClipboard } from "@mantine/hooks"; + +type Props = { + value: string; + onCopiedLabel: string; +}; + +export const ActionIconCopy: FC = ({ onCopiedLabel, value }) => { + const clipboard = useClipboard(); + + return ( + + clipboard.copy(value)} + > + {clipboard.copied ? ( + + ) : ( + + )} + + + ); +}; +export default ActionIconCopy; diff --git a/src/components/Chat/Chat.module.css b/src/components/Chat/Chat.module.css new file mode 100644 index 0000000..97e5cdf --- /dev/null +++ b/src/components/Chat/Chat.module.css @@ -0,0 +1,20 @@ + +.message { + @mixin light { + background-color: var(--mantine-color-gray-2); + } + @mixin dark { + background-color: var(--mantine-color-dark-5); + } + border-radius: 1em; +} + +.other-message { + @mixin light { + background-color: var(--mantine-color-gray-3); + } + @mixin dark { + background-color: var(--mantine-color-dark-6); + } + border-radius: 1em; +} \ No newline at end of file diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx new file mode 100644 index 0000000..feb1b51 --- /dev/null +++ b/src/components/Chat/Chat.tsx @@ -0,0 +1,86 @@ +import Message from "./components/Message/Message.tsx"; +import { useChatContext } from "../../pages/ClientsPage/contexts/ChatContext.tsx"; +import { MessageSchema } from "../../client"; +import { ReactNode, useCallback } from "react"; +import ChatDate from "./components/ChatDate/ChatDate.tsx"; +import MessageInput from "./components/MessageInput/MessageInput.tsx"; +import { Virtuoso } from "react-virtuoso"; +import { Stack } from "@mantine/core"; + +const Chat = () => { + const { + messages, + lastMessage, + firstItemIndex, + fetchMoreMessages, + } = useChatContext(); + + const onFollowOutputHandler = useCallback( + (atBottom: boolean) => { + if (atBottom) { + return "auto"; + } else { + return false; + } + }, + [lastMessage], + ); + + const itemContent = useCallback( + (index: number, sessionData: MessageSchema) => { + let dateComponent: ReactNode | null = null; + const msgArrayIdx = index - firstItemIndex; + if (msgArrayIdx < 0 || msgArrayIdx > messages.length - 1) return; + const currMessage = messages[msgArrayIdx]; + let prevMessage = null; + if (msgArrayIdx > 0) { + prevMessage = messages[msgArrayIdx - 1]; + } + if (!prevMessage || prevMessage.createdAt.substring(5, 10) != currMessage.createdAt.substring(5, 10)) { + dateComponent = ( + + ); + } + return ( + + {dateComponent} + + + ); + }, + [messages], + ); + + if (messages.length === 0) { + return ( + + + + ); + } + + return ( + + + + + ); +}; + +export default Chat; diff --git a/src/components/Chat/components/ChatDate/ChatDate.tsx b/src/components/Chat/components/ChatDate/ChatDate.tsx new file mode 100644 index 0000000..0eacbe0 --- /dev/null +++ b/src/components/Chat/components/ChatDate/ChatDate.tsx @@ -0,0 +1,20 @@ +import { Box, Center, Pill } from "@mantine/core"; +import styles from "../../Chat.module.css"; + +type Props = { + date: Date; +} + +const ChatDate = ({ date }: Props) => { + return ( +
+ + + {date.toLocaleDateString("ru-RU", { day: "numeric", month: "long" })} + + +
+ ); +}; + +export default ChatDate; diff --git a/src/components/Chat/components/ChatFile/ChatFile.module.css b/src/components/Chat/components/ChatFile/ChatFile.module.css new file mode 100644 index 0000000..468ff12 --- /dev/null +++ b/src/components/Chat/components/ChatFile/ChatFile.module.css @@ -0,0 +1,15 @@ +.file-circe { + @mixin light { + background-color: var(--mantine-color-gray-0); + } + @mixin dark { + background-color: var(--mantine-color-dark-4); + } + border-radius: 50%; + align-items: center; + justify-items: center; + padding: 0.8rem; + width: 3rem; + height: 3rem; + cursor: pointer; +} diff --git a/src/components/Chat/components/ChatFile/ChatFile.tsx b/src/components/Chat/components/ChatFile/ChatFile.tsx new file mode 100644 index 0000000..f2fcabb --- /dev/null +++ b/src/components/Chat/components/ChatFile/ChatFile.tsx @@ -0,0 +1,21 @@ +import { MessageFileSchema } from "../../../../client"; +import ChatDocument from "./components/ChatDocument.tsx"; +import ChatPhoto from "./components/ChatPhoto.tsx"; + + +type Props = { + file: MessageFileSchema; +} + +const ChatFile = ({ file }: Props) => { + if (file.type === "photo") { + return ( + + ); + } + return ( + + ); +}; + +export default ChatFile; diff --git a/src/components/Chat/components/ChatFile/components/ChatDocument.tsx b/src/components/Chat/components/ChatFile/components/ChatDocument.tsx new file mode 100644 index 0000000..957bebc --- /dev/null +++ b/src/components/Chat/components/ChatFile/components/ChatDocument.tsx @@ -0,0 +1,48 @@ +import { Center, Group, rem, Stack, Text } from "@mantine/core"; +import { IconFileFilled } from "@tabler/icons-react"; +import { MessageFileSchema } from "../../../../../client"; +import styles from "../ChatFile.module.css"; +import classNames from "classnames"; +import downloadFile from "../utils/downloadFile.ts"; + +type Props = { + file: MessageFileSchema; +} + +const ChatDocument = ({ file }: Props) => { + const round = (value: number) => Math.round(value * 10) / 10; + + const getFileSize = () => { + const BYTES_IN_KB = 1_024; + const BYTES_IN_MB = 1_048_576; + const BYTES_IN_GB = 1_073_741_824; + + if (file.fileSize < BYTES_IN_KB) { + return `${file.fileSize} B`; + } + if (file.fileSize < BYTES_IN_MB) { + return `${round(file.fileSize / BYTES_IN_KB)} KB`; + } + if (file.fileSize < BYTES_IN_GB) { + return `${round(file.fileSize / BYTES_IN_MB)} MB`; + } + return `${round(file.fileSize / BYTES_IN_GB)} GB`; + }; + + return ( + +
downloadFile(file)} + > + +
+ + {file.fileName} + {getFileSize()} + +
+ ); +}; + +export default ChatDocument; diff --git a/src/components/Chat/components/ChatFile/components/ChatPhoto.tsx b/src/components/Chat/components/ChatFile/components/ChatPhoto.tsx new file mode 100644 index 0000000..47f5b76 --- /dev/null +++ b/src/components/Chat/components/ChatFile/components/ChatPhoto.tsx @@ -0,0 +1,44 @@ +import { MessageFileSchema } from "../../../../../client"; +import { Box, Image, Modal } from "@mantine/core"; +import getDocLink from "../utils/getDocLink.ts"; +import { useDisclosure } from "@mantine/hooks"; + + +type Props = { + file: MessageFileSchema; +} + +const ChatPhoto = ({ file }: Props) => { + const [opened, { open, close }] = useDisclosure(false); + + const image = ( + + ); + + return ( + <> + + + {image} + + + + {image} + + + ); +}; + +export default ChatPhoto; diff --git a/src/components/Chat/components/ChatFile/utils/downloadFile.ts b/src/components/Chat/components/ChatFile/utils/downloadFile.ts new file mode 100644 index 0000000..ccf5641 --- /dev/null +++ b/src/components/Chat/components/ChatFile/utils/downloadFile.ts @@ -0,0 +1,14 @@ +import { MessageFileSchema } from "../../../../../client"; +import getDocLink from "./getDocLink.ts"; + + +const downloadFile = (file: MessageFileSchema) => { + const link = document.createElement("a"); + link.href = getDocLink(file.id); + link.download = file.fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + +export default downloadFile; diff --git a/src/components/Chat/components/ChatFile/utils/getDocLink.ts b/src/components/Chat/components/ChatFile/utils/getDocLink.ts new file mode 100644 index 0000000..75bd530 --- /dev/null +++ b/src/components/Chat/components/ChatFile/utils/getDocLink.ts @@ -0,0 +1,5 @@ +const getDocLink = (fileId: number) => { + return `${import.meta.env.VITE_API_URL}/chat/tg-file/${fileId}`; +}; + +export default getDocLink; diff --git a/src/components/Chat/components/Message/Message.tsx b/src/components/Chat/components/Message/Message.tsx new file mode 100644 index 0000000..56e4b8b --- /dev/null +++ b/src/components/Chat/components/Message/Message.tsx @@ -0,0 +1,115 @@ +import { MessageSchema } from "../../../../client"; +import { Box, Center, em, Flex, Group, rem } from "@mantine/core"; +import { formatDateTime } from "../../../../types/utils.ts"; +import { IconAlertCircle, IconBrandTelegram, IconCheck, IconClock, IconEdit, IconTrash } from "@tabler/icons-react"; +import { useContextMenu } from "mantine-contextmenu"; +import useMessage from "./hooks/useMessage.tsx"; +import styles from "../../Chat.module.css"; +import classNames from "classnames"; +import ChatFile from "../ChatFile/ChatFile.tsx"; + +type Props = { + message: MessageSchema; +} + +enum MessageStatuses { + SUCCESS = "SUCCESS", + ERROR = "ERROR", + SENDING = "SENDING", +} + +const Message = ({ message }: Props) => { + const isMine = !message.tgSender; + const isSuccess = message.status == MessageStatuses.SUCCESS; + const isError = message.status == MessageStatuses.ERROR; + + const { + onDeleteMessageClick, + onEditMessageClick, + onRepeatSendingClick, + } = useMessage(); + + const { showContextMenu } = useContextMenu(); + + const contextMenuSuccessMsg = () => showContextMenu([ + { + key: "delete", + onClick: () => onDeleteMessageClick(message), + title: "Удалить", + icon: , + }, + { + key: "edit", + onClick: () => onEditMessageClick(message), + title: "Редактировать", + icon: , + }, + ]); + + const contextMenuErrorMsg = () => showContextMenu([ + { + key: "repeatSending", + onClick: () => onRepeatSendingClick(message), + title: "Повторить отправку", + icon: , + }, + ]); + + const getContext = () => { + if (!isMine || message.file) return; + + if (isSuccess) return contextMenuSuccessMsg(); + if (isError) return contextMenuErrorMsg(); + }; + + const getStatusIcon = () => { + const size = em(18); + if (message.status == MessageStatuses.ERROR) { + return ; + } + if (message.status == MessageStatuses.SENDING) { + return ; + } + return ; + }; + + return ( + + + {!isMine && ( +
{message.tgSender!.lastName} {message.tgSender!.firstName}
+ )} + {message.file && ( + + + + )} +
{message.text}
+ + {message.isEdited && "ред."} +
+ {formatDateTime(message.createdAt).substring(11, 16)} +
+ {isMine && ( +
+ {getStatusIcon()} +
+ )} +
+
+
+ ); +}; + +export default Message; diff --git a/src/components/Chat/components/Message/hooks/useMessage.tsx b/src/components/Chat/components/Message/hooks/useMessage.tsx new file mode 100644 index 0000000..4f8d695 --- /dev/null +++ b/src/components/Chat/components/Message/hooks/useMessage.tsx @@ -0,0 +1,48 @@ +import { ChatService, MessageSchema } from "../../../../../client"; +import { notifications } from "../../../../../shared/lib/notifications.ts"; +import { useChatContext } from "../../../../../pages/ClientsPage/contexts/ChatContext.tsx"; + +const useMessage = () => { + const { form } = useChatContext(); + + const onDeleteMessageClick = (message: MessageSchema) => { + ChatService.deleteMessage({ + messageId: message.id, + }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + } + }) + .catch(err => console.log(err)); + }; + + const onEditMessageClick = (message: MessageSchema) => { + form.setValues({ + messageId: message.id, + message: message.text, + }); + }; + + const onRepeatSendingClick = (message: MessageSchema) => { + ChatService.repeatSendingTextMessage({ + requestBody: { + message, + }, + }) + .then(({ ok, message }) => { + if (!ok) { + notifications.error({ message }); + } + }) + .catch(err => console.log(err)); + }; + + return { + onDeleteMessageClick, + onEditMessageClick, + onRepeatSendingClick, + }; +}; + +export default useMessage; diff --git a/src/components/Chat/components/MessageInput/MessageInput.tsx b/src/components/Chat/components/MessageInput/MessageInput.tsx new file mode 100644 index 0000000..a12fb63 --- /dev/null +++ b/src/components/Chat/components/MessageInput/MessageInput.tsx @@ -0,0 +1,75 @@ +import { ActionIcon, Button, Divider, Group, Stack, Textarea, Tooltip } from "@mantine/core"; +import { IconCheck, IconPaperclip, IconSend2, IconX } from "@tabler/icons-react"; +import { useChatContext } from "../../../../pages/ClientsPage/contexts/ChatContext.tsx"; +import ActionIconCopy from "../../../ActionIconCopy/ActionIconCopy.tsx"; +import SelectedFile from "../SelectedFile/SelectedFile.tsx"; + + +const MessageInput = () => { + const { + submitMessage, + chat, + form, + files, + fileDialog, + isMessageSending, + } = useChatContext(); + + const getFiles = files.map(file => ( + + )); + + return ( +
submitMessage(values))}> + + + {getFiles} + + {chat?.tgGroup?.tgInviteLink && ( + + )} + + + + + +