Merge remote-tracking branch 'origin/master'
This commit is contained in:
		@@ -7,8 +7,6 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
 | 
			
		||||
export { OpenAPI } from './core/OpenAPI';
 | 
			
		||||
export type { OpenAPIConfig } from './core/OpenAPI';
 | 
			
		||||
 | 
			
		||||
export type { ActiveWorkShiftSchema } from './models/ActiveWorkShiftSchema';
 | 
			
		||||
export type { ActiveWorkShiftsResponse } from './models/ActiveWorkShiftsResponse';
 | 
			
		||||
export type { AuthLoginRequest } from './models/AuthLoginRequest';
 | 
			
		||||
export type { AuthLoginResponse } from './models/AuthLoginResponse';
 | 
			
		||||
export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema';
 | 
			
		||||
@@ -30,6 +28,7 @@ export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
 | 
			
		||||
export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema';
 | 
			
		||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
 | 
			
		||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
 | 
			
		||||
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';
 | 
			
		||||
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
 | 
			
		||||
@@ -151,6 +150,8 @@ export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWare
 | 
			
		||||
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
 | 
			
		||||
export type { ExpenseSchemaBase } from './models/ExpenseSchemaBase';
 | 
			
		||||
export type { ExpenseTagSchema } from './models/ExpenseTagSchema';
 | 
			
		||||
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
 | 
			
		||||
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
 | 
			
		||||
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
 | 
			
		||||
export type { FinishShiftResponse } from './models/FinishShiftResponse';
 | 
			
		||||
export type { GetAllBarcodeTemplateAttributesResponse } from './models/GetAllBarcodeTemplateAttributesResponse';
 | 
			
		||||
@@ -187,12 +188,14 @@ export type { GetProfitTableDataResponse } from './models/GetProfitTableDataResp
 | 
			
		||||
export type { GetServiceKitSchema } from './models/GetServiceKitSchema';
 | 
			
		||||
export type { GetTimeTrackingRecordsRequest } from './models/GetTimeTrackingRecordsRequest';
 | 
			
		||||
export type { GetTimeTrackingRecordsResponse } from './models/GetTimeTrackingRecordsResponse';
 | 
			
		||||
export type { GetWorkShiftsResponse } from './models/GetWorkShiftsResponse';
 | 
			
		||||
export type { GroupBillRequestSchema } from './models/GroupBillRequestSchema';
 | 
			
		||||
export type { HTTPValidationError } from './models/HTTPValidationError';
 | 
			
		||||
export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
 | 
			
		||||
export type { MarketplaceSchema } from './models/MarketplaceSchema';
 | 
			
		||||
export type { NotificationChannel } from './models/NotificationChannel';
 | 
			
		||||
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
 | 
			
		||||
export type { PassportImageSchema } from './models/PassportImageSchema';
 | 
			
		||||
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
 | 
			
		||||
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
 | 
			
		||||
export type { PayRateSchema } from './models/PayRateSchema';
 | 
			
		||||
@@ -246,6 +249,8 @@ export type { ServiceUpdateCategoryResponse } from './models/ServiceUpdateCatego
 | 
			
		||||
export type { ServiceUpdateRequest } from './models/ServiceUpdateRequest';
 | 
			
		||||
export type { ServiceUpdateResponse } from './models/ServiceUpdateResponse';
 | 
			
		||||
export type { ShippingWarehouseSchema } from './models/ShippingWarehouseSchema';
 | 
			
		||||
export type { StartPauseByShiftIdResponse } from './models/StartPauseByShiftIdResponse';
 | 
			
		||||
export type { StartPauseByUserIdResponse } from './models/StartPauseByUserIdResponse';
 | 
			
		||||
export type { StartShiftResponse } from './models/StartShiftResponse';
 | 
			
		||||
export type { SynchronizeMarketplaceRequest } from './models/SynchronizeMarketplaceRequest';
 | 
			
		||||
export type { TaskInfoResponse } from './models/TaskInfoResponse';
 | 
			
		||||
@@ -271,10 +276,13 @@ export type { UpdateTimeTrackingRecordRequest } from './models/UpdateTimeTrackin
 | 
			
		||||
export type { UpdateTimeTrackingRecordResponse } from './models/UpdateTimeTrackingRecordResponse';
 | 
			
		||||
export type { UpdateUserRequest } from './models/UpdateUserRequest';
 | 
			
		||||
export type { UpdateUserResponse } from './models/UpdateUserResponse';
 | 
			
		||||
export type { UploadPassportImageResponse } from './models/UploadPassportImageResponse';
 | 
			
		||||
export type { UserCreate } from './models/UserCreate';
 | 
			
		||||
export type { UserSchema } from './models/UserSchema';
 | 
			
		||||
export type { UserUpdate } from './models/UserUpdate';
 | 
			
		||||
export type { ValidationError } from './models/ValidationError';
 | 
			
		||||
export type { WorkShiftRowSchema } from './models/WorkShiftRowSchema';
 | 
			
		||||
export type { WorkShiftSchema } from './models/WorkShiftSchema';
 | 
			
		||||
 | 
			
		||||
export { AuthService } from './services/AuthService';
 | 
			
		||||
export { BarcodeService } from './services/BarcodeService';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { ActiveWorkShiftSchema } from './ActiveWorkShiftSchema';
 | 
			
		||||
