Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vertical stepper implemenetation #1441

Merged
merged 22 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fe9341f
Inmpelemented modularised vertical stepper for upload screen
ashish-egov Sep 30, 2024
70aab30
Merge remote-tracking branch 'origin/console' into console
ashish-egov Sep 30, 2024
d9525f3
Update health/micro-ui/web/micro-ui-internals/packages/modules/microp…
ashish-egov Sep 30, 2024
d631f37
Update health/micro-ui/web/micro-ui-internals/packages/modules/microp…
ashish-egov Sep 30, 2024
c12d9eb
Enchancement in vetical Stepper
ashish-egov Oct 7, 2024
8e22724
Merge remote-tracking branch 'origin/console' into vertical-stepper-i…
ashish-egov Oct 7, 2024
6c05bf1
Vertical stepper enhancement
ashish-egov Oct 7, 2024
bcce31b
Merge branch 'vertical-stepper-implemenetation' of https://github.com…
ashish-egov Oct 7, 2024
b720036
Vertical Stepper Integration
ashish-egov Oct 7, 2024
ef7e850
Update SetupMicroplan.js
ashish-egov Oct 7, 2024
d1d493e
Update health/micro-ui/web/micro-ui-internals/packages/modules/microp…
ashish-egov Oct 7, 2024
a8787a5
Delete micro-ui/web/workbench/package-lock.json
ashish-egov Oct 7, 2024
e1c207f
Verycal Stepper Enhancement
ashish-egov Oct 7, 2024
b30cc19
Merge branch 'vertical-stepper-implemenetation' of https://github.com…
ashish-egov Oct 7, 2024
0ad275f
Update package.json
ashish-egov Oct 7, 2024
f580d17
Update package.json
ashish-egov Oct 7, 2024
12d71e6
Vertical stepper enhancement
ashish-egov Oct 7, 2024
a1c5dc2
Merge branch 'vertical-stepper-implemenetation' of https://github.com…
ashish-egov Oct 7, 2024
253e2cb
Merge branch 'console' into vertical-stepper-implemenetation
nipunarora-eGov Oct 7, 2024
f372b96
Revert
ashish-egov Oct 7, 2024
22e3bc4
Merge branch 'vertical-stepper-implemenetation' of https://github.com…
ashish-egov Oct 7, 2024
941ea96
Vertical stepper
ashish-egov Oct 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,25 @@ export const downloadExcelWithCustomName = ({ fileStoreId = null, customName = n
};

if (fileStoreId) {
axios
.get("/filestore/v1/files/id", {
responseType: "arraybuffer",
headers: {
"Content-Type": "application/json",
Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"auth-token": Digit.UserService.getUser()?.["access_token"],
},
params: {
tenantId: Digit.ULBService.getCurrentTenantId(),
fileStoreId: fileStoreId,
},
})
axios.request({
nipunarora-eGov marked this conversation as resolved.
Show resolved Hide resolved
method: 'get',
url: "/filestore/v1/files/id",
responseType: "arraybuffer",
headers: {
"Content-Type": "application/json",
Accept: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"auth-token": Digit.UserService.getUser()?.["access_token"],
},
params: {
tenantId: Digit.ULBService.getCurrentTenantId(),
fileStoreId: fileStoreId,
},
data: { // Use 'data' to send the body
RequestInfo: {
authToken: Digit.UserService.getUser()?.access_token || null,
}
}
})
ashish-egov marked this conversation as resolved.
Show resolved Hide resolved
.then(async (res) => {
downloadExcel(
new Blob([res.data], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
"react-hook-form": "6.15.8",
"react-i18next": "11.16.2",
"react-query": "3.6.1",
"react-router-dom": "5.3.0"
"react-router-dom": "5.3.0",
"xlsx": "0.17.5",
"react-drag-drop-files": "^2.3.10",
"@cyntler/react-doc-viewer": "1.10.3"
},
"author": "Jagankumar <[email protected]>",
"license": "MIT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CampaignDetails from "./components/CampaignDetails";
import { ProviderContext } from "./utils/context";
import BoundarySelection from "./components/BoundarySelection";
import HypothesisWrapper from "./components/HypothesisWrapper";
import UploadDataCustom from "./components/UploadDataCustom";
import DataMgmtTable from "./components/DataMgmtTable";
import FileComponent from "./components/FileComponent";
import HeaderComp from "./components/HeaderComp";
Expand Down Expand Up @@ -55,6 +56,7 @@ const componentsToRegister = {
MicroplanDetails,
BoundarySelection,
HypothesisWrapper,
UploadDataCustom,
ashish-egov marked this conversation as resolved.
Show resolved Hide resolved
DataMgmtTable,
FileComponent,
HeaderComp,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import React, { useState, useMemo, useEffect } from "react";
import { UploadIcon, FileIcon, DeleteIconv2, Button, DownloadIcon, PopUp, SVG } from "@egovernments/digit-ui-react-components";
import { FileUploader } from "react-drag-drop-files";
import { useTranslation } from "react-i18next";
import XLSX from "xlsx";
import XlsPreview from "./XlsPreview";
import { PRIMARY_COLOR } from "../utils";
import { Toast } from "@egovernments/digit-ui-components";

/**
* The BulkUpload component in JavaScript allows users to upload, validate, preview, download, and
* delete files in bulk with support for Excel file validation.
* @returns The `BulkUpload` component is returning a JSX structure that includes conditional rendering
* based on the presence of `fileData`. If `fileData` is empty or null, it renders a `FileUploader`
* component with drag and drop functionality. If `fileData` contains files, it renders file cards for
* each file with options to download and delete. Additionally, it includes a preview component for
* Excel files
*/
const BulkUpload = ({ multiple = true, onSubmit, fileData, onFileDelete, onFileDownload }) => {
const { t } = useTranslation();
const [files, setFiles] = useState([]);
const fileTypes = ["XLS", "XLSX", "csv", "CSV"];
const tenantId = Digit.ULBService.getCurrentTenantId();
const [showPreview, setShowPreview] = useState(false);
const [fileUrl, setFileUrl] = useState(fileData?.[0]);
const [fileName, setFileName] = useState(null);
const [showToast, setShowToast] = useState(false);

useEffect(() => {
const fetch = async () => {
const { data: { fileStoreIds: fileUrl } = {} } = await Digit.UploadServices.Filefetch([fileData?.[0]?.filestoreId], tenantId);
const temp = fileData?.map((i) => ({
...i,
url: fileUrl?.[0]?.url,
}));
setFileUrl(temp?.[0]);
};
fetch();
}, [fileData]);

const closeToast = () => {
setTimeout(() => {
setShowToast(null);
}, 5000);
};

const dragDropJSX = (
<div className="upload-drag-drop-container">
<UploadIcon />
<p className="drag-drop-text">
<text className="drag-drop"> {t("WBH_DRAG_DROP")}</text> <text className="browse-text">{t("WBH_BULK_BROWSE_FILES")}</text>
</p>
</div>
);

const handleFileDelete = (file, index) => {
onFileDelete(file, index);
};

const validateExcel = (selectedFile) => {
return new Promise((resolve, reject) => {
// Check if a file is selected
if (!selectedFile) {
reject(t("HCM_FILE_UPLOAD_ERROR"));
return;
}

// Read the Excel file
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: "array" });

// Assuming your columns are in the first sheet
const sheet = workbook.Sheets[workbook.SheetNames[0]];

const columnsToValidate = XLSX.utils.sheet_to_json(sheet, {
header: 1,
})[0];

// Check if all columns have non-empty values in every row
const isValid = XLSX.utils
.sheet_to_json(sheet)
.every((row) => columnsToValidate.every((column) => row[column] !== undefined && row[column] !== null && row[column] !== ""));

if (isValid) {
// All columns in all rows have non-empty values, it is valid
resolve(true);
} else {
// const label = "HCM_FILE_VALIDATION_ERROR";
// setShowToast({ isError: true, label });
reject(t("HCM_FILE_VALIDATION_ERROR"));
}
} catch (error) {
reject("HCM_FILE_UNAVAILABLE");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrap error message with translation function

The error message 'HCM_FILE_UNAVAILABLE' should be wrapped with the t() function to support internationalization, consistent with other error messages.

Apply the following diff:

  } catch (error) {
-   reject("HCM_FILE_UNAVAILABLE");
+   reject(t("HCM_FILE_UNAVAILABLE"));
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
reject("HCM_FILE_UNAVAILABLE");
reject(t("HCM_FILE_UNAVAILABLE"));

}
};

reader.readAsArrayBuffer(selectedFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle FileReader errors

The FileReader currently lacks error handling for file read errors. Adding an onerror handler will improve robustness by properly managing read errors.

Add the following code:

reader.onload = (e) => {
  try {
    // existing code...
  } catch (error) {
    reject(t("HCM_FILE_UNAVAILABLE"));
  }
};
+ reader.onerror = (e) => {
+   reject(t("HCM_FILE_READING_ERROR"));
+ };

Ensure that the translation key HCM_FILE_READING_ERROR is added to your localization files.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: "array" });
// Assuming your columns are in the first sheet
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const columnsToValidate = XLSX.utils.sheet_to_json(sheet, {
header: 1,
})[0];
// Check if all columns have non-empty values in every row
const isValid = XLSX.utils
.sheet_to_json(sheet)
.every((row) => columnsToValidate.every((column) => row[column] !== undefined && row[column] !== null && row[column] !== ""));
if (isValid) {
// All columns in all rows have non-empty values, it is valid
resolve(true);
} else {
// const label = "HCM_FILE_VALIDATION_ERROR";
// setShowToast({ isError: true, label });
reject(t("HCM_FILE_VALIDATION_ERROR"));
}
} catch (error) {
reject("HCM_FILE_UNAVAILABLE");
}
};
reader.readAsArrayBuffer(selectedFile);
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: "array" });
// Assuming your columns are in the first sheet
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const columnsToValidate = XLSX.utils.sheet_to_json(sheet, {
header: 1,
})[0];
// Check if all columns have non-empty values in every row
const isValid = XLSX.utils
.sheet_to_json(sheet)
.every((row) => columnsToValidate.every((column) => row[column] !== undefined && row[column] !== null && row[column] !== ""));
if (isValid) {
// All columns in all rows have non-empty values, it is valid
resolve(true);
} else {
// const label = "HCM_FILE_VALIDATION_ERROR";
// setShowToast({ isError: true, label });
reject(t("HCM_FILE_VALIDATION_ERROR"));
}
} catch (error) {
reject("HCM_FILE_UNAVAILABLE");
}
};
reader.onerror = (e) => {
reject(t("HCM_FILE_READING_ERROR"));
};
reader.readAsArrayBuffer(selectedFile);

});
};

