feat: sending and receiving messages with files, editing text messages
This commit is contained in:
		@@ -40,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';
 | 
			
		||||
@@ -216,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';
 | 
			
		||||
@@ -279,12 +283,14 @@ 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';
 | 
			
		||||
@@ -334,12 +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 { SendMessageRequest } from './models/SendMessageRequest';
 | 
			
		||||
export type { SendMessageResponse } from './models/SendMessageResponse';
 | 
			
		||||
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';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/models/Body_send_messages_with_files.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/models/Body_send_messages_with_files.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<Blob>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/EditMessageRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/EditMessageRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type SendMessageResponse = {
 | 
			
		||||
export type EditMessageResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/EditMessageSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/EditMessageSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/LoadMessagesResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/LoadMessagesResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								src/client/models/MessageFileSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/client/models/MessageFileSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { MessageFileSchema } from './MessageFileSchema';
 | 
			
		||||
import type { TgUserSchema } from './TgUserSchema';
 | 
			
		||||
export type MessageSchema = {
 | 
			
		||||
    text: string;
 | 
			
		||||
@@ -10,5 +11,7 @@ export type MessageSchema = {
 | 
			
		||||
    createdAt: string;
 | 
			
		||||
    tgSender: (TgUserSchema | null);
 | 
			
		||||
    status: string;
 | 
			
		||||
    isEdited: boolean;
 | 
			
		||||
    file?: (MessageFileSchema | null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/RepeatSendingMessageSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/RepeatSendingMessageSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/RepeatSendingTextMessageRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/RepeatSendingTextMessageRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/RepeatSendingTextMessageResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/RepeatSendingTextMessageResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { BaseMessageSchema } from './BaseMessageSchema';
 | 
			
		||||
export type SendMessageRequest = {
 | 
			
		||||
export type SendTextMessageRequest = {
 | 
			
		||||
    message: BaseMessageSchema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/SendTextMessageResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/SendTextMessageResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,29 +2,35 @@
 | 
			
		||||
/* 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 { SendMessageRequest } from '../models/SendMessageRequest';
 | 
			
		||||
import type { SendMessageResponse } from '../models/SendMessageResponse';
 | 
			
		||||
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 Message
 | 
			
		||||
     * @returns SendMessageResponse Successful Response
 | 
			
		||||
     * Send Text Message
 | 
			
		||||
     * @returns SendTextMessageResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static sendMessage({
 | 
			
		||||
    public static sendTextMessage({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: SendMessageRequest,
 | 
			
		||||
    }): CancelablePromise<SendMessageResponse> {
 | 
			
		||||
        requestBody: SendTextMessageRequest,
 | 
			
		||||
    }): CancelablePromise<SendTextMessageResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/chat/message',
 | 
			
		||||
@@ -35,6 +41,74 @@ export class ChatService {
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Edit Message
 | 
			
		||||
     * @returns EditMessageResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static editMessage({
 | 
			
		||||
        requestBody,
 | 
			
		||||
    }: {
 | 
			
		||||
        requestBody: EditMessageRequest,
 | 
			
		||||
    }): CancelablePromise<EditMessageResponse> {
 | 
			
		||||
        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<RepeatSendingTextMessageResponse> {
 | 
			
		||||
        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<LoadMessagesResponse> {
 | 
			
		||||
        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
 | 
			
		||||
@@ -116,4 +190,25 @@ export class ChatService {
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get Tg File
 | 
			
		||||
     * @returns any Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static getTgFileChatTgFileFileIdGet({
 | 
			
		||||
        fileId,
 | 
			
		||||
    }: {
 | 
			
		||||
        fileId: number,
 | 
			
		||||
    }): CancelablePromise<any> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            url: '/chat/tg-file/{file_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'file_id': fileId,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,17 +15,13 @@ export const ActionIconCopy: FC<Props> = ({ onCopiedLabel, value }) => {
 | 
			
		||||
        <Tooltip
 | 
			
		||||
            label={onCopiedLabel}
 | 
			
		||||
            offset={5}
 | 
			
		||||
            position="bottom"
 | 
			
		||||
            radius="xl"
 | 
			
		||||
            transitionProps={{ duration: 100, transition: "slide-down" }}
 | 
			
		||||
            opened={clipboard.copied}>
 | 
			
		||||
            <ActionIcon
 | 
			
		||||
                variant={"default"}
 | 
			
		||||
                size="lg"
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                    console.log("AKLSKLSKSM");
 | 
			
		||||
                    return clipboard.copy(value);
 | 
			
		||||
                }}
 | 
			
		||||
                onClick={() => clipboard.copy(value)}
 | 
			
		||||
            >
 | 
			
		||||
                {clipboard.copied ? (
 | 
			
		||||
                    <IconCheck
 | 
			
		||||
 
 | 
			
		||||
@@ -28,9 +28,9 @@ const Chat = () => {
 | 
			
		||||
                    />
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            elements.push((
 | 
			
		||||
            elements.push(
 | 
			
		||||
                <Message key={currMessage.id + "msg"} message={currMessage} />
 | 
			
		||||
            ));
 | 
			
		||||
            );
 | 
			
		||||
            prevMessage = currMessage;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -38,12 +38,13 @@ const Chat = () => {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Stack>
 | 
			
		||||
        <Stack h={"96vh"}>
 | 
			
		||||
            <ScrollArea
 | 
			
		||||
                h={"100%"}
 | 
			
		||||
                viewportRef={scrollRef}
 | 
			
		||||
                onScrollPositionChange={onScrollPositionChange}
 | 
			
		||||
            >
 | 
			
		||||
                <Stack h={"91vh"} pr={"md"} gap={"sm"}>
 | 
			
		||||
                <Stack pr={"md"} gap={"sm"}>
 | 
			
		||||
                    {getChatElements()}
 | 
			
		||||
                </Stack>
 | 
			
		||||
            </ScrollArea>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								src/components/Chat/components/ChatFile/ChatFile.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/components/Chat/components/ChatFile/ChatFile.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/components/Chat/components/ChatFile/ChatFile.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/components/Chat/components/ChatFile/ChatFile.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
			
		||||
            <ChatPhoto file={file} />
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <ChatDocument file={file} />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ChatFile;
 | 
			
		||||
@@ -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 (
 | 
			
		||||
        <Group justify={"center"} my={"sm"}>
 | 
			
		||||
            <Center
 | 
			
		||||
                className={classNames(styles["file-circe"])}
 | 
			
		||||
                onClick={() => downloadFile(file)}
 | 
			
		||||
            >
 | 
			
		||||
                <IconFileFilled />
 | 
			
		||||
            </Center>
 | 
			
		||||
            <Stack gap={rem(1)} my={rem(1)}>
 | 
			
		||||
                <Text>{file.fileName}</Text>
 | 
			
		||||
                <Text>{getFileSize()}</Text>
 | 
			
		||||
            </Stack>
 | 
			
		||||
        </Group>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ChatDocument;
 | 
			
		||||
@@ -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 = (
 | 
			
		||||
        <Image
 | 
			
		||||
            src={getDocLink(file.id)}
 | 
			
		||||
            radius={"md"}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Modal
 | 
			
		||||
                opened={opened}
 | 
			
		||||
                onClose={close}
 | 
			
		||||
                size={"auto"}
 | 
			
		||||
                withCloseButton={false}
 | 
			
		||||
            >
 | 
			
		||||
                <Box mah={"90vh"} maw={"90vw"}>
 | 
			
		||||
                    {image}
 | 
			
		||||
                </Box>
 | 
			
		||||
            </Modal>
 | 
			
		||||
            <Box
 | 
			
		||||
                my={"sm"}
 | 
			
		||||
                onClick={open}
 | 
			
		||||
                style={{ cursor: "pointer" }}
 | 
			
		||||
            >
 | 
			
		||||
                {image}
 | 
			
		||||
            </Box>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ChatPhoto;
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
const getDocLink = (fileId: number) => {
 | 
			
		||||
    return `${import.meta.env.VITE_API_URL}/chat/tg-file/${fileId}`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default getDocLink;
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import { MessageSchema } from "../../../../client";
 | 
			
		||||
import { Box, Center, em, Flex, Group, rem } from "@mantine/core";
 | 
			
		||||
import { formatDateTime } from "../../../../types/utils.ts";
 | 
			
		||||
import { IconAlertCircle, IconCheck, IconClock, IconTrash } from "@tabler/icons-react";
 | 
			
		||||
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;
 | 
			
		||||
@@ -20,18 +21,47 @@ enum MessageStatuses {
 | 
			
		||||
const Message = ({ message }: Props) => {
 | 
			
		||||
    const isMine = !message.tgSender;
 | 
			
		||||
    const isSuccess = message.status == MessageStatuses.SUCCESS;
 | 
			
		||||
    const { onDeleteMessageClick } = useMessage();
 | 
			
		||||
    const isError = message.status == MessageStatuses.ERROR;
 | 
			
		||||
 | 
			
		||||
    const {
 | 
			
		||||
        onDeleteMessageClick,
 | 
			
		||||
        onEditMessageClick,
 | 
			
		||||
        onRepeatSendingClick,
 | 
			
		||||
    } = useMessage();
 | 
			
		||||
 | 
			
		||||
    const { showContextMenu } = useContextMenu();
 | 
			
		||||
 | 
			
		||||
    const contextMenu = () => showContextMenu([
 | 
			
		||||
    const contextMenuSuccessMsg = () => showContextMenu([
 | 
			
		||||
        {
 | 
			
		||||
            key: "delete",
 | 
			
		||||
            onClick: () => onDeleteMessageClick(message),
 | 
			
		||||
            title: "Удалить",
 | 
			
		||||
            icon: <IconTrash />,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            key: "edit",
 | 
			
		||||
            onClick: () => onEditMessageClick(message),
 | 
			
		||||
            title: "Редактировать",
 | 
			
		||||
            icon: <IconEdit />,
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const contextMenuErrorMsg = () => showContextMenu([
 | 
			
		||||
        {
 | 
			
		||||
            key: "repeatSending",
 | 
			
		||||
            onClick: () => onRepeatSendingClick(message),
 | 
			
		||||
            title: "Повторить отправку",
 | 
			
		||||
            icon: <IconBrandTelegram />,
 | 
			
		||||
        },
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    const getContext = () => {
 | 
			
		||||
        if (!isMine) return;
 | 
			
		||||
 | 
			
		||||
        if (isSuccess) return contextMenuSuccessMsg();
 | 
			
		||||
        if (isError) return contextMenuErrorMsg();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const getStatusIcon = () => {
 | 
			
		||||
        const size = em(18);
 | 
			
		||||
        if (message.status == MessageStatuses.ERROR) {
 | 
			
		||||
@@ -49,11 +79,15 @@ const Message = ({ message }: Props) => {
 | 
			
		||||
                py={rem(5)}
 | 
			
		||||
                px={rem(15)}
 | 
			
		||||
                className={classNames(isMine ? styles["message"] : styles["other-message"])}
 | 
			
		||||
                onContextMenu={isMine && isSuccess ? contextMenu() : undefined}
 | 
			
		||||
                onContextMenu={getContext()}
 | 
			
		||||
                maw={em(600)}
 | 
			
		||||
            >
 | 
			
		||||
                {!isMine && (
 | 
			
		||||
                    <div>{message.tgSender!.lastName} {message.tgSender!.firstName}</div>
 | 
			
		||||
                )}
 | 
			
		||||
                {message.file && (
 | 
			
		||||
                    <ChatFile file={message.file} />
 | 
			
		||||
                )}
 | 
			
		||||
                <div>{message.text}</div>
 | 
			
		||||
                <Group
 | 
			
		||||
                    gap={em(5)}
 | 
			
		||||
@@ -61,6 +95,7 @@ const Message = ({ message }: Props) => {
 | 
			
		||||
                    align={"center"}
 | 
			
		||||
                    wrap={"nowrap"}
 | 
			
		||||
                >
 | 
			
		||||
                    {message.isEdited && "ред."}
 | 
			
		||||
                    <Center>
 | 
			
		||||
                        {formatDateTime(message.createdAt).substring(11, 16)}
 | 
			
		||||
                    </Center>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
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({
 | 
			
		||||
@@ -15,8 +17,31 @@ const useMessage = () => {
 | 
			
		||||
            .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,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,71 @@
 | 
			
		||||
import { Button, Group, TextInput, Tooltip } from "@mantine/core";
 | 
			
		||||
import { IconSend2 } from "@tabler/icons-react";
 | 
			
		||||
import { ActionIcon, Button, Divider, Group, Stack, TextInput, Tooltip } from "@mantine/core";
 | 
			
		||||
import { IconCheck, IconPaperclip, IconSend2, IconX } from "@tabler/icons-react";
 | 
			
		||||
import { useChatContext } from "../../../../pages/ClientsPage/contexts/ChatContext.tsx";
 | 
			
		||||
import { useForm } from "@mantine/form";
 | 
			
		||||
import ActionIconCopy from "../../../ActionIconCopy/ActionIconCopy.tsx";
 | 
			
		||||
import SelectedFile from "../SelectedFile/SelectedFile.tsx";
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
 | 
			
		||||
export type MessageForm = {
 | 
			
		||||
    message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MessageInput = () => {
 | 
			
		||||
    const { sendMessage, chat } = useChatContext();
 | 
			
		||||
    const form = useForm<MessageForm>({
 | 
			
		||||
        initialValues: {
 | 
			
		||||
            message: "",
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
    const {
 | 
			
		||||
        submitMessage,
 | 
			
		||||
        chat,
 | 
			
		||||
        form,
 | 
			
		||||
        files,
 | 
			
		||||
        fileDialog,
 | 
			
		||||
    } = useChatContext();
 | 
			
		||||
 | 
			
		||||
    const getFiles = useMemo(() => {
 | 
			
		||||
        return files.map(file => (
 | 
			
		||||
            <SelectedFile key={file.name} file={file} />
 | 
			
		||||
        ));
 | 
			
		||||
    }, [files]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <form onSubmit={form.onSubmit(values => sendMessage(values, form))}>
 | 
			
		||||
        <form onSubmit={form.onSubmit(values => submitMessage(values))}>
 | 
			
		||||
            <Stack gap={"xs"}>
 | 
			
		||||
                <Divider />
 | 
			
		||||
                {getFiles}
 | 
			
		||||
                <Group wrap={"nowrap"} align={"center"}>
 | 
			
		||||
                    {chat?.tgGroup?.tgInviteLink && (
 | 
			
		||||
                    <Tooltip label={"Ссылка-приглашение"}>
 | 
			
		||||
                        <ActionIconCopy
 | 
			
		||||
                            onCopiedLabel={"Ссылка скопирована в буфер обмена"}
 | 
			
		||||
                            onCopiedLabel={"Ссылка на чат скопирована в буфер обмена"}
 | 
			
		||||
                            value={chat.tgGroup.tgInviteLink}
 | 
			
		||||
                        />
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                    )}
 | 
			
		||||
                    <Tooltip label={"Прикрепить файлы"}>
 | 
			
		||||
                        <ActionIcon
 | 
			
		||||
                            variant={"default"}
 | 
			
		||||
                            onClick={fileDialog.open}
 | 
			
		||||
                            size="lg"
 | 
			
		||||
                        >
 | 
			
		||||
                            <IconPaperclip />
 | 
			
		||||
                        </ActionIcon>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                    <TextInput
 | 
			
		||||
                        {...form.getInputProps("message")}
 | 
			
		||||
                        w={"100%"}
 | 
			
		||||
                    />
 | 
			
		||||
                    {form.values.messageId && (
 | 
			
		||||
                        <Button
 | 
			
		||||
                            variant={"default"}
 | 
			
		||||
                            onClick={form.reset}
 | 
			
		||||
                        >
 | 
			
		||||
                            <IconX />
 | 
			
		||||
                        </Button>
 | 
			
		||||
                    )}
 | 
			
		||||
                    <Button
 | 
			
		||||
                        variant={"default"}
 | 
			
		||||
                        type="submit"
 | 
			
		||||
                    >
 | 
			
		||||
                        {form.values.messageId ? (
 | 
			
		||||
                            <IconCheck />
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <IconSend2 />
 | 
			
		||||
                        )}
 | 
			
		||||
                    </Button>
 | 
			
		||||
                </Group>
 | 
			
		||||
            </Stack>
 | 
			
		||||
        </form>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								src/components/Chat/components/SelectedFile/SelectedFile.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/components/Chat/components/SelectedFile/SelectedFile.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import { Box, Group, Text } from "@mantine/core";
 | 
			
		||||
import { IconX } from "@tabler/icons-react";
 | 
			
		||||
import { useChatContext } from "../../../../pages/ClientsPage/contexts/ChatContext.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    file: File;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SelectedFile = ({ file }: Props) => {
 | 
			
		||||
    const { files, filesHandlers } = useChatContext();
 | 
			
		||||
 | 
			
		||||
    const onCancelFileClick = () => {
 | 
			
		||||
        const idx = files.findIndex(f => f.name === file.name && f.type === file.type);
 | 
			
		||||
 | 
			
		||||
        if (idx < 0) return;
 | 
			
		||||
        filesHandlers.remove(idx);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Box>
 | 
			
		||||
            <Group justify={"space-between"}>
 | 
			
		||||
                <Text>
 | 
			
		||||
                    {file.name}
 | 
			
		||||
                </Text>
 | 
			
		||||
                <Box
 | 
			
		||||
                    onClick={onCancelFileClick}
 | 
			
		||||
                    style={{
 | 
			
		||||
                        cursor: "pointer",
 | 
			
		||||
                    }}
 | 
			
		||||
                >
 | 
			
		||||
                    <IconX />
 | 
			
		||||
                </Box>
 | 
			
		||||
            </Group>
 | 
			
		||||
        </Box>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default SelectedFile;
 | 
			
		||||
@@ -3,13 +3,16 @@ import CreateServiceCategoryModal from "../pages/ServicesPage/modals/CreateServi
 | 
			
		||||
import CreateServiceModal from "../pages/ServicesPage/modals/CreateServiceModal.tsx";
 | 
			
		||||
import createProductModal from "../pages/ProductsPage/modals/CreateProductModal/CreateProductModal.tsx";
 | 
			
		||||
import ProductFormModal from "../pages/ClientsPage/modals/ClientFormModal/ClientFormModal.tsx";
 | 
			
		||||
import AddCardServiceModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardServiceModal.tsx";
 | 
			
		||||
import AddCardProductModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardProductModal.tsx";
 | 
			
		||||
import AddCardServiceModal
 | 
			
		||||
    from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardServiceModal.tsx";
 | 
			
		||||
import AddCardProductModal
 | 
			
		||||
    from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/AddCardProductModal.tsx";
 | 
			
		||||
import PrintBarcodeModal from "./PrintBarcodeModal/PrintBarcodeModal.tsx";
 | 
			
		||||
import AddBarcodeModal from "./AddBarcodeModal/AddBarcodeModal.tsx";
 | 
			
		||||
import BarcodeTemplateFormModal
 | 
			
		||||
    from "../pages/BarcodePage/modals/BarcodeTemplateFormModal/BarcodeTemplateFormModal.tsx";
 | 
			
		||||
import ProductServiceFormModal from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/ProductServiceFormModal.tsx";
 | 
			
		||||
import ProductServiceFormModal
 | 
			
		||||
    from "../modules/cardModules/cardEditorTabs/ProductAndServiceTab/modals/ProductServiceFormModal.tsx";
 | 
			
		||||
import UserFormModal from "../pages/AdminPage/modals/UserFormModal/UserFormModal.tsx";
 | 
			
		||||
import EmployeeSelectModal from "./EmployeeSelectModal/EmployeeSelectModal.tsx";
 | 
			
		||||
import EmployeeTableModal from "./EmployeeTableModal/EmployeeTableModal.tsx";
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ const generateRows = (modules: Module[]) => {
 | 
			
		||||
        };
 | 
			
		||||
        const tsxContent = template(data);
 | 
			
		||||
        fs.writeFileSync(OUTPUT_PATH, tsxContent);
 | 
			
		||||
        console.log("File successfully generated.");
 | 
			
		||||
        console.log("ChatFile successfully generated.");
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        console.error(error);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,31 @@
 | 
			
		||||
import React, { createContext, FC, MutableRefObject, useContext, useEffect, useRef, useState } from "react";
 | 
			
		||||
import { ChatSchema, ChatService, MessageSchema } from "../../../client";
 | 
			
		||||
import { notifications } from "../../../shared/lib/notifications.ts";
 | 
			
		||||
import { MessageForm } from "../../../components/Chat/components/MessageInput/MessageInput.tsx";
 | 
			
		||||
import { UseFormReturnType } from "@mantine/form";
 | 
			
		||||
import { useDebouncedState } from "@mantine/hooks";
 | 
			
		||||
import { useForm, UseFormReturnType } from "@mantine/form";
 | 
			
		||||
import { useDebouncedState, useFileDialog, useListState, UseListStateHandlers } from "@mantine/hooks";
 | 
			
		||||
 | 
			
		||||
export type MessageForm = {
 | 
			
		||||
    message: string;
 | 
			
		||||
    messageId?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FileDialog = {
 | 
			
		||||
    files: FileList | null;
 | 
			
		||||
    open: () => void;
 | 
			
		||||
    reset: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChatContextState = {
 | 
			
		||||
    chat: ChatSchema | null;
 | 
			
		||||
    setChat: (chat: ChatSchema | null) => void;
 | 
			
		||||
    messages: MessageSchema[];
 | 
			
		||||
    form: UseFormReturnType<MessageForm>;
 | 
			
		||||
    onScrollPositionChange: (values: { x: number, y: number }) => void;
 | 
			
		||||
    scrollRef: MutableRefObject<HTMLDivElement | null>;
 | 
			
		||||
    sendMessage: (values: MessageForm, form: UseFormReturnType<MessageForm>) => void;
 | 
			
		||||
    submitMessage: (values: MessageForm) => void;
 | 
			
		||||
    files: Array<File>;
 | 
			
		||||
    filesHandlers: UseListStateHandlers<File>;
 | 
			
		||||
    fileDialog: FileDialog;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ChatContext = createContext<ChatContextState | undefined>(undefined);
 | 
			
		||||
@@ -26,17 +40,30 @@ const useChatContextState = () => {
 | 
			
		||||
    const [isScrollToBottom, setIsScrollToBottom] = useState(true);
 | 
			
		||||
    const [scrollPosition, setScrollPosition] = useDebouncedState<{ x: number, y: number }>({ x: 0, y: 0 }, 400);
 | 
			
		||||
 | 
			
		||||
    const [files, filesHandlers] = useListState<File>([]);
 | 
			
		||||
    const fileDialog = useFileDialog();
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        filesHandlers.setState(Array.from(fileDialog.files ?? []));
 | 
			
		||||
    }, [fileDialog.files]);
 | 
			
		||||
 | 
			
		||||
    const form = useForm<MessageForm>({
 | 
			
		||||
        initialValues: {
 | 
			
		||||
            message: "",
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const setChatValue = (chat: ChatSchema | null) => {
 | 
			
		||||
        if (chat) {
 | 
			
		||||
            setOffset(0);
 | 
			
		||||
            setHasMore(true);
 | 
			
		||||
            setScrollPosition({ x: 0, y: 0 });
 | 
			
		||||
            filesHandlers.setState([]);
 | 
			
		||||
            form.reset();
 | 
			
		||||
        }
 | 
			
		||||
        setChat(chat);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    console.log(`has more = ${hasMore}`);
 | 
			
		||||
 | 
			
		||||
    const fetchMessages = () => {
 | 
			
		||||
        if (!chat) return;
 | 
			
		||||
 | 
			
		||||
@@ -71,9 +98,7 @@ const useChatContextState = () => {
 | 
			
		||||
                setMessages((prev) => [...prev, ...newMessages]);
 | 
			
		||||
                setOffset((prev) => prev + limit);
 | 
			
		||||
                if (scrollRef.current) {
 | 
			
		||||
                    console.log("FETCH ON SCROLL")
 | 
			
		||||
                    const prevPosition = limit / offset * scrollRef.current.scrollHeight;
 | 
			
		||||
                    console.log(`PREV POSITION = ${prevPosition}`)
 | 
			
		||||
                    scrollRef.current.scrollTo({ top: prevPosition, behavior: "instant" });
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
@@ -91,25 +116,42 @@ const useChatContextState = () => {
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (scrollRef.current && isScrollToBottom && chat) {
 | 
			
		||||
            setIsScrollToBottom(false);
 | 
			
		||||
            console.log("Scroll to the bottom");
 | 
			
		||||
            console.log(`scrollHeight = ${scrollRef.current.scrollHeight}`);
 | 
			
		||||
            scrollRef.current.scrollTo({ top: scrollRef.current.scrollHeight, behavior: "smooth" });
 | 
			
		||||
        }
 | 
			
		||||
    }, [messages, isScrollToBottom]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!chat) return;
 | 
			
		||||
        console.log(`handleScroll ${Math.abs(scrollPosition.y - 200)}`);
 | 
			
		||||
        if (Math.abs(scrollPosition.y - 200) <= 200 && hasMore) {
 | 
			
		||||
            console.log("handleScroll WORK");
 | 
			
		||||
            fetchMessagesOnScroll();
 | 
			
		||||
        }
 | 
			
		||||
    }, [scrollPosition]);
 | 
			
		||||
 | 
			
		||||
    const sendMessage = (values: MessageForm, form: UseFormReturnType<MessageForm>) => {
 | 
			
		||||
    const sendMessageWithFiles = (values: MessageForm) => {
 | 
			
		||||
        if (!chat) return;
 | 
			
		||||
 | 
			
		||||
        ChatService.sendMessage({
 | 
			
		||||
        ChatService.sendMessagesWithFiles({
 | 
			
		||||
            formData: {
 | 
			
		||||
                files,
 | 
			
		||||
            },
 | 
			
		||||
            chatId: chat.id,
 | 
			
		||||
            caption: values.message,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                if (!ok) {
 | 
			
		||||
                    notifications.error({ message });
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                filesHandlers.setState([]);
 | 
			
		||||
                form.reset();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const sendTextMessage = (values: MessageForm) => {
 | 
			
		||||
        if (!chat) return;
 | 
			
		||||
 | 
			
		||||
        ChatService.sendTextMessage({
 | 
			
		||||
            requestBody: {
 | 
			
		||||
                message: {
 | 
			
		||||
                    text: values.message,
 | 
			
		||||
@@ -127,13 +169,49 @@ const useChatContextState = () => {
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const editMessage = (values: MessageForm) => {
 | 
			
		||||
        if (!chat) return;
 | 
			
		||||
 | 
			
		||||
        ChatService.editMessage({
 | 
			
		||||
            requestBody: {
 | 
			
		||||
                message: {
 | 
			
		||||
                    id: values.messageId!,
 | 
			
		||||
                    text: values.message,
 | 
			
		||||
                    chatId: chat.id,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                if (!ok) {
 | 
			
		||||
                    notifications.error({ message });
 | 
			
		||||
                }
 | 
			
		||||
                form.reset();
 | 
			
		||||
                setIsScrollToBottom(true);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const submitMessage = (values: MessageForm) => {
 | 
			
		||||
        if (values.messageId) {
 | 
			
		||||
            editMessage(values);
 | 
			
		||||
        } else if (files.length === 0) {
 | 
			
		||||
            sendTextMessage(values);
 | 
			
		||||
        } else {
 | 
			
		||||
            sendMessageWithFiles(values);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        chat,
 | 
			
		||||
        setChat: setChatValue,
 | 
			
		||||
        messages,
 | 
			
		||||
        form,
 | 
			
		||||
        onScrollPositionChange: setScrollPosition,
 | 
			
		||||
        scrollRef,
 | 
			
		||||
        sendMessage,
 | 
			
		||||
        submitMessage,
 | 
			
		||||
        files,
 | 
			
		||||
        filesHandlers,
 | 
			
		||||
        fileDialog,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user