Skip to content

Commit f8196ce

Browse files
committed
Refactor import organization components to TypeScript
- Converted ImportDialog, ImportFile, ImportFileGuide, ImportFileTable, ProgressBackdrop, and importValidation components from JavaScript to TypeScript. - Updated prop types and interfaces for better type safety and clarity. - Removed deprecated JavaScript files and replaced them with TypeScript counterparts. - Enhanced validation logic in importValidation to use async/await for improved readability. - Adjusted component structure and props to align with TypeScript standards.
1 parent 3a64727 commit f8196ce

7 files changed

Lines changed: 142 additions & 77 deletions

File tree

client/src/components/Admin/ImportOrganizations/ImportDialog.jsx renamed to client/src/components/Admin/ImportOrganizations/ImportDialog.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,32 @@ import {
1010
RadioGroup,
1111
Typography,
1212
} from "@mui/material";
13-
import PropTypes from "prop-types";
13+
type ImportAction = 'replace' | 'update' | 'add';
1414

15-
const optionDescriptions = {
15+
interface ImportData {
16+
action: ImportAction | '';
17+
}
18+
19+
interface ImportDialogProps {
20+
tenantName?: string;
21+
handleImport: () => void;
22+
handleImportAction: (event: React.ChangeEvent<HTMLInputElement>) => void;
23+
importData: ImportData;
24+
open: boolean;
25+
title: string;
26+
message: string;
27+
children?: React.ReactNode;
28+
}
29+
30+
const optionDescriptions: Record<ImportAction, string> = {
1631
replace:
1732
"Removes all current records and adds imported records. This will REMOVE ALL existing stakeholder data.",
1833
update:
1934
"Updates all records matching your provided IDs. If an ID field is blank, the record will be treated as a new entry.",
2035
add: "Imports records without changing any existing records. This is not destructive but can result in duplicate records.",
2136
};
2237

23-
function ImportDialog(props) {
38+
function ImportDialog(props: ImportDialogProps) {
2439
const {
2540
tenantName,
2641
handleImport,
@@ -49,8 +64,9 @@ function ImportDialog(props) {
4964
>
5065
<Typography>{props.message}</Typography>
5166
<Typography>
52-
{optionDescriptions[importData.action] ||
53-
"Please select an option below."}
67+
{importData.action
68+
? optionDescriptions[importData.action]
69+
: "Please select an option below."}
5470
</Typography>
5571
</DialogContent>
5672
<FormControl
@@ -60,7 +76,7 @@ function ImportDialog(props) {
6076
})}
6177
>
6278
<RadioGroup
63-
arial-label="import-options"
79+
aria-label="import-options"
6480
name="import-options"
6581
onChange={handleImportAction}
6682
>
@@ -74,7 +90,11 @@ function ImportDialog(props) {
7490
</RadioGroup>
7591
</FormControl>
7692
<DialogActions>
77-
<Button variant="outlined" type="button" onClick={handleImportAction}>
93+
<Button
94+
variant="outlined"
95+
type="button"
96+
onClick={() => handleImportAction({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>)}
97+
>
7898
Cancel
7999
</Button>
80100
<Button
@@ -91,8 +111,4 @@ function ImportDialog(props) {
91111
);
92112
}
93113

94-
ImportDialog.propTypes = {
95-
open: PropTypes.bool.isRequired,
96-
};
97-
98114
export default ImportDialog;

client/src/components/Admin/ImportOrganizations/ImportFile.jsx renamed to client/src/components/Admin/ImportOrganizations/ImportFile.tsx

File renamed without changes.

client/src/components/Admin/ImportOrganizations/ImportFileGuide.jsx renamed to client/src/components/Admin/ImportOrganizations/ImportFileGuide.tsx

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,40 @@ import {
1414
TableRow,
1515
Typography,
1616
} from "@mui/material";
17-
import PropTypes from "prop-types";
18-
import { useEffect, useRef, useState } from "react";
17+
import { useEffect, useRef, useState, ChangeEvent } from "react";
1918
import { STAKEHOLDER_SCHEMA } from "../../../constants/stakeholder-schema";
2019

21-
const ImportFileGuide = (props) => {
20+
interface StakeholderField {
21+
name: string;
22+
label?: string;
23+
show: boolean;
24+
required?: boolean;
25+
description?: string;
26+
default_value?: string | number | boolean;
27+
sample_format?: string;
28+
}
29+
30+
interface ImportFileGuideProps {
31+
handleDownload: () => void;
32+
handleChange: (event: ChangeEvent<HTMLInputElement>) => void;
33+
handleUpload: () => void;
34+
file?: File | null;
35+
}
36+
37+
const ImportFileGuide = (props: ImportFileGuideProps) => {
2238
const { handleDownload, handleChange, handleUpload, file } = props;
23-
const [visibleFields, setVisibleFields] = useState("all");
24-
const ref = useRef(null);
39+
const [visibleFields, setVisibleFields] = useState<"all" | "required">("all");
40+
const ref = useRef<HTMLInputElement>(null);
2541

26-
const handleVisibleFields = (e) => {
42+
const handleVisibleFields = (e: ChangeEvent<{ value: "all" | "required" }>) => {
2743
const { value } = e.target;
2844
setVisibleFields(value);
2945
};
3046

3147
useEffect(() => {
32-
if (!file) ref.current.value = "";
48+
if (!file && ref.current) {
49+
ref.current.value = "";
50+
}
3351
}, [file]);
3452

3553
return (
@@ -149,7 +167,7 @@ const ImportFileGuide = (props) => {
149167
>
150168
<Select
151169
defaultValue="all"
152-
onChange={handleVisibleFields}
170+
onChange={(e) => handleVisibleFields(e as ChangeEvent<{ value: "all" | "required" }>)}
153171
style={{ width: "100%" }}
154172
>
155173
<MenuItem value="all">All</MenuItem>
@@ -180,11 +198,11 @@ const ImportFileGuide = (props) => {
180198
},
181199
})}
182200
>
183-
<TableCell style={{ fontWeight: field.required && 900 }}>
201+
<TableCell style={{ fontWeight: field.required ? 900 : undefined }}>
184202
{`${field.name} ${field.required ? "(required)" : ""}`}
185203
</TableCell>
186204
<TableCell>{field.description}</TableCell>
187-
<TableCell style={{ fontWeight: field.required && 900 }}>
205+
<TableCell style={{ fontWeight: field.required ? 900 : undefined }}>
188206
{field.default_value}
189207
</TableCell>
190208
<TableCell>{field.sample_format}</TableCell>
@@ -222,10 +240,4 @@ const ImportFileGuide = (props) => {
222240
);
223241
};
224242

225-
ImportFileGuide.propTypes = {
226-
handleDownload: PropTypes.func.isRequired,
227-
handleChange: PropTypes.func.isRequired,
228-
handleUpload: PropTypes.func.isRequired,
229-
};
230-
231243
export default ImportFileGuide;

client/src/components/Admin/ImportOrganizations/ImportFileTable.jsx renamed to client/src/components/Admin/ImportOrganizations/ImportFileTable.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,36 @@ import {
99
TableRow,
1010
Typography,
1111
} from "@mui/material";
12-
import PropTypes from "prop-types";
1312
import { STAKEHOLDER_SCHEMA } from "../../../constants/stakeholder-schema";
1413

15-
const flattenHours = (daysArray) => {
14+
interface DayHours {
15+
weekOfMonth: number;
16+
dayOfWeek: number;
17+
open: string;
18+
close: string;
19+
}
20+
21+
interface StakeholderData {
22+
name: string;
23+
hours?: DayHours[];
24+
[key: string]: any; // For other dynamic fields from STAKEHOLDER_SCHEMA
25+
}
26+
27+
interface ImportFileTableProps {
28+
tenantName?: string;
29+
data?: StakeholderData[];
30+
handleImportAction: () => void;
31+
handleCancel: () => void;
32+
}
33+
34+
const flattenHours = (daysArray?: DayHours[]): string[] => {
1635
if (!daysArray || !daysArray.length) return [];
1736
return daysArray.map((day) => {
1837
return `(${day.weekOfMonth},${day.dayOfWeek},${day.open},${day.close})`;
1938
});
2039
};
2140

22-
const ImportFileTable = (props) => {
41+
const ImportFileTable = (props: ImportFileTableProps) => {
2342
const { tenantName, data, handleImportAction, handleCancel } = props;
2443

2544
return (
@@ -97,10 +116,4 @@ const ImportFileTable = (props) => {
97116
);
98117
};
99118

100-
ImportFileTable.propTypes = {
101-
data: PropTypes.arrayOf(PropTypes.object),
102-
handleImportAction: PropTypes.func.isRequired,
103-
handleCancel: PropTypes.func.isRequired,
104-
};
105-
106119
export default ImportFileTable;

client/src/components/Admin/ImportOrganizations/ProgressBackdrop.jsx renamed to client/src/components/Admin/ImportOrganizations/ProgressBackdrop.tsx

File renamed without changes.

client/src/components/Admin/ImportOrganizations/importValidation.jsx

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// TODO: build robust data validation for imported files
2+
3+
interface SchemaField {
4+
name: string;
5+
validation?: {
6+
isValid: (value: any) => Promise<boolean>;
7+
};
8+
}
9+
10+
interface Row {
11+
id?: string | number;
12+
name: string;
13+
[key: string]: any;
14+
}
15+
16+
interface ValidatedFields {
17+
_id: string | number | null;
18+
_name: string;
19+
[key: string]: any;
20+
}
21+
22+
async function importValidation(
23+
rows: Row[],
24+
schema: SchemaField[],
25+
fieldNames: string[] | null = null
26+
): Promise<ValidatedFields[]> {
27+
const validatedRows: ValidatedFields[] = [];
28+
29+
for (const row of rows) {
30+
const validatedFields: ValidatedFields = {
31+
_id: row.id || null,
32+
_name: row.name,
33+
};
34+
35+
const fieldsToValidate = fieldNames || Object.keys(row);
36+
37+
for (const fieldName of fieldsToValidate) {
38+
// if we want to strictly enforce rows match schema
39+
// if (
40+
// fieldName !== "tenantId" &&
41+
// !schema.find((field) => field.name === fieldName)
42+
// ) {
43+
// throw new Error(
44+
// `${fieldName} field not found in schema. Please check if there are any missing, extra, or misspelled columns.`
45+
// );
46+
// }
47+
48+
const schemaField = schema.find((field) => field.name === fieldName);
49+
const validator = schemaField?.validation;
50+
51+
if (validator) {
52+
const valid = await validator.isValid(row[fieldName]);
53+
validatedFields[fieldName] = valid;
54+
} else {
55+
validatedFields[fieldName] = "N/A";
56+
}
57+
}
58+
59+
validatedRows.push(validatedFields);
60+
}
61+
62+
return validatedRows;
63+
}
64+
65+
export default importValidation;

0 commit comments

Comments
 (0)