const handleFileDownload = async (e, file) => {
if (e?.stopPropagation) {
e.stopPropagation();
}
onFileDownload(file);
};

const handleChange = async (newFiles) => {
try {
// await validateExcel(newFiles[0]);
onSubmit([...newFiles]);
} catch (error) {
// Handle the validation error, you can display a message or take appropriate actions.
setShowToast({ key: "error", label: error });
closeToast();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enable file validation in handleChange function

The call to validateExcel is currently commented out, which means uploaded files are not being validated. To ensure that only valid files are processed, consider uncommenting this line.

Apply the following diff:

const handleChange = async (newFiles) => {
  try {
-    // await validateExcel(newFiles[0]);
+    await validateExcel(newFiles[0]);
    onSubmit([...newFiles]);
  } catch (error) {
    // Handle the validation error, you can display a message or take appropriate actions.
    setShowToast({ key: "error", label: error });
    closeToast();
  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleChange = async (newFiles) => {
try {
// await validateExcel(newFiles[0]);
onSubmit([...newFiles]);
} catch (error) {
// Handle the validation error, you can display a message or take appropriate actions.
setShowToast({ key: "error", label: error });
closeToast();
}
const handleChange = async (newFiles) => {
try {
await validateExcel(newFiles[0]);
onSubmit([...newFiles]);
} catch (error) {
// Handle the validation error, you can display a message or take appropriate actions.
setShowToast({ key: "error", label: error });
closeToast();
}

};

const fileTypeError = (err) => {
setShowToast({ key: "error", label: t("HCM_ERROR_INVALID_FILE_TYPE") });
};

const renderFileCards = useMemo(() => {
return fileData?.map((file, index) => (
<div
className="uploaded-file-container"
key={index}
onClick={(e) => {
e.stopPropagation();
setShowPreview(true);
}}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve accessibility by adding keyboard event handlers

The div element at lines 128-135 has an onClick handler but lacks corresponding keyboard event handlers. This can hinder accessibility for users who navigate via keyboard. Consider adding onKeyDown or onKeyUp event handlers and setting the tabIndex and role attributes to make the element focusable and operable with the keyboard.

Apply the following diff to enhance accessibility:

<div
  className="uploaded-file-container"
  key={index}
  onClick={(e) => {
    e.stopPropagation();
    setShowPreview(true);
  }}
+ role="button"
+ tabIndex="0"
+ onKeyDown={(e) => {
+   if (e.key === 'Enter' || e.key === ' ') {
+     e.preventDefault();
+     setShowPreview(true);
+   }
+ }}
>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
className="uploaded-file-container"
key={index}
onClick={(e) => {
e.stopPropagation();
setShowPreview(true);
}}
>
<div
className="uploaded-file-container"
key={index}
onClick={(e) => {
e.stopPropagation();
setShowPreview(true);
}}
role="button"
tabIndex="0"
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setShowPreview(true);
}
}}
>
🧰 Tools
🪛 Biome

[error] 128-135: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

<div
className="uploaded-file-container-sub"
style={{ cursor: "pointer" }}
// onClick={() => {
// setShowPreview(true);
// }}
>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Clean up commented-out code

The commented-out onClick handler is not needed if it's not intended for future use. Removing it can help keep the code clean.

Apply the following diff:

style={{ cursor: "pointer" }}
-            // onClick={() => {
-            //   setShowPreview(true);
-            // }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// onClick={() => {
// setShowPreview(true);
// }}
>
style={{ cursor: "pointer" }}
>

<FileIcon className="icon" />
<div style={{ marginLeft: "0.5rem", color: "#505A5F", fontWeight: "700" }}>{file.filename}</div>
</div>
<div className="delete-and-download-button">
<Button
label={t("WBH_DOWNLOAD")}
variation="secondary"
icon={<DownloadIcon styles={{ height: "1.25rem", width: "1.25rem" }} fill={PRIMARY_COLOR} />}
type="button"
className="workbench-download-template-btn hover"
onButtonClick={(e) => {
e.stopPropagation();
handleFileDownload(e, fileUrl);
}}
/>
<Button
label={t("WBH_DELETE")}
variation="secondary"
icon={<DeleteIconv2 styles={{ height: "1.25rem", width: "2.5rem" }} fill={PRIMARY_COLOR} />}
type="button"
className="workbench-download-template-btn hover"
onButtonClick={(e) => {
e.stopPropagation();
handleFileDelete(file, index);
setShowPreview(false);
}}
/>
</div>
</div>
));
}, [fileData, fileUrl]);

return (
<React.Fragment>
{(!fileData || fileData?.length === 0) && (
<FileUploader
multiple={multiple}
handleChange={handleChange}
name="file"
types={fileTypes}
children={dragDropJSX}
onTypeError={fileTypeError}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid passing children via props

Passing children as a prop is not recommended. Instead, the standard practice in React is to nest child elements directly within the component's JSX.

Apply the following diff to refactor the code:

<FileUploader
  multiple={multiple}
  handleChange={handleChange}
  name="file"
  types={fileTypes}
-  children={dragDropJSX}
  onTypeError={fileTypeError}
>
+  {dragDropJSX}
</FileUploader>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
children={dragDropJSX}
onTypeError={fileTypeError}
/>
multiple={multiple}
handleChange={handleChange}
name="file"
types={fileTypes}
onTypeError={fileTypeError}
>
{dragDropJSX}
</FileUploader>
🧰 Tools
🪛 Biome

[error] 183-183: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

)}
{fileData?.length > 0 && renderFileCards}
{showPreview && <XlsPreview file={fileUrl} onDownload={() => handleFileDownload(null, fileUrl)} onBack={() => setShowPreview(false)} />}
{showToast && (
<Toast
label={showToast?.label}
type={showToast?.key === "error" ? "error" : showToast?.key === "info" ? "info" : "success"}
// error={showToast?.key === "error" ? true : false}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Remove unused commented-out code

The commented-out error prop in the <Toast> component is unnecessary and can be removed to declutter the code.

Apply the following diff:

-      // error={showToast?.key === "error" ? true : false}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// error={showToast?.key === "error" ? true : false}

isDleteBtn={true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in prop name

There's a typo in the prop isDleteBtn. It should be isDeleteBtn to ensure the property functions correctly.

Apply the following diff:

-      isDleteBtn={true}
+      isDeleteBtn={true}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
isDleteBtn={true}
isDeleteBtn={true}

onClose={() => setShowToast(null)}
></Toast>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Simplify JSX by using self-closing elements

The <Toast> component does not contain any children and can be self-closed for cleaner syntax.

Apply the following diff:

-    <Toast
-      label={showToast?.label}
-      type={showToast?.key === "error" ? "error" : showToast?.key === "info" ? "info" : "success"}
-      // error={showToast?.key === "error" ? true : false}
-      isDleteBtn={true}
-      onClose={() => setShowToast(null)}
-    ></Toast>
+    <Toast
+      label={showToast?.label}
+      type={showToast?.key === "error" ? "error" : showToast?.key === "info" ? "info" : "success"}
+      // error={showToast?.key === "error" ? true : false}
+      isDleteBtn={true}
+      onClose={() => setShowToast(null)}
+    />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Toast
label={showToast?.label}
type={showToast?.key === "error" ? "error" : showToast?.key === "info" ? "info" : "success"}
// error={showToast?.key === "error" ? true : false}
isDleteBtn={true}
onClose={() => setShowToast(null)}
></Toast>
<Toast
label={showToast?.label}
type={showToast?.key === "error" ? "error" : showToast?.key === "info" ? "info" : "success"}
// error={showToast?.key === "error" ? true : false}
isDleteBtn={true}
onClose={() => setShowToast(null)}
/>
🧰 Tools
🪛 Biome

[error] 190-196: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.

Unsafe fix: Use a SelfClosingElement instead

(lint/style/useSelfClosingElements)

)}
</React.Fragment>
);
};

export default BulkUpload;
Loading