export type ActiveWorkShiftsResponse = {
 | 
			
		||||
    shifts: Array<ActiveWorkShiftSchema>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/client/models/Body_upload_passport_image.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/client/models/Body_upload_passport_image.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_upload_passport_image = {
 | 
			
		||||
    upload_file: Blob;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/FinishPauseByShiftIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/FinishPauseByShiftIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type FinishPauseByShiftIdResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/FinishPauseByUserIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/FinishPauseByUserIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type FinishPauseByUserIdResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/client/models/GetWorkShiftsResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/models/GetWorkShiftsResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { PaginationInfoSchema } from './PaginationInfoSchema';
 | 
			
		||||
import type { WorkShiftRowSchema } from './WorkShiftRowSchema';
 | 
			
		||||
export type GetWorkShiftsResponse = {
 | 
			
		||||
    shifts: Array<WorkShiftRowSchema>;
 | 
			
		||||
    paginationInfo: PaginationInfoSchema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type PaginationInfoSchema = {
 | 
			
		||||
    totalPages?: number;
 | 
			
		||||
    totalItems?: number;
 | 
			
		||||
    totalPages: number;
 | 
			
		||||
    totalItems: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/PassportImageSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/PassportImageSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type PassportImageSchema = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    userId: number;
 | 
			
		||||
    imageUrl: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/StartPauseByShiftIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/StartPauseByShiftIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type StartPauseByShiftIdResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										9
									
								
								src/client/models/StartPauseByUserIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/StartPauseByUserIdResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type StartPauseByUserIdResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								src/client/models/UploadPassportImageResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/client/models/UploadPassportImageResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
export type UploadPassportImageResponse = {
 | 
			
		||||
    ok: boolean;
 | 
			
		||||
    message: string;
 | 
			
		||||
    imageUrl?: (string | null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { PassportImageSchema } from './PassportImageSchema';
 | 
			
		||||
import type { PayRateSchema } from './PayRateSchema';
 | 
			
		||||
export type UserCreate = {
 | 
			
		||||
    telegramId: number;
 | 
			
		||||
@@ -16,6 +17,8 @@ export type UserCreate = {
 | 
			
		||||
    isDeleted: boolean;
 | 
			
		||||
    roleKey: string;
 | 
			
		||||
    payRate?: (PayRateSchema | null);
 | 
			
		||||
    passportImageUrl?: (string | null);
 | 
			
		||||
    passportImages?: (Array<PassportImageSchema> | null);
 | 
			
		||||
    positionKey?: (string | null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { PassportImageSchema } from './PassportImageSchema';
 | 
			
		||||
import type { PayRateSchema } from './PayRateSchema';
 | 
			
		||||
import type { PositionSchema } from './PositionSchema';
 | 
			
		||||
import type { RoleSchema } from './RoleSchema';
 | 
			
		||||
@@ -18,6 +19,8 @@ export type UserSchema = {
 | 
			
		||||
    isDeleted: boolean;
 | 
			
		||||
    roleKey: string;
 | 
			
		||||
    payRate?: (PayRateSchema | null);
 | 
			
		||||
    passportImageUrl?: (string | null);
 | 
			
		||||
    passportImages?: (Array<PassportImageSchema> | null);
 | 
			
		||||
    id: number;
 | 
			
		||||
    role: RoleSchema;
 | 
			
		||||
    position?: (PositionSchema | null);
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { PassportImageSchema } from './PassportImageSchema';
 | 
			
		||||
import type { PayRateSchema } from './PayRateSchema';
 | 
			
		||||
export type UserUpdate = {
 | 
			
		||||
    telegramId: number;
 | 
			
		||||
@@ -16,6 +17,8 @@ export type UserUpdate = {
 | 
			
		||||
    isDeleted: boolean;
 | 
			
		||||
    roleKey: string;
 | 
			
		||||
    payRate?: (PayRateSchema | null);
 | 
			
		||||
    passportImageUrl?: (string | null);
 | 
			
		||||
    passportImages?: (Array<PassportImageSchema> | null);
 | 
			
		||||
    id: number;
 | 
			
		||||
    positionKey?: (string | null);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								src/client/models/WorkShiftRowSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/client/models/WorkShiftRowSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
/* generated using openapi-typescript-codegen -- do not edit */
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { WorkShiftSchema } from './WorkShiftSchema';
 | 
			
		||||
export type WorkShiftRowSchema = {
 | 
			
		||||
    workShift: WorkShiftSchema;
 | 
			
		||||
    totalHours?: (number | null);
 | 
			
		||||
    pauseHours?: (number | null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -3,9 +3,11 @@
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { UserSchema } from './UserSchema';
 | 
			
		||||
export type ActiveWorkShiftSchema = {
 | 
			
		||||
export type WorkShiftSchema = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    startedAt: string;
 | 
			
		||||
    finishedAt?: (string | null);
 | 
			
		||||
    isPaused?: (boolean | null);
 | 
			
		||||
    user: UserSchema;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -2,12 +2,14 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { Body_upload_passport_image } from '../models/Body_upload_passport_image';
 | 
			
		||||
import type { CreateUserRequest } from '../models/CreateUserRequest';
 | 
			
		||||
import type { CreateUserResponse } from '../models/CreateUserResponse';
 | 
			
		||||
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
 | 
			
		||||
import type { GetManagersResponse } from '../models/GetManagersResponse';
 | 
			
		||||
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
 | 
			
		||||
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
 | 
			
		||||
import type { UploadPassportImageResponse } from '../models/UploadPassportImageResponse';
 | 
			
		||||
import type { CancelablePromise } from '../core/CancelablePromise';
 | 
			
		||||
import { OpenAPI } from '../core/OpenAPI';
 | 
			
		||||
import { request as __request } from '../core/request';
 | 
			
		||||
@@ -74,4 +76,29 @@ export class UserService {
 | 
			
		||||
            url: '/user/get-managers',
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Upload Passport Image
 | 
			
		||||
     * @returns UploadPassportImageResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static uploadPassportImage({
 | 
			
		||||
        userId,
 | 
			
		||||
        formData,
 | 
			
		||||
    }: {
 | 
			
		||||
        userId: number,
 | 
			
		||||
        formData: Body_upload_passport_image,
 | 
			
		||||
    }): CancelablePromise<UploadPassportImageResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/user/passport-images/upload/{user_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'user_id': userId,
 | 
			
		||||
            },
 | 
			
		||||
            formData: formData,
 | 
			
		||||
            mediaType: 'multipart/form-data',
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,14 @@
 | 
			
		||||
/* istanbul ignore file */
 | 
			
		||||
/* tslint:disable */
 | 
			
		||||
/* eslint-disable */
 | 
			
		||||
import type { ActiveWorkShiftsResponse } from '../models/ActiveWorkShiftsResponse';
 | 
			
		||||
import type { DeleteShiftResponse } from '../models/DeleteShiftResponse';
 | 
			
		||||
import type { FinishPauseByShiftIdResponse } from '../models/FinishPauseByShiftIdResponse';
 | 
			
		||||
import type { FinishPauseByUserIdResponse } from '../models/FinishPauseByUserIdResponse';
 | 
			
		||||
import type { FinishShiftByIdResponse } from '../models/FinishShiftByIdResponse';
 | 
			
		||||
import type { FinishShiftResponse } from '../models/FinishShiftResponse';
 | 
			
		||||
import type { GetWorkShiftsResponse } from '../models/GetWorkShiftsResponse';
 | 
			
		||||
import type { StartPauseByShiftIdResponse } from '../models/StartPauseByShiftIdResponse';
 | 
			
		||||
import type { StartPauseByUserIdResponse } from '../models/StartPauseByUserIdResponse';
 | 
			
		||||
import type { StartShiftResponse } from '../models/StartShiftResponse';
 | 
			
		||||
import type { CancelablePromise } from '../core/CancelablePromise';
 | 
			
		||||
import { OpenAPI } from '../core/OpenAPI';
 | 
			
		||||
@@ -96,14 +100,32 @@ export class WorkShiftsService {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get Active Shifts
 | 
			
		||||
     * @returns ActiveWorkShiftsResponse Successful Response
 | 
			
		||||
     * Get Shifts
 | 
			
		||||
     * @returns GetWorkShiftsResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static getActiveShifts(): CancelablePromise<ActiveWorkShiftsResponse> {
 | 
			
		||||
    public static getShifts({
 | 
			
		||||
        isActive,
 | 
			
		||||
        page,
 | 
			
		||||
        itemsPerPage,
 | 
			
		||||
    }: {
 | 
			
		||||
        isActive: boolean,
 | 
			
		||||
        page?: (number | null),
 | 
			
		||||
        itemsPerPage?: (number | null),
 | 
			
		||||
    }): CancelablePromise<GetWorkShiftsResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            url: '/work-shifts/get-active-shifts',
 | 
			
		||||
            url: '/work-shifts/get-shifts/{is_active}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'is_active': isActive,
 | 
			
		||||
            },
 | 
			
		||||
            query: {
 | 
			
		||||
                'page': page,
 | 
			
		||||
                'items_per_page': itemsPerPage,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
@@ -127,4 +149,88 @@ export class WorkShiftsService {
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Start Pause By Shift Id
 | 
			
		||||
     * @returns StartPauseByShiftIdResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static startPauseByShiftId({
 | 
			
		||||
        shiftId,
 | 
			
		||||
    }: {
 | 
			
		||||
        shiftId: number,
 | 
			
		||||
    }): CancelablePromise<StartPauseByShiftIdResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/work-shifts/pause/start/{shift_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'shift_id': shiftId,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Start Pause By User Id
 | 
			
		||||
     * @returns StartPauseByUserIdResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static startPauseByUserId({
 | 
			
		||||
        userId,
 | 
			
		||||
    }: {
 | 
			
		||||
        userId: number,
 | 
			
		||||
    }): CancelablePromise<StartPauseByUserIdResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/work-shifts/pause/start/for-user/{user_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'user_id': userId,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Finish Pause By Shift Id
 | 
			
		||||
     * @returns FinishPauseByShiftIdResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static finishPauseByShiftId({
 | 
			
		||||
        shiftId,
 | 
			
		||||
    }: {
 | 
			
		||||
        shiftId: number,
 | 
			
		||||
    }): CancelablePromise<FinishPauseByShiftIdResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/work-shifts/pause/finish/{shift_id}',
 | 
			
		||||
            path: {
 | 
			
		||||
                'shift_id': shiftId,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Finish Pause By User Id
 | 
			
		||||
     * @returns FinishPauseByUserIdResponse Successful Response
 | 
			
		||||
     * @throws ApiError
 | 
			
		||||
     */
 | 
			
		||||
    public static finishPauseByUserId({
 | 
			
		||||
        userId,
 | 
			
		||||
    }: {
 | 
			
		||||
        userId: number,
 | 
			
		||||
    }): CancelablePromise<FinishPauseByUserIdResponse> {
 | 
			
		||||
        return __request(OpenAPI, {
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            url: '/work-shifts/pause/finish/for-user/{shift_id}',
 | 
			
		||||
            query: {
 | 
			
		||||
                'user_id': userId,
 | 
			
		||||
            },
 | 
			
		||||
            errors: {
 | 
			
		||||
                422: `Validation Error`,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,77 +1,34 @@
 | 
			
		||||
import { Dropzone, DropzoneProps, FileWithPath } from "@mantine/dropzone";
 | 
			
		||||
import { FC, useState } from "react";
 | 
			
		||||
import {
 | 
			
		||||
    Button,
 | 
			
		||||
    Fieldset,
 | 
			
		||||
    Flex,
 | 
			
		||||
    Group,
 | 
			
		||||
    Image,
 | 
			
		||||
    Loader,
 | 
			
		||||
    rem,
 | 
			
		||||
    Text,
 | 
			
		||||
} from "@mantine/core";
 | 
			
		||||
import { FC } from "react";
 | 
			
		||||
import { Button, Fieldset, Flex, Group, Image, Loader, rem, Text } from "@mantine/core";
 | 
			
		||||
import { IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
 | 
			
		||||
import { omit } from "lodash";
 | 
			
		||||
import { BaseFormInputProps } from "../../types/utils.ts";
 | 
			
		||||
import { notifications } from "../../shared/lib/notifications.ts";
 | 
			
		||||
import { ProductService } from "../../client";
 | 
			
		||||
import UseImageDropzone from "../../types/UseImageDropzone.tsx";
 | 
			
		||||
 | 
			
		||||
interface RestProps {
 | 
			
		||||
    imageUrlInputProps?: BaseFormInputProps<string>;
 | 
			
		||||
    productId?: number;
 | 
			
		||||
    imageDropzone: UseImageDropzone;
 | 
			
		||||
    onDrop: (files: FileWithPath[]) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
 | 
			
		||||
 | 
			
		||||
const ImageDropzone: FC<Props> = (props: Props) => {
 | 
			
		||||
    const [showDropzone, setShowDropzone] = useState(
 | 
			
		||||
        !(
 | 
			
		||||
            typeof props.imageUrlInputProps?.value === "string" &&
 | 
			
		||||
            props.imageUrlInputProps.value.trim() !== ""
 | 
			
		||||
        )
 | 
			
		||||
    );
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
    const {
 | 
			
		||||
        showDropzone,
 | 
			
		||||
        setShowDropzone,
 | 
			
		||||
        isLoading,
 | 
			
		||||
        imageUrlInputProps,
 | 
			
		||||
    } = props.imageDropzone;
 | 
			
		||||
 | 
			
		||||
    const restProps = omit(props, [
 | 
			
		||||
        "imageUrl",
 | 
			
		||||
        "productId",
 | 
			
		||||
        "imageUrlInputProps",
 | 
			
		||||
    ]);
 | 
			
		||||
    const onDrop = (files: FileWithPath[]) => {
 | 
			
		||||
        if (!props.productId || !props.imageUrlInputProps) return;
 | 
			
		||||
        if (files.length > 1) {
 | 
			
		||||
            notifications.error({ message: "Прикрепите одно изображение" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const file = files[0];
 | 
			
		||||
        // TODO check if file is image
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        ProductService.uploadProductImage({
 | 
			
		||||
            productId: props.productId,
 | 
			
		||||
            formData: {
 | 
			
		||||
                upload_file: file,
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message, imageUrl }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
 | 
			
		||||
                if (!ok || !imageUrl) {
 | 
			
		||||
                    setShowDropzone(true);
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                props.imageUrlInputProps?.onChange(imageUrl);
 | 
			
		||||
                setShowDropzone(false);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(error => {
 | 
			
		||||
                notifications.error({ message: error.toString() });
 | 
			
		||||
                setShowDropzone(true);
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
    const getBody = () => {
 | 
			
		||||
        return props.imageUrlInputProps?.value && !showDropzone ? (
 | 
			
		||||
            <Image src={props.imageUrlInputProps.value} />
 | 
			
		||||
        return imageUrlInputProps?.value && !showDropzone ? (
 | 
			
		||||
            <Image src={imageUrlInputProps.value} />
 | 
			
		||||
        ) : (
 | 
			
		||||
            <Dropzone
 | 
			
		||||
                {...restProps}
 | 
			
		||||
@@ -87,7 +44,7 @@ const ImageDropzone: FC<Props> = (props: Props) => {
 | 
			
		||||
                    "image/heic",
 | 
			
		||||
                ]}
 | 
			
		||||
                multiple={false}
 | 
			
		||||
                onDrop={onDrop}>
 | 
			
		||||
                onDrop={props.onDrop}>
 | 
			
		||||
                <Group
 | 
			
		||||
                    justify="center"
 | 
			
		||||
                    gap="xl"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								src/hooks/useImageDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/hooks/useImageDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { BaseFormInputProps } from "../types/utils.ts";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    imageUrlInputProps?: BaseFormInputProps<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useImageDropzone = ({ imageUrlInputProps }: Props) => {
 | 
			
		||||
    const [showDropzone, setShowDropzone] = useState(
 | 
			
		||||
        !(
 | 
			
		||||
            typeof imageUrlInputProps?.value === "string" &&
 | 
			
		||||
            imageUrlInputProps.value.trim() !== ""
 | 
			
		||||
        ),
 | 
			
		||||
    );
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        showDropzone,
 | 
			
		||||
        setShowDropzone,
 | 
			
		||||
        isLoading,
 | 
			
		||||
        setIsLoading,
 | 
			
		||||
        imageUrlInputProps,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useImageDropzone;
 | 
			
		||||
@@ -1,116 +0,0 @@
 | 
			
		||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
 | 
			
		||||
import { useActiveShiftsTableColumns } from "./columns.tsx";
 | 
			
		||||
import { ActionIcon, Flex, Stack, Text, Title, Tooltip } from "@mantine/core";
 | 
			
		||||
import { IconCheck, IconTrash } from "@tabler/icons-react";
 | 
			
		||||
import { ActiveWorkShiftSchema, WorkShiftsService } from "../../../../client";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import { formatDate } from "../../../../types/utils.ts";
 | 
			
		||||
import { MRT_TableOptions } from "mantine-react-table";
 | 
			
		||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    activeShifts: ActiveWorkShiftSchema[];
 | 
			
		||||
    fetchActiveShifts: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ActiveShiftsTable = ({ activeShifts, fetchActiveShifts }: Props) => {
 | 
			
		||||
    const columns = useActiveShiftsTableColumns();
 | 
			
		||||
 | 
			
		||||
    const onDelete = (workShift: ActiveWorkShiftSchema) => {
 | 
			
		||||
        WorkShiftsService.deleteWorkShift({
 | 
			
		||||
            shiftId: workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchActiveShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => {
 | 
			
		||||
                console.log(err);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onDeleteClick = (workShift: ActiveWorkShiftSchema) => {
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            title: "Удаление записи о начале смены",
 | 
			
		||||
            children: (
 | 
			
		||||
                <Text size="sm">
 | 
			
		||||
                    Вы уверены что хотите удалить запись о начале смены работника{" "}
 | 
			
		||||
                    {workShift.user.firstName} {workShift.user.secondName} от{" "}
 | 
			
		||||
                    {formatDate(workShift.startedAt)}
 | 
			
		||||
                </Text>
 | 
			
		||||
            ),
 | 
			
		||||
            labels: { confirm: "Да", cancel: "Нет" },
 | 
			
		||||
            confirmProps: { color: "red" },
 | 
			
		||||
            onConfirm: () => onDelete(workShift),
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftFinish = (workShift: ActiveWorkShiftSchema) => {
 | 
			
		||||
        WorkShiftsService.finishWorkShiftById({
 | 
			
		||||
            shiftId: workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchActiveShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const onShiftFinishClick = (workShift: ActiveWorkShiftSchema) => {
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            title: "Завершение смены",
 | 
			
		||||
            children: (
 | 
			
		||||
                <Text size="sm">
 | 
			
		||||
                    Вы уверены что хотите завершить смену работника{" "}
 | 
			
		||||
                    {workShift.user.firstName} {workShift.user.secondName} от{" "}
 | 
			
		||||
                    {formatDate(workShift.startedAt)}
 | 
			
		||||
                </Text>
 | 
			
		||||
            ),
 | 
			
		||||
            labels: { confirm: "Да", cancel: "Нет" },
 | 
			
		||||
            confirmProps: { color: "red" },
 | 
			
		||||
            onConfirm: () => onShiftFinish(workShift),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Stack mx={"xs"}>
 | 
			
		||||
            <Title order={4} mt={"md"}>
 | 
			
		||||
                Активные смены
 | 
			
		||||
            </Title>
 | 
			
		||||
            <BaseTable
 | 
			
		||||
                data={activeShifts}
 | 
			
		||||
                columns={columns}
 | 
			
		||||
                restProps={
 | 
			
		||||
                    {
 | 
			
		||||
                        enableRowActions: true,
 | 
			
		||||
                        enableSorting: true,
 | 
			
		||||
                        enableColumnActions: false,
 | 
			
		||||
                        renderRowActions: ({ row }) => (
 | 
			
		||||
                            <Flex gap="md">
 | 
			
		||||
                                <Tooltip label="Удалить">
 | 
			
		||||
                                    <ActionIcon
 | 
			
		||||
                                        onClick={() =>
 | 
			
		||||
                                            onDeleteClick(row.original)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        variant={"default"}>
 | 
			
		||||
                                        <IconTrash />
 | 
			
		||||
                                    </ActionIcon>
 | 
			
		||||
                                </Tooltip>
 | 
			
		||||
                                <Tooltip label="Завершить смену">
 | 
			
		||||
                                    <ActionIcon
 | 
			
		||||
                                        onClick={() =>
 | 
			
		||||
                                            onShiftFinishClick(row.original)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        variant={"default"}>
 | 
			
		||||
                                        <IconCheck />
 | 
			
		||||
                                    </ActionIcon>
 | 
			
		||||
                                </Tooltip>
 | 
			
		||||
                            </Flex>
 | 
			
		||||
                        ),
 | 
			
		||||
                    } as MRT_TableOptions<ActiveWorkShiftSchema>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
        </Stack>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import { MRT_ColumnDef } from "mantine-react-table";
 | 
			
		||||
import { ActiveWorkShiftSchema } from "../../../../client";
 | 
			
		||||
 | 
			
		||||
export const useActiveShiftsTableColumns = () => {
 | 
			
		||||
    return useMemo<MRT_ColumnDef<ActiveWorkShiftSchema>[]>(
 | 
			
		||||
        () => [
 | 
			
		||||
            {
 | 
			
		||||
                header: "Начало смены",
 | 
			
		||||
                accessorKey: "startedAt",
 | 
			
		||||
                Cell: ({ row }) =>
 | 
			
		||||
                    new Date(row.original.startedAt).toLocaleString("ru-RU"),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "ФИО",
 | 
			
		||||
                Cell: ({ row }) =>
 | 
			
		||||
                    `${row.original.user.firstName} ${row.original.user.secondName}`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Роль",
 | 
			
		||||
                accessorKey: "user.role.name",
 | 
			
		||||
                enableSorting: false,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Должность",
 | 
			
		||||
                accessorKey: "user.position.name",
 | 
			
		||||
                enableSorting: false,
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        []
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,60 @@
 | 
			
		||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
 | 
			
		||||
import { FC } from "react";
 | 
			
		||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { UserService } from "../../../../client";
 | 
			
		||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
 | 
			
		||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
 | 
			
		||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
 | 
			
		||||
 | 
			
		||||
interface RestProps {
 | 
			
		||||
    imageUrlInputProps?: BaseFormInputProps<string>;
 | 
			
		||||
    userId?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
 | 
			
		||||
 | 
			
		||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, userId }: Props) => {
 | 
			
		||||
    const imageDropzoneProps = useImageDropzone({
 | 
			
		||||
        imageUrlInputProps,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const onDrop = (files: FileWithPath[]) => {
 | 
			
		||||
        if (!userId || !imageUrlInputProps) return;
 | 
			
		||||
        if (files.length > 1) {
 | 
			
		||||
            notifications.error({ message: "Прикрепите одно изображение" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const { setIsLoading, setShowDropzone } = imageDropzoneProps;
 | 
			
		||||
        const file = files[0];
 | 
			
		||||
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        UserService.uploadPassportImage({
 | 
			
		||||
            userId: userId,
 | 
			
		||||
            formData: {
 | 
			
		||||
                upload_file: file,
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message, imageUrl }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
 | 
			
		||||
                if (!ok || !imageUrl) {
 | 
			
		||||
                    setShowDropzone(true);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                imageUrlInputProps?.onChange(imageUrl);
 | 
			
		||||
                setShowDropzone(false);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(error => {
 | 
			
		||||
                notifications.error({ message: error.toString() });
 | 
			
		||||
                setShowDropzone(true);
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ProductImageDropzone;
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
 | 
			
		||||
import { FC } from "react";
 | 
			
		||||
 | 
			
		||||
export enum ShiftsTableType {
 | 
			
		||||
    ACTIVE,
 | 
			
		||||
    HISTORY,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = Omit<SegmentedControlProps, "data">;
 | 
			
		||||
const data = [
 | 
			
		||||
    {
 | 
			
		||||
        label: "Активные смены",
 | 
			
		||||
        value: ShiftsTableType.ACTIVE.toString(),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        label: "Завершенные смены",
 | 
			
		||||
        value: ShiftsTableType.HISTORY.toString(),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const ShiftsTableSegmentedControl: FC<Props> = props => {
 | 
			
		||||
    return (
 | 
			
		||||
        <SegmentedControl
 | 
			
		||||
            data={data}
 | 
			
		||||
            {...props}
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -10,6 +10,8 @@ import { capitalize } from "lodash";
 | 
			
		||||
import { IMaskInput } from "react-imask";
 | 
			
		||||
import phone from "phone";
 | 
			
		||||
import PayRateSelect from "../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
 | 
			
		||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
 | 
			
		||||
import PassportImageDropzone from "../../components/PassportImageDropzone/PassportImageDropzone.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = CreateEditFormProps<UserSchema>;
 | 
			
		||||
const UserFormModal = ({
 | 
			
		||||
@@ -107,6 +109,10 @@ const UserFormModal = ({
 | 
			
		||||
                                    {...form.getInputProps("phoneNumber")}
 | 
			
		||||
                                />
 | 
			
		||||
                            </Input.Wrapper>
 | 
			
		||||
                        </Stack>
 | 
			
		||||
                    </Fieldset>
 | 
			
		||||
                    <Fieldset legend={"Паспортные данные"}>
 | 
			
		||||
                        <Stack>
 | 
			
		||||
                            <Input.Wrapper
 | 
			
		||||
                                label={"Серия и номер паспорта"}
 | 
			
		||||
                                error={form.getInputProps("passportData").error}>
 | 
			
		||||
@@ -117,6 +123,18 @@ const UserFormModal = ({
 | 
			
		||||
                                    {...form.getInputProps("passportData")}
 | 
			
		||||
                                />
 | 
			
		||||
                            </Input.Wrapper>
 | 
			
		||||
                            {
 | 
			
		||||
                                isEditing && (
 | 
			
		||||
                                    <PassportImageDropzone
 | 
			
		||||
                                        imageUrlInputProps={
 | 
			
		||||
                                            form.getInputProps(
 | 
			
		||||
                                                "passportImageUrl",
 | 
			
		||||
                                            ) as BaseFormInputProps<string>
 | 
			
		||||
                                        }
 | 
			
		||||
                                        userId={innerProps?.element.id}
 | 
			
		||||
                                    />
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        </Stack>
 | 
			
		||||
                    </Fieldset>
 | 
			
		||||
                    <Fieldset legend={"Роль и должность"}>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +1,52 @@
 | 
			
		||||
import { Button, Group, Stack } from "@mantine/core";
 | 
			
		||||
import { ActiveWorkShiftSchema, WorkShiftsService } from "../../../../client";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import { ActiveShiftsTable } from "../../components/ActiveShiftsTable/ActiveShiftsTable.tsx";
 | 
			
		||||
import { Divider, Flex, Pagination, rem, Skeleton, Stack } from "@mantine/core";
 | 
			
		||||
import { ShiftsTable } from "./components/ShiftsTable.tsx";
 | 
			
		||||
import {
 | 
			
		||||
    ShiftsTableSegmentedControl,
 | 
			
		||||
} from "../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx";
 | 
			
		||||
import useWorkShiftsTable from "./hooks/useWorkShiftsTable.tsx";
 | 
			
		||||
import WorkShiftInput from "./components/WorkShiftInput.tsx";
 | 
			
		||||
 | 
			
		||||
export const WorkShiftsTab = () => {
 | 
			
		||||
    let inputType: "StartShift" | "FinishShift" = "StartShift";
 | 
			
		||||
    const [activeShifts, setActiveShifts] = useState<ActiveWorkShiftSchema[]>([]);
 | 
			
		||||
 | 
			
		||||
    const fetchActiveShifts = () => {
 | 
			
		||||
        WorkShiftsService.getActiveShifts()
 | 
			
		||||
            .then(res => {
 | 
			
		||||
                setActiveShifts(res.shifts);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        fetchActiveShifts();
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const onInputFinish = (userIdInput: string) => {
 | 
			
		||||
        const userId = parseInt(userIdInput);
 | 
			
		||||
        if (isNaN(userId)) {
 | 
			
		||||
            notifications.error({ message: "Ошибка, некорректные данные в QR-коде" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (inputType === "StartShift") {
 | 
			
		||||
            WorkShiftsService.startShift({
 | 
			
		||||
                userId: userId!,
 | 
			
		||||
            })
 | 
			
		||||
                .then(async ({ ok, message }) => {
 | 
			
		||||
                    notifications.guess(ok, { message });
 | 
			
		||||
                    fetchActiveShifts();
 | 
			
		||||
                })
 | 
			
		||||
                .catch(err => console.log(err));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        WorkShiftsService.finishShift({
 | 
			
		||||
            userId: userId!,
 | 
			
		||||
        })
 | 
			
		||||
            .then(async ({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchActiveShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onScanningStart = () => {
 | 
			
		||||
        modals.openContextModal({
 | 
			
		||||
            modal: "scanningModal",
 | 
			
		||||
            innerProps: {
 | 
			
		||||
                label: "Отсканируйте QR-код",
 | 
			
		||||
                onScan: onInputFinish,
 | 
			
		||||
                closeOnScan: true,
 | 
			
		||||
            },
 | 
			
		||||
            withCloseButton: false,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftStart = () => {
 | 
			
		||||
        inputType = "StartShift";
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftFinish = () => {
 | 
			
		||||
        inputType = "FinishShift";
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
    const {
 | 
			
		||||
        shifts,
 | 
			
		||||
        shiftsTableType,
 | 
			
		||||
        setShiftsTableType,
 | 
			
		||||
        totalPages,
 | 
			
		||||
        page,
 | 
			
		||||
        setPage,
 | 
			
		||||
        fetchShifts,
 | 
			
		||||
        isLoading,
 | 
			
		||||
    } = useWorkShiftsTable();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Stack>
 | 
			
		||||
            <Group ml={"xs"} mt={"xs"}>
 | 
			
		||||
                <Button variant={"default"} onClick={onShiftStart}>
 | 
			
		||||
                    Начать смену
 | 
			
		||||
                </Button>
 | 
			
		||||
                <Button variant={"default"} onClick={onShiftFinish}>
 | 
			
		||||
                    Закончить смену
 | 
			
		||||
                </Button>
 | 
			
		||||
            </Group>
 | 
			
		||||
            <ActiveShiftsTable
 | 
			
		||||
                activeShifts={activeShifts}
 | 
			
		||||
                fetchActiveShifts={fetchActiveShifts}
 | 
			
		||||
        <Stack gap={0}>
 | 
			
		||||
            <WorkShiftInput fetchShifts={fetchShifts} />
 | 
			
		||||
            <Divider />
 | 
			
		||||
            <ShiftsTableSegmentedControl
 | 
			
		||||
                value={shiftsTableType.toString()}
 | 
			
		||||
                onChange={event => {
 | 
			
		||||
                    setPage(1);
 | 
			
		||||
                    setShiftsTableType(parseInt(event));
 | 
			
		||||
                }}
 | 
			
		||||
            />
 | 
			
		||||
            <Skeleton visible={isLoading}>
 | 
			
		||||
                <Flex gap={rem(10)} direction={"column"}>
 | 
			
		||||
                    <ShiftsTable
 | 
			
		||||
                        shiftsTableType={shiftsTableType}
 | 
			
		||||
                        shifts={shifts}
 | 
			
		||||
                        fetchShifts={fetchShifts}
 | 
			
		||||
                    />
 | 
			
		||||
                    {totalPages > 1 && (
 | 
			
		||||
                        <Pagination
 | 
			
		||||
                            style={{ alignSelf: "flex-end" }}
 | 
			
		||||
                            withEdges
 | 
			
		||||
                            onChange={event => setPage(event)}
 | 
			
		||||
                            value={page}
 | 
			
		||||
                            total={totalPages}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                </Flex>
 | 
			
		||||
            </Skeleton>
 | 
			
		||||
        </Stack>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										175
									
								
								src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								src/pages/AdminPage/tabs/WorkShifts/components/ShiftsTable.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,175 @@
 | 
			
		||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
 | 
			
		||||
import { useShiftsTableColumns } from "../hooks/columns.tsx";
 | 
			
		||||
import { ActionIcon, Flex, Text, Tooltip } from "@mantine/core";
 | 
			
		||||
import { IconCheck, IconPlayerPause, IconPlayerPlay, IconTrash } from "@tabler/icons-react";
 | 
			
		||||
import { WorkShiftRowSchema, WorkShiftsService } from "../../../../../client";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
import { formatDate } from "../../../../../types/utils.ts";
 | 
			
		||||
import { MRT_Row, MRT_TableOptions } from "mantine-react-table";
 | 
			
		||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { ShiftsTableType } from "../../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx";
 | 
			
		||||
import { ReactNode } from "react";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    shifts: WorkShiftRowSchema[];
 | 
			
		||||
    fetchShifts: () => void;
 | 
			
		||||
    shiftsTableType: ShiftsTableType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ShiftsTable = ({
 | 
			
		||||
                                shifts,
 | 
			
		||||
                                fetchShifts,
 | 
			
		||||
                                shiftsTableType,
 | 
			
		||||
                            }: Props) => {
 | 
			
		||||
    const isActiveShiftsTable = shiftsTableType === ShiftsTableType.ACTIVE;
 | 
			
		||||
    const columns = useShiftsTableColumns({ isActiveShiftsTable });
 | 
			
		||||
 | 
			
		||||
    const onDelete = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        WorkShiftsService.deleteWorkShift({
 | 
			
		||||
            shiftId: workShiftRow.workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onDeleteClick = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            title: "Удаление смены",
 | 
			
		||||
            children: (
 | 
			
		||||
                <Text size="sm">
 | 
			
		||||
                    Вы уверены что хотите удалить смену работника{" "}
 | 
			
		||||
                    {workShiftRow.workShift.user.firstName} {workShiftRow.workShift.user.secondName} от{" "}
 | 
			
		||||
                    {formatDate(workShiftRow.workShift.startedAt)}
 | 
			
		||||
                </Text>
 | 
			
		||||
            ),
 | 
			
		||||
            labels: { confirm: "Да", cancel: "Нет" },
 | 
			
		||||
            confirmProps: { color: "red" },
 | 
			
		||||
            onConfirm: () => onDelete(workShiftRow),
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftFinish = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        WorkShiftsService.finishWorkShiftById({
 | 
			
		||||
            shiftId: workShiftRow.workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftFinishClick = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        modals.openConfirmModal({
 | 
			
		||||
            title: "Завершение смены",
 | 
			
		||||
            children: (
 | 
			
		||||
                <Text size="sm">
 | 
			
		||||
                    Вы уверены что хотите завершить смену работника{" "}
 | 
			
		||||
                    {workShiftRow.workShift.user.firstName} {workShiftRow.workShift.user.secondName} от{" "}
 | 
			
		||||
                    {formatDate(workShiftRow.workShift.startedAt)}
 | 
			
		||||
                </Text>
 | 
			
		||||
            ),
 | 
			
		||||
            labels: { confirm: "Да", cancel: "Нет" },
 | 
			
		||||
            confirmProps: { color: "red" },
 | 
			
		||||
            onConfirm: () => onShiftFinish(workShiftRow),
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftPauseClick = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        WorkShiftsService.startPauseByShiftId({
 | 
			
		||||
            shiftId: workShiftRow.workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftResumeClick = (workShiftRow: WorkShiftRowSchema) => {
 | 
			
		||||
        WorkShiftsService.finishPauseByShiftId({
 | 
			
		||||
            shiftId: workShiftRow.workShift.id,
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getAction = (
 | 
			
		||||
        label: string,
 | 
			
		||||
        func: () => void,
 | 
			
		||||
        icon: ReactNode,
 | 
			
		||||
    ) => {
 | 
			
		||||
        return (
 | 
			
		||||
            <Tooltip label={label}>
 | 
			
		||||
                <ActionIcon
 | 
			
		||||
                    onClick={func}
 | 
			
		||||
                    variant={"default"}>
 | 
			
		||||
                    {icon}
 | 
			
		||||
                </ActionIcon>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getRowActions = (row: MRT_Row<WorkShiftRowSchema>) => {
 | 
			
		||||
        const actions = [
 | 
			
		||||
            getAction("Удалить", () => onDeleteClick(row.original), <IconTrash />),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isActiveShiftsTable) {
 | 
			
		||||
            actions.push(
 | 
			
		||||
                getAction(
 | 
			
		||||
                    "Завершить смену",
 | 
			
		||||
                    () => onShiftFinishClick(row.original),
 | 
			
		||||
                    <IconCheck />,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
            if (row.original.workShift.isPaused) {
 | 
			
		||||
                actions.push(
 | 
			
		||||
                    getAction(
 | 
			
		||||
                        "Продолжить смену",
 | 
			
		||||
                        () => onShiftResumeClick(row.original),
 | 
			
		||||
                        <IconPlayerPlay />,
 | 
			
		||||
                    ),
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                actions.push(
 | 
			
		||||
                    getAction(
 | 
			
		||||
                        "Поставить смену на паузу",
 | 
			
		||||
                        () => onShiftPauseClick(row.original),
 | 
			
		||||
                        <IconPlayerPause />,
 | 
			
		||||
                    ),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return actions;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <BaseTable
 | 
			
		||||
            data={shifts}
 | 
			
		||||
            columns={columns}
 | 
			
		||||
            restProps={
 | 
			
		||||
                {
 | 
			
		||||
                    enableRowActions: true,
 | 
			
		||||
                    enableSorting: false,
 | 
			
		||||
                    enableColumnActions: false,
 | 
			
		||||
                    renderRowActions: ({ row }) => {
 | 
			
		||||
                        return (
 | 
			
		||||
                            <Flex gap="md">
 | 
			
		||||
                                {...getRowActions(row)}
 | 
			
		||||
                            </Flex>
 | 
			
		||||
                        );
 | 
			
		||||
                    },
 | 
			
		||||
                } as MRT_TableOptions<WorkShiftRowSchema>
 | 
			
		||||
            }
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
import { Button, Group } from "@mantine/core";
 | 
			
		||||
import useWorkShiftInput from "../hooks/useWorkShiftInput.tsx";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    fetchShifts: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const WorkShiftInput = ({ fetchShifts }: Props) => {
 | 
			
		||||
    const {
 | 
			
		||||
        onShiftStart,
 | 
			
		||||
        onShiftFinish,
 | 
			
		||||
        onShiftResume,
 | 
			
		||||
        onShiftPause,
 | 
			
		||||
    } = useWorkShiftInput({ fetchShifts });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Group ml={"xs"} my={"xs"}>
 | 
			
		||||
            <Button variant={"default"} onClick={onShiftStart}>
 | 
			
		||||
                Начать смену
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button variant={"default"} onClick={onShiftFinish}>
 | 
			
		||||
                Закончить смену
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button variant={"default"} onClick={onShiftPause}>
 | 
			
		||||
                Начать перерыв
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button variant={"default"} onClick={onShiftResume}>
 | 
			
		||||
                Закончить перерыв
 | 
			
		||||
            </Button>
 | 
			
		||||
        </Group>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default WorkShiftInput;
 | 
			
		||||
							
								
								
									
										73
									
								
								src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/pages/AdminPage/tabs/WorkShifts/hooks/columns.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import { MRT_ColumnDef, MRT_Row } from "mantine-react-table";
 | 
			
		||||
import { WorkShiftRowSchema } from "../../../../../client";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    isActiveShiftsTable: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useShiftsTableColumns = ({ isActiveShiftsTable }: Props) => {
 | 
			
		||||
    const getWorkedHoursString = (seconds: number) => {
 | 
			
		||||
        const hours = Math.floor(seconds / 3_600);
 | 
			
		||||
        const minutes = Math.floor(seconds % 3_600 / 60);
 | 
			
		||||
        if (hours === 0) {
 | 
			
		||||
            return `${minutes} мин.`;
 | 
			
		||||
        }
 | 
			
		||||
        return `${hours} ч. ${minutes} мин.`;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getColumnsForHistory = () => {
 | 
			
		||||
        return isActiveShiftsTable ? [] : [
 | 
			
		||||
            {
 | 
			
		||||
                header: "Конец смены",
 | 
			
		||||
                accessorKey: "workShift.finishedAt",
 | 
			
		||||
                Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
 | 
			
		||||
                    row.original.workShift.finishedAt && new Date(row.original.workShift.finishedAt).toLocaleString("ru-RU"),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Длительность смены",
 | 
			
		||||
                accessorKey: "totalHours",
 | 
			
		||||
                Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
 | 
			
		||||
                    getWorkedHoursString(row.original.totalHours ?? 0),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Перерывы",
 | 
			
		||||
                accessorKey: "pauseHours",
 | 
			
		||||
                Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
 | 
			
		||||
                    getWorkedHoursString(row.original.pauseHours ?? 0),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Отработано",
 | 
			
		||||
                Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
 | 
			
		||||
                    getWorkedHoursString((row.original.totalHours ?? 0) - (row.original.pauseHours ?? 0)),
 | 
			
		||||
            },
 | 
			
		||||
        ] as MRT_ColumnDef<WorkShiftRowSchema>[];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return useMemo<MRT_ColumnDef<WorkShiftRowSchema>[]>(
 | 
			
		||||
        () => [
 | 
			
		||||
            {
 | 
			
		||||
                header: "ФИО",
 | 
			
		||||
                Cell: ({ row }: { row: MRT_Row<WorkShiftRowSchema> }) =>
 | 
			
		||||
                    `${row.original.workShift.user.firstName} ${row.original.workShift.user.secondName}`,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Роль",
 | 
			
		||||
                accessorKey: "workShift.user.role.name",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Должность",
 | 
			
		||||
                accessorKey: "workShift.user.position.name",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                header: "Начало смены",
 | 
			
		||||
                accessorKey: "workShift.startedAt",
 | 
			
		||||
                Cell: ({ row }) =>
 | 
			
		||||
                    new Date(row.original.workShift.startedAt).toLocaleString("ru-RU"),
 | 
			
		||||
            },
 | 
			
		||||
            ...getColumnsForHistory(),
 | 
			
		||||
        ],
 | 
			
		||||
        [isActiveShiftsTable],
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@@ -0,0 +1,81 @@
 | 
			
		||||
import { WorkShiftsService } from "../../../../../client";
 | 
			
		||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { modals } from "@mantine/modals";
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    fetchShifts: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum InputType {
 | 
			
		||||
    START_SHIFT,
 | 
			
		||||
    FINISH_SHIFT,
 | 
			
		||||
    RESUME_SHIFT,
 | 
			
		||||
    PAUSE_SHIFT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useWorkShiftInput = ({ fetchShifts }: Props) => {
 | 
			
		||||
    let inputType: InputType = InputType.START_SHIFT;
 | 
			
		||||
 | 
			
		||||
    const workShiftMethods = {
 | 
			
		||||
        [InputType.START_SHIFT]: WorkShiftsService.startShift,
 | 
			
		||||
        [InputType.FINISH_SHIFT]: WorkShiftsService.finishShift,
 | 
			
		||||
        [InputType.RESUME_SHIFT]: WorkShiftsService.finishPauseByUserId,
 | 
			
		||||
        [InputType.PAUSE_SHIFT]: WorkShiftsService.startPauseByUserId,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onInputFinish = (userIdInput: string) => {
 | 
			
		||||
        const userId = parseInt(userIdInput);
 | 
			
		||||
        if (isNaN(userId)) {
 | 
			
		||||
            notifications.error({ message: "Ошибка, некорректные данные в QR-коде" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        workShiftMethods[inputType]({ userId })
 | 
			
		||||
            .then(async ({ ok, message }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                fetchShifts();
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onScanningStart = () => {
 | 
			
		||||
        modals.openContextModal({
 | 
			
		||||
            modal: "scanningModal",
 | 
			
		||||
            innerProps: {
 | 
			
		||||
                label: "Отсканируйте QR-код",
 | 
			
		||||
                onScan: onInputFinish,
 | 
			
		||||
                closeOnScan: true,
 | 
			
		||||
            },
 | 
			
		||||
            withCloseButton: false,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftStart = () => {
 | 
			
		||||
        inputType = InputType.START_SHIFT;
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftFinish = () => {
 | 
			
		||||
        inputType = InputType.FINISH_SHIFT;
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftResume = () => {
 | 
			
		||||
        inputType = InputType.RESUME_SHIFT;
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onShiftPause = () => {
 | 
			
		||||
        inputType = InputType.PAUSE_SHIFT;
 | 
			
		||||
        onScanningStart();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        onShiftStart,
 | 
			
		||||
        onShiftFinish,
 | 
			
		||||
        onShiftResume,
 | 
			
		||||
        onShiftPause,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useWorkShiftInput;
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { WorkShiftRowSchema, WorkShiftsService } from "../../../../../client";
 | 
			
		||||
import { ShiftsTableType } from "../../../components/ShiftsTableSegmentedControl/ShiftsTableSegmentedControl.tsx";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const useWorkShiftsTable = () => {
 | 
			
		||||
    const [totalPages, setTotalPages] = useState(1);
 | 
			
		||||
    const [page, setPage] = useState(1);
 | 
			
		||||
    const [shifts, setShifts] = useState<WorkShiftRowSchema[]>([]);
 | 
			
		||||
    const [shiftsTableType, setShiftsTableType] = useState<ShiftsTableType>(ShiftsTableType.ACTIVE);
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const fetchShifts = () => {
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        WorkShiftsService.getShifts({
 | 
			
		||||
            isActive: shiftsTableType === ShiftsTableType.ACTIVE,
 | 
			
		||||
            page,
 | 
			
		||||
            itemsPerPage: 10,
 | 
			
		||||
        })
 | 
			
		||||
            .then(res => {
 | 
			
		||||
                setShifts(res.shifts);
 | 
			
		||||
                setTotalPages(res.paginationInfo.totalPages);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(err => console.log(err))
 | 
			
		||||
            .finally(() => setIsLoading(false));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        fetchShifts();
 | 
			
		||||
    }, [shiftsTableType, page]);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        shifts,
 | 
			
		||||
        shiftsTableType,
 | 
			
		||||
        setShiftsTableType,
 | 
			
		||||
        totalPages,
 | 
			
		||||
        page,
 | 
			
		||||
        setPage,
 | 
			
		||||
        fetchShifts,
 | 
			
		||||
        isLoading,
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useWorkShiftsTable;
 | 
			
		||||
@@ -0,0 +1,60 @@
 | 
			
		||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
 | 
			
		||||
import { FC } from "react";
 | 
			
		||||
import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
			
		||||
import { ProductService } from "../../../../client";
 | 
			
		||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
 | 
			
		||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
 | 
			
		||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
 | 
			
		||||
 | 
			
		||||
interface RestProps {
 | 
			
		||||
    imageUrlInputProps?: BaseFormInputProps<string>;
 | 
			
		||||
    productId?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
 | 
			
		||||
 | 
			
		||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, productId }: Props) => {
 | 
			
		||||
    const imageDropzoneProps = useImageDropzone({
 | 
			
		||||
        imageUrlInputProps,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const onDrop = (files: FileWithPath[]) => {
 | 
			
		||||
        if (!productId || !imageUrlInputProps) return;
 | 
			
		||||
        if (files.length > 1) {
 | 
			
		||||
            notifications.error({ message: "Прикрепите одно изображение" });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const { setIsLoading, setShowDropzone } = imageDropzoneProps;
 | 
			
		||||
        const file = files[0];
 | 
			
		||||
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        ProductService.uploadProductImage({
 | 
			
		||||
            productId,
 | 
			
		||||
            formData: {
 | 
			
		||||
                upload_file: file,
 | 
			
		||||
            },
 | 
			
		||||
        })
 | 
			
		||||
            .then(({ ok, message, imageUrl }) => {
 | 
			
		||||
                notifications.guess(ok, { message });
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
 | 
			
		||||
                if (!ok || !imageUrl) {
 | 
			
		||||
                    setShowDropzone(true);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                imageUrlInputProps?.onChange(imageUrl);
 | 
			
		||||
                setShowDropzone(false);
 | 
			
		||||
            })
 | 
			
		||||
            .catch(error => {
 | 
			
		||||
                notifications.error({ message: error.toString() });
 | 
			
		||||
                setShowDropzone(true);
 | 
			
		||||
                setIsLoading(false);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ProductImageDropzone;
 | 
			
		||||
@@ -4,9 +4,9 @@ import { useForm } from "@mantine/form";
 | 
			
		||||
import { BaseProduct, CreateProductRequest } from "../../types.ts";
 | 
			
		||||
import { ProductSchema } from "../../../../client";
 | 
			
		||||
import BarcodeTemplateSelect from "../../../../components/Selects/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx";
 | 
			
		||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
 | 
			
		||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
 | 
			
		||||
import BarcodeImageDropzone from "../../../../components/BarcodeImageDropzone/BarcodeImageDropzone.tsx";
 | 
			
		||||
import ProductImageDropzone from "../../components/ProductImageDropzone/ProductImageDropzone.tsx";
 | 
			
		||||
 | 
			
		||||
type CreateProps = {
 | 
			
		||||
    clientId: number;
 | 
			
		||||
@@ -117,7 +117,7 @@ const CreateProductModal = ({
 | 
			
		||||
                        isEditProps && (
 | 
			
		||||
                            // <Fieldset legend={"Изображение"}>
 | 
			
		||||
                            <>
 | 
			
		||||
                                <ImageDropzone
 | 
			
		||||
                                <ProductImageDropzone
 | 
			
		||||
                                    imageUrlInputProps={
 | 
			
		||||
                                        form.getInputProps(
 | 
			
		||||
                                            "imageUrl",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/types/UseImageDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/types/UseImageDropzone.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { Dispatch, SetStateAction } from "react";
 | 
			
		||||
import { BaseFormInputProps } from "./utils.ts";
 | 
			
		||||
 | 
			
		||||
type UseImageDropzone = {
 | 
			
		||||
    showDropzone: boolean;
 | 
			
		||||
    setShowDropzone: Dispatch<SetStateAction<boolean>>;
 | 
			
		||||
    isLoading: boolean;
 | 
			
		||||
    setIsLoading: Dispatch<SetStateAction<boolean>>;
 | 
			
		||||
    imageUrlInputProps?: BaseFormInputProps<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default UseImageDropzone;
 | 
			
		||||
@@ -45,7 +45,7 @@ export function ObjectStateToTableProps<T extends MRT_RowData>(
 | 
			
		||||
 | 
			
		||||
export const floatHoursToHoursAndMinutes = (hours: number): number[] => {
 | 
			
		||||
    const resHours = Math.floor(hours);
 | 
			
		||||
    const minutes = Math.round((hours - resHours) * 60);
 | 
			
		||||
    const minutes = Math.floor((hours - resHours) * 60);
 | 
			
		||||
    return [resHours, minutes];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user