Skip to content

Commit d03e2c4

Browse files
v1.9.3 (#1145)
2 parents 97da683 + a9340d2 commit d03e2c4

File tree

30 files changed

+272
-51
lines changed

30 files changed

+272
-51
lines changed

apps/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/api",
3-
"version": "1.9.2",
3+
"version": "1.9.3",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ 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, { cellDates: true, cellText: false });
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, {
2020
blankrows: false,
2121
skipHidden: true,
2222
forceQuotes: true,
23-
dateNF: Defaults.DATE_FORMAT.toLowerCase(),
2423
// rawNumbers: true, // was converting 12:12:12 to 1.3945645673
2524
})
2625
);

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/queue-manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@impler/queue-manager",
3-
"version": "1.9.2",
3+
"version": "1.9.3",
44
"author": "implerhq",
55
"license": "MIT",
66
"private": true,

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

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
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, Group, Flex } from '@mantine/core';
3+
import { LockIcon } from '@assets/icons/Lock.icon';
4+
import { useForm, Controller } from 'react-hook-form';
45
import { useFocusTrap } from '@mantine/hooks';
56

67
import { Button } from '@ui/button';
7-
import { ITemplate } from '@impler/shared';
8+
import { ITemplate, TemplateModeEnum } from '@impler/shared';
9+
import { ImportConfigEnum } from '@types';
10+
import { SAMPLE_DATE_FORMATS, VARIABLES } from '@config';
11+
import { MultiSelect } from '@ui/multi-select';
12+
import { validateDateFormatString } from '@shared/utils';
813

914
interface UpdateImportFormProps {
1015
data: ITemplate;
1116
onSubmit: (data: IUpdateTemplateData) => void;
17+
isAutoImportAvailable: boolean;
1218
}
1319

14-
export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) {
20+
export function UpdateImportForm({ onSubmit, data, isAutoImportAvailable }: UpdateImportFormProps) {
1521
const focusTrapRef = useFocusTrap();
1622
const {
1723
reset,
24+
control,
1825
register,
1926
handleSubmit,
2027
formState: { errors },
@@ -23,20 +30,114 @@ export function UpdateImportForm({ onSubmit, data }: UpdateImportFormProps) {
2330
useEffect(() => {
2431
reset({
2532
name: data.name,
33+
mode: data.mode || TemplateModeEnum.MANUAL,
34+
expectedDateFormat: data.expectedDateFormat,
2635
});
2736
}, [data, reset]);
2837

38+
const handleFormSubmit = (formData: IUpdateTemplateData) => {
39+
onSubmit(formData);
40+
};
41+
2942
return (
30-
<form onSubmit={handleSubmit(onSubmit)} ref={focusTrapRef}>
31-
<Stack spacing="sm">
43+
<form onSubmit={handleSubmit(handleFormSubmit)} ref={focusTrapRef}>
44+
<Stack spacing="lg">
3245
<Input
3346
autoFocus
3447
required
48+
label="Import Name"
3549
{...register('name')}
3650
error={errors.name?.message}
3751
placeholder="I want to import..."
52+
description="A descriptive name for this import"
3853
/>
39-
<Button type="submit" fullWidth>
54+
55+
<Box>
56+
<Text size="sm" weight={500} mb={4}>
57+
Import Mode
58+
</Text>
59+
<Text size="xs" color="dimmed" mb="xs">
60+
Choose whether this import is triggered manually or automatically
61+
</Text>
62+
<Controller
63+
name="mode"
64+
control={control}
65+
render={({ field }) => (
66+
<SegmentedControl
67+
fullWidth
68+
value={field.value || ImportConfigEnum.MANUAL}
69+
onChange={field.onChange}
70+
data={[
71+
{ label: 'Manual', value: ImportConfigEnum.MANUAL },
72+
{
73+
value: ImportConfigEnum.AUTOMATED,
74+
disabled: !isAutoImportAvailable,
75+
label: (
76+
<Group position="center" spacing={4}>
77+
{!isAutoImportAvailable && (
78+
<Flex>
79+
<LockIcon size="md" />
80+
</Flex>
81+
)}
82+
Automatic
83+
</Group>
84+
),
85+
},
86+
]}
87+
/>
88+
)}
89+
/>
90+
</Box>
91+
92+
<Box>
93+
<Controller
94+
name="expectedDateFormat"
95+
control={control}
96+
rules={{
97+
validate: (value) => {
98+
if (!value) return true;
99+
100+
const result = validateDateFormatString(value as string);
101+
if (typeof result === 'object' && 'isValid' in result) {
102+
return result.isValid ? true : result.error || 'Invalid date format';
103+
}
104+
105+
return result === true ? true : (result as string);
106+
},
107+
}}
108+
render={({ field, fieldState }) => (
109+
<MultiSelect
110+
creatable
111+
maxSelectedValues={VARIABLES.ONE}
112+
clearable
113+
searchable
114+
label="Date Formats"
115+
placeholder="Date Formats"
116+
description="Define the date format you expect in your import data."
117+
data={[
118+
...SAMPLE_DATE_FORMATS,
119+
...(field.value && !SAMPLE_DATE_FORMATS.includes(field.value) ? [field.value] : []),
120+
]}
121+
getCreateLabel={(query) => `Add "${query}"`}
122+
onCreate={(newItem) => {
123+
field.onChange(newItem);
124+
125+
return newItem;
126+
}}
127+
onChange={(value) => {
128+
field.onChange(value[0]);
129+
}}
130+
error={fieldState.error?.message}
131+
value={field.value ? [field.value] : []}
132+
/>
133+
)}
134+
/>
135+
<Text size="xs" color="dimmed" mt="xs">
136+
Example formats: DD/MM/YYYY, MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY
137+
</Text>
138+
</Box>
139+
140+
<Button type="submit" fullWidth mt="md">
40141
Update
41142
</Button>
42143
</Stack>

0 commit comments

Comments
 (0)