diff --git a/apps/api/package.json b/apps/api/package.json index 6867afe99..32e7a2fc7 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "@impler/api", - "version": "1.9.2", + "version": "1.9.3", "author": "implerhq", "license": "MIT", "private": true, diff --git a/apps/api/src/app/shared/services/file/file.service.ts b/apps/api/src/app/shared/services/file/file.service.ts index 0f9fa5005..6b40650f3 100644 --- a/apps/api/src/app/shared/services/file/file.service.ts +++ b/apps/api/src/app/shared/services/file/file.service.ts @@ -10,17 +10,16 @@ import { InvalidFileException } from '@shared/exceptions/invalid-file.exception' import { IExcelFileHeading } from '@shared/types/file.types'; export class ExcelFileService { - async convertToCsv(file: Express.Multer.File, sheetName?: string): Promise { + async convertToCsv(file: Express.Multer.File, sheetName?: string, dateNF?: string): Promise { return new Promise(async (resolve, reject) => { try { - const wb = XLSX.read(file.buffer as any, { cellDates: true, cellText: false }); + const wb = XLSX.read(file.buffer as any, { dateNF }); const ws = sheetName && wb.SheetNames.includes(sheetName) ? wb.Sheets[sheetName] : wb.Sheets[wb.SheetNames[0]]; resolve( XLSX.utils.sheet_to_csv(ws, { blankrows: false, skipHidden: true, forceQuotes: true, - dateNF: Defaults.DATE_FORMAT.toLowerCase(), // rawNumbers: true, // was converting 12:12:12 to 1.3945645673 }) ); diff --git a/apps/api/src/app/template/dtos/template-response.dto.ts b/apps/api/src/app/template/dtos/template-response.dto.ts index e1e8b616a..1231d0234 100644 --- a/apps/api/src/app/template/dtos/template-response.dto.ts +++ b/apps/api/src/app/template/dtos/template-response.dto.ts @@ -66,4 +66,11 @@ export class TemplateResponseDto { @IsString() @IsDefined() mode: string; + + @ApiPropertyOptional({ + description: 'Expected Date Format', + }) + @IsString() + @IsOptional() + expectedDateFormat?: string; } diff --git a/apps/api/src/app/template/dtos/update-template-request.dto.ts b/apps/api/src/app/template/dtos/update-template-request.dto.ts index cf0cd60f1..d6734b085 100644 --- a/apps/api/src/app/template/dtos/update-template-request.dto.ts +++ b/apps/api/src/app/template/dtos/update-template-request.dto.ts @@ -24,4 +24,11 @@ export class UpdateTemplateRequestDto { @IsOptional() @IsEnum(IntegrationEnum) integration?: IntegrationEnum; + + @ApiProperty({ + description: 'Expected Date Format', + nullable: true, + }) + @IsOptional() + expectedDateFormat?: string; } diff --git a/apps/api/src/app/template/template.controller.ts b/apps/api/src/app/template/template.controller.ts index 4d75d0a02..081730a83 100644 --- a/apps/api/src/app/template/template.controller.ts +++ b/apps/api/src/app/template/template.controller.ts @@ -223,6 +223,7 @@ export class TemplateController { UpdateTemplateCommand.create({ name: body.name, mode: body.mode, + expectedDateFormat: body.expectedDateFormat, }), templateId ); diff --git a/apps/api/src/app/template/usecases/get-template-details/get-template-details.usecase.ts b/apps/api/src/app/template/usecases/get-template-details/get-template-details.usecase.ts index 066bd18c6..21f22ee07 100644 --- a/apps/api/src/app/template/usecases/get-template-details/get-template-details.usecase.ts +++ b/apps/api/src/app/template/usecases/get-template-details/get-template-details.usecase.ts @@ -17,7 +17,7 @@ export class GetTemplateDetails { }): Promise { const template = await this.templateRepository.findOne( { _id: _templateId, _projectId }, - '_projectId name sampleFileUrl _id totalUploads totalInvalidRecords totalRecords mode integration' + '_projectId name sampleFileUrl _id totalUploads totalInvalidRecords totalRecords mode integration expectedDateFormat' ); if (!template) { throw new DocumentNotFoundException('Template', _templateId); @@ -33,6 +33,7 @@ export class GetTemplateDetails { totalRecords: template.totalRecords, mode: template.mode, integration: template.integration as IntegrationEnum, + expectedDateFormat: template.expectedDateFormat, }; } } diff --git a/apps/api/src/app/template/usecases/update-template/update-template.command.ts b/apps/api/src/app/template/usecases/update-template/update-template.command.ts index 371c62778..4c2f43b7b 100644 --- a/apps/api/src/app/template/usecases/update-template/update-template.command.ts +++ b/apps/api/src/app/template/usecases/update-template/update-template.command.ts @@ -20,4 +20,8 @@ export class UpdateTemplateCommand extends BaseCommand { @IsEnum(IntegrationEnum) @IsOptional() integration?: IntegrationEnum; + + @IsString() + @IsOptional() + expectedDateFormat?: string; } diff --git a/apps/api/src/app/upload/usecases/make-upload-entry/make-upload-entry.usecase.ts b/apps/api/src/app/upload/usecases/make-upload-entry/make-upload-entry.usecase.ts index b243643e7..65afbfeeb 100644 --- a/apps/api/src/app/upload/usecases/make-upload-entry/make-upload-entry.usecase.ts +++ b/apps/api/src/app/upload/usecases/make-upload-entry/make-upload-entry.usecase.ts @@ -66,6 +66,7 @@ export class MakeUploadEntry { file.mimetype === FileMimeTypesEnum.EXCELM ) { try { + const template = await this.templateRepository.findOne({ _id: templateId }, 'expectedDateFormat'); const fileService = new ExcelFileService(); const opts = await fileService.getExcelRowsColumnsCount(file, selectedSheetName); @@ -77,7 +78,7 @@ export class MakeUploadEntry { } this.analyzeLargeFile(opts, true, maxRecords); - csvFile = await fileService.convertToCsv(file, selectedSheetName); + csvFile = await fileService.convertToCsv(file, selectedSheetName, template?.expectedDateFormat); } catch (error) { if (error instanceof FileSizeException || error instanceof MaxRecordsExceededException) { throw error; diff --git a/apps/queue-manager/package.json b/apps/queue-manager/package.json index f6b1ebf75..27766fdb7 100644 --- a/apps/queue-manager/package.json +++ b/apps/queue-manager/package.json @@ -1,6 +1,6 @@ { "name": "@impler/queue-manager", - "version": "1.9.2", + "version": "1.9.3", "author": "implerhq", "license": "MIT", "private": true, diff --git a/apps/web/components/imports/forms/UpdateImportForm.tsx b/apps/web/components/imports/forms/UpdateImportForm.tsx index f8c1fa163..32f32bab7 100644 --- a/apps/web/components/imports/forms/UpdateImportForm.tsx +++ b/apps/web/components/imports/forms/UpdateImportForm.tsx @@ -1,20 +1,27 @@ import { useEffect } from 'react'; -import { Stack, TextInput as Input } from '@mantine/core'; -import { useForm } from 'react-hook-form'; +import { Stack, TextInput as Input, Text, SegmentedControl, Box, Group, Flex } from '@mantine/core'; +import { LockIcon } from '@assets/icons/Lock.icon'; +import { useForm, Controller } from 'react-hook-form'; import { useFocusTrap } from '@mantine/hooks'; import { Button } from '@ui/button'; -import { ITemplate } from '@impler/shared'; +import { ITemplate, TemplateModeEnum } from '@impler/shared'; +import { ImportConfigEnum } from '@types'; +import { SAMPLE_DATE_FORMATS, VARIABLES } from '@config'; +import { MultiSelect } from '@ui/multi-select'; +import { validateDateFormatString } from '@shared/utils'; interface UpdateImportFormProps { data: ITemplate; onSubmit: (data: IUpdateTemplateData) => void; + isAutoImportAvailable: boolean; } -export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) { +export function UpdateImportForm({ onSubmit, data, isAutoImportAvailable }: UpdateImportFormProps) { const focusTrapRef = useFocusTrap(); const { reset, + control, register, handleSubmit, formState: { errors }, @@ -23,20 +30,114 @@ export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) { useEffect(() => { reset({ name: data.name, + mode: data.mode || TemplateModeEnum.MANUAL, + expectedDateFormat: data.expectedDateFormat, }); }, [data, reset]); + const handleFormSubmit = (formData: IUpdateTemplateData) => { + onSubmit(formData); + }; + return ( -
- + + - diff --git a/apps/web/config/constants.config.ts b/apps/web/config/constants.config.ts index eb946f172..9b944f385 100644 --- a/apps/web/config/constants.config.ts +++ b/apps/web/config/constants.config.ts @@ -260,6 +260,7 @@ export const NOTIFICATION_KEYS = { ERROR_DELETING_INVITATION: 'INVITATION_DELETED', PERMISSION_DENIED_WHILE_DELETING_PROJECT: 'PERMISSION_DENIED_WHILE_DELETING_PROJECT', SUBSCRIPTION_FEATURE_NOT_INCLUDED_IN_CURRENT_PLAN: 'SUBSCRIPTION_FEATURE_NOT_INCLUDED_IN_CURRENT_PLAN', + ERROR_UPDATING_IMPORT_TEMPLATE: 'ERROR_UPDATING_IMPORT_TEMPLATE', } as const; export const ROUTES = { @@ -881,3 +882,5 @@ export const defaultWidgetAppereanceThemeDark = { buttonShadow: '0px 4px 16px rgba(0, 0, 0, 0.6)', }, }; + +export const SAMPLE_DATE_FORMATS = ['DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY']; diff --git a/apps/web/hooks/useImportDetails.tsx b/apps/web/hooks/useImportDetails.tsx index cb3679bf3..2757cf616 100644 --- a/apps/web/hooks/useImportDetails.tsx +++ b/apps/web/hooks/useImportDetails.tsx @@ -51,7 +51,7 @@ export function useImportDetails({ templateId }: useImportDetailProps) { [API_KEYS.TEMPLATES_LIST, profileInfo!._projectId], (oldData) => oldData?.map((item) => (item._id === data._id ? data : item)) ); - queryClient.setQueryData([API_KEYS.TEMPLATE_DETAILS, templateId], data); + queryClient.invalidateQueries([API_KEYS.TEMPLATE_DETAILS, templateId]); notify(NOTIFICATION_KEYS.IMPORT_UPDATED); }, } @@ -111,7 +111,15 @@ export function useImportDetails({ templateId }: useImportDetailProps) { modalId: MODAL_KEYS.IMPORT_UPDATE, title: MODAL_TITLES.IMPORT_UPDATE, - children: , + children: ( + + ), + size: 'xl', + centered: true, }); }; diff --git a/apps/web/package.json b/apps/web/package.json index 00a2208b9..a2d3ba572 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@impler/web", - "version": "1.9.2", + "version": "1.9.3", "author": "implerhq", "license": "MIT", "private": true, diff --git a/apps/web/pages/imports/[id].tsx b/apps/web/pages/imports/[id].tsx index 74d3543f4..ab67da19c 100644 --- a/apps/web/pages/imports/[id].tsx +++ b/apps/web/pages/imports/[id].tsx @@ -2,10 +2,10 @@ import Link from 'next/link'; import React, { useCallback, useEffect, useState } from 'react'; import dynamic from 'next/dynamic'; import { useRouter } from 'next/router'; -import { ActionIcon, Flex, Group, LoadingOverlay, Title, Select } from '@mantine/core'; +import { Flex, Group, LoadingOverlay, Title } from '@mantine/core'; import { track } from '@libs/amplitude'; -import { defaultWidgetAppereance, TemplateModeEnum } from '@impler/shared'; -import { CONSTANTS, IMPORT_MODES, MODAL_KEYS, ROUTES, SubjectsEnum, colors } from '@config'; +import { defaultWidgetAppereance } from '@impler/shared'; +import { CONSTANTS, MODAL_KEYS, ROUTES, SubjectsEnum, colors } from '@config'; import { useImportDetails } from '@hooks/useImportDetails'; import { Tabs } from '@ui/Tabs'; @@ -44,10 +44,8 @@ function ImportDetails() { const [activeTab, setActiveTab] = useState<'schema' | 'destination' | 'snippet' | 'validator' | 'output'>(); const { - meta, columns, profileInfo, - updateImport, templateData, onUpdateClick, onDeleteClick, @@ -149,23 +147,12 @@ function ImportDetails() { {templateData?.name} - - - -