Skip to content

Commit ec0afb5

Browse files
Fix/user date format in edit template (#1141)
2 parents 797d292 + 922fb3d commit ec0afb5

File tree

16 files changed

+239
-32
lines changed

16 files changed

+239
-32
lines changed

apps/api/src/app/shared/services/file/file.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { InvalidFileException } from '@shared/exceptions/invalid-file.exception'
1010
import { IExcelFileHeading } from '@shared/types/file.types';
1111

1212
export class ExcelFileService {
13-
async convertToCsv(file: Express.Multer.File, sheetName?: string): Promise<string> {
13+
async convertToCsv(file: Express.Multer.File, sheetName?: string, dateNF?: string): Promise<string> {
1414
return new Promise(async (resolve, reject) => {
1515
try {
16-
const wb = XLSX.read(file.buffer as any);
16+
const wb = XLSX.read(file.buffer as any, { dateNF });
1717
const ws = sheetName && wb.SheetNames.includes(sheetName) ? wb.Sheets[sheetName] : wb.Sheets[wb.SheetNames[0]];
1818
resolve(
1919
XLSX.utils.sheet_to_csv(ws, {

apps/api/src/app/template/dtos/template-response.dto.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,11 @@ export class TemplateResponseDto {
6666
@IsString()
6767
@IsDefined()
6868
mode: string;
69+
70+
@ApiPropertyOptional({
71+
description: 'Expected Date Format',
72+
})
73+
@IsString()
74+
@IsOptional()
75+
expectedDateFormat?: string;
6976
}

apps/api/src/app/template/dtos/update-template-request.dto.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,11 @@ export class UpdateTemplateRequestDto {
2424
@IsOptional()
2525
@IsEnum(IntegrationEnum)
2626
integration?: IntegrationEnum;
27+
28+
@ApiProperty({
29+
description: 'Expected Date Format',
30+
nullable: true,
31+
})
32+
@IsOptional()
33+
expectedDateFormat?: string;
2734
}

apps/api/src/app/template/template.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export class TemplateController {
223223
UpdateTemplateCommand.create({
224224
name: body.name,
225225
mode: body.mode,
226+
expectedDateFormat: body.expectedDateFormat,
226227
}),
227228
templateId
228229
);

apps/api/src/app/template/usecases/get-template-details/get-template-details.usecase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class GetTemplateDetails {
1717
}): Promise<TemplateResponseDto> {
1818
const template = await this.templateRepository.findOne(
1919
{ _id: _templateId, _projectId },
20-
'_projectId name sampleFileUrl _id totalUploads totalInvalidRecords totalRecords mode integration'
20+
'_projectId name sampleFileUrl _id totalUploads totalInvalidRecords totalRecords mode integration expectedDateFormat'
2121
);
2222
if (!template) {
2323
throw new DocumentNotFoundException('Template', _templateId);
@@ -33,6 +33,7 @@ export class GetTemplateDetails {
3333
totalRecords: template.totalRecords,
3434
mode: template.mode,
3535
integration: template.integration as IntegrationEnum,
36+
expectedDateFormat: template.expectedDateFormat,
3637
};
3738
}
3839
}

apps/api/src/app/template/usecases/update-template/update-template.command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,8 @@ export class UpdateTemplateCommand extends BaseCommand {
2020
@IsEnum(IntegrationEnum)
2121
@IsOptional()
2222
integration?: IntegrationEnum;
23+
24+
@IsString()
25+
@IsOptional()
26+
expectedDateFormat?: string;
2327
}

apps/api/src/app/upload/usecases/make-upload-entry/make-upload-entry.usecase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export class MakeUploadEntry {
6666
file.mimetype === FileMimeTypesEnum.EXCELM
6767
) {
6868
try {
69+
const template = await this.templateRepository.findOne({ _id: templateId }, 'expectedDateFormat');
6970
const fileService = new ExcelFileService();
7071
const opts = await fileService.getExcelRowsColumnsCount(file, selectedSheetName);
7172

@@ -77,7 +78,7 @@ export class MakeUploadEntry {
7778
}
7879

7980
this.analyzeLargeFile(opts, true, maxRecords);
80-
csvFile = await fileService.convertToCsv(file, selectedSheetName);
81+
csvFile = await fileService.convertToCsv(file, selectedSheetName, template?.expectedDateFormat);
8182
} catch (error) {
8283
if (error instanceof FileSizeException || error instanceof MaxRecordsExceededException) {
8384
throw error;

apps/web/components/imports/forms/UpdateImportForm.tsx

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
import { useEffect } from 'react';
2-
import { Stack, TextInput as Input } from '@mantine/core';
3-
import { useForm } from 'react-hook-form';
2+
import { Stack, TextInput as Input, Text, SegmentedControl, Box } from '@mantine/core';
3+
import { useForm, Controller } from 'react-hook-form';
44
import { useFocusTrap } from '@mantine/hooks';
55

66
import { Button } from '@ui/button';
7-
import { ITemplate } from '@impler/shared';
7+
import { ITemplate, TemplateModeEnum } from '@impler/shared';
8+
import { SAMPLE_DATE_FORMATS, VARIABLES } from '@config';
9+
import { MultiSelect } from '@ui/multi-select';
10+
import { validateDateFormatString } from '@shared/utils';
811

912
interface UpdateImportFormProps {
1013
data: ITemplate;
1114
onSubmit: (data: IUpdateTemplateData) => void;
15+
isAutoImportAvailable: boolean;
1216
}
1317

14-
export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) {
18+
export function UpdateImportForm({ onSubmit, data, isAutoImportAvailable }: UpdateImportFormProps) {
1519
const focusTrapRef = useFocusTrap();
1620
const {
1721
reset,
22+
control,
1823
register,
1924
handleSubmit,
2025
formState: { errors },
@@ -23,20 +28,101 @@ export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) {
2328
useEffect(() => {
2429
reset({
2530
name: data.name,
31+
mode: data.mode || TemplateModeEnum.MANUAL,
32+
expectedDateFormat: data.expectedDateFormat,
2633
});
2734
}, [data, reset]);
2835

36+
const handleFormSubmit = (formData: IUpdateTemplateData) => {
37+
onSubmit(formData);
38+
};
39+
2940
return (
30-
<form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}>
31-
<Stack spacing="sm">
41+
<form onSubmit={handleSubmit(handleFormSubmit)} ref={focusTrapRef}>
42+
<Stack spacing="lg">
3243
<Input
3344
autoFocus
3445
required
46+
label="Import Name"
3547
{...register('name')}
3648
error={errors.name?.message}
3749
placeholder="I want to import..."
50+
description="A descriptive name for this import"
3851
/>
39-
<Button type="submit" fullWidth>
52+
53+
<Box>
54+
<Text size="sm" weight={500} mb={4}>
55+
Import Mode
56+
</Text>
57+
<Text size="xs" color="dimmed" mb="xs">
58+
Choose whether this import is triggered manually or automatically
59+
</Text>
60+
<Controller
61+
name="mode"
62+
control={control}
63+
render={({ field }) => (
64+
<SegmentedControl
65+
fullWidth
66+
value={field.value || 'manual'}
67+
onChange={field.onChange}
68+
data={[
69+
{ label: 'Manual', value: 'manual' },
70+
{ label: 'Automatic', value: 'automatic', disabled: !isAutoImportAvailable },
71+
]}
72+
/>
73+
)}
74+
/>
75+
</Box>
76+
77+
<Box>
78+
<Controller
79+
name="expectedDateFormat"
80+
control={control}
81+
rules={{
82+
validate: (value) => {
83+
if (!value) return true;
84+
85+
const result = validateDateFormatString(value as string);
86+
if (typeof result === 'object' && 'isValid' in result) {
87+
return result.isValid ? true : result.error || 'Invalid date format';
88+
}
89+
90+
return result === true ? true : (result as string);
91+
},
92+
}}
93+
render={({ field, fieldState }) => (
94+
<MultiSelect
95+
creatable
96+
maxSelectedValues={VARIABLES.ONE}
97+
clearable
98+
searchable
99+
label="Date Formats"
100+
placeholder="Date Formats"
101+
description="Define the date format you expect in your import data."
102+
data={[
103+
...SAMPLE_DATE_FORMATS,
104+
...(field.value && !SAMPLE_DATE_FORMATS.includes(field.value) ? [field.value] : []),
105+
]}
106+
getCreateLabel={(query) => `Add "${query}"`}
107+
onCreate={(newItem) => {
108+
field.onChange(newItem);
109+
110+
return newItem;
111+
}}
112+
onChange={(value) => {
113+
field.onChange(value[0]);
114+
}}
115+
error={fieldState.error?.message}
116+
value={field.value ? [field.value] : []}
117+
/>
118+
)}
119+
/>
120+
<Text size="xs" color="dimmed" mt="xs">
121+
Example formats: DD/MM/YYYY, MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY
122+
</Text>
123+
</Box>
124+
125+
<Button type="submit" fullWidth mt="md">
40126
Update
41127
</Button>
42128
</Stack>

apps/web/config/constants.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ export const NOTIFICATION_KEYS = {
260260
ERROR_DELETING_INVITATION: 'INVITATION_DELETED',
261261
PERMISSION_DENIED_WHILE_DELETING_PROJECT: 'PERMISSION_DENIED_WHILE_DELETING_PROJECT',
262262
SUBSCRIPTION_FEATURE_NOT_INCLUDED_IN_CURRENT_PLAN: 'SUBSCRIPTION_FEATURE_NOT_INCLUDED_IN_CURRENT_PLAN',
263+
ERROR_UPDATING_IMPORT_TEMPLATE: 'ERROR_UPDATING_IMPORT_TEMPLATE',
263264
} as const;
264265

265266
export const ROUTES = {
@@ -881,3 +882,5 @@ export const defaultWidgetAppereanceThemeDark = {
881882
buttonShadow: '0px 4px 16px rgba(0, 0, 0, 0.6)',
882883
},
883884
};
885+
886+
export const SAMPLE_DATE_FORMATS = ['DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY'];

apps/web/hooks/useImportDetails.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function useImportDetails({ templateId }: useImportDetailProps) {
5151
[API_KEYS.TEMPLATES_LIST, profileInfo!._projectId],
5252
(oldData) => oldData?.map((item) => (item._id === data._id ? data : item))
5353
);
54-
queryClient.setQueryData<ITemplate>([API_KEYS.TEMPLATE_DETAILS, templateId], data);
54+
queryClient.invalidateQueries([API_KEYS.TEMPLATE_DETAILS, templateId]);
5555
notify(NOTIFICATION_KEYS.IMPORT_UPDATED);
5656
},
5757
}
@@ -111,7 +111,15 @@ export function useImportDetails({ templateId }: useImportDetailProps) {
111111
modalId: MODAL_KEYS.IMPORT_UPDATE,
112112
title: MODAL_TITLES.IMPORT_UPDATE,
113113

114-
children: <UpdateImportForm onSubmit={updateImport} data={templateData} />,
114+
children: (
115+
<UpdateImportForm
116+
onSubmit={updateImport}
117+
data={templateData}
118+
isAutoImportAvailable={meta?.AUTOMATIC_IMPORTS ? true : false}
119+
/>
120+
),
121+
size: 'xl',
122+
centered: true,
115123
});
116124
};
117125

0 commit comments

Comments
 (0)