ENCOA-260, ENCOA-259
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import Button from "@/components/Low/Button";
|
||||
import axios from "axios";
|
||||
import { uniqBy } from "lodash";
|
||||
import { capitalize, uniqBy } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useFilePicker } from "use-file-picker";
|
||||
@@ -19,6 +19,9 @@ import { Type, UserImport } from "../../../interfaces/IUserImport";
|
||||
import UserTable from "../../../components/UserTable";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
import Select from "@/components/Low/Select";
|
||||
import { IoInformationCircleOutline } from "react-icons/io5";
|
||||
import { FaFileDownload } from "react-icons/fa";
|
||||
import { HiOutlineDocumentText } from "react-icons/hi";
|
||||
|
||||
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/);
|
||||
|
||||
@@ -135,7 +138,7 @@ export default function BatchCreateUser({ user, entities = [], permissions, onFi
|
||||
}
|
||||
|
||||
setInfos(information);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
toast.error(
|
||||
@@ -157,7 +160,7 @@ export default function BatchCreateUser({ user, entities = [], permissions, onFi
|
||||
|
||||
const crossRefEmails = response.data;
|
||||
if (!!crossRefEmails) {
|
||||
const existingEmails = new Set(crossRefEmails.map((x: any)=> x.email));
|
||||
const existingEmails = new Set(crossRefEmails.map((x: any) => x.email));
|
||||
const dupes = infos.filter(info => existingEmails.has(info.email));
|
||||
const newUsersList = infos.filter(info => !existingEmails.has(info.email));
|
||||
setNewUsers(newUsersList);
|
||||
@@ -192,10 +195,10 @@ export default function BatchCreateUser({ user, entities = [], permissions, onFi
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await axios.post("/api/batch_users", {users: newUsers.map((user) => ({...user, type, expiryDate}))});
|
||||
await axios.post("/api/batch_users", { users: newUsers.map((user) => ({ ...user, type, expiryDate })) });
|
||||
toast.success(`Successfully added ${newUsers.length} user(s)!`);
|
||||
onFinish();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error("Something went wrong, please try again later!");
|
||||
} finally {
|
||||
@@ -210,57 +213,126 @@ export default function BatchCreateUser({ user, entities = [], permissions, onFi
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleTemplateDownload = () => {
|
||||
const fileName = "UsersTemplate.xlsx";
|
||||
const url = `https://firebasestorage.googleapis.com/v0/b/encoach-staging.appspot.com/o/import_templates%2F${fileName}?alt=media&token=b771a535-bf95-4060-889c-a086df65d480`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
|
||||
link.download = fileName;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={showHelp} onClose={() => setShowHelp(false)} title="Excel File Format">
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<span>Please upload an Excel file with the following format:</span>
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border border-neutral-200 px-2 py-1">First Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Last Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Student ID</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Passport/National ID</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">E-mail</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Classroom Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Country</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<span className="mt-4">
|
||||
<b>Notes:</b>
|
||||
<ul>
|
||||
<li>- All incorrect e-mails will be ignored;</li>
|
||||
<li>- All already registered e-mails will be ignored;</li>
|
||||
<li>- You may have a header row with the format above, however, it is not necessary;</li>
|
||||
<li>- All of the e-mails in the file will receive an e-mail to join EnCoach with the role selected below.</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
<Modal isOpen={showHelp} onClose={() => setShowHelp(false)}>
|
||||
<>
|
||||
<div className="flex font-bold text-xl justify-center text-gray-700"><span>Excel File Format</span></div>
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-3 bg-gray-100 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<HiOutlineDocumentText className={`w-5 h-5 text-mti-purple-light`} />
|
||||
<h2 className="text-lg font-semibold">
|
||||
The uploaded document must:
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="flex flex-col pl-10 gap-2">
|
||||
<li className="text-gray-700 list-disc">
|
||||
be an Excel .xlsx document.
|
||||
</li>
|
||||
<li className="text-gray-700 list-disc">
|
||||
only have a single spreadsheet with only the following 8 columns in the <b>same order</b>:
|
||||
<div className="py-4 pr-4">
|
||||
<table className="w-full bg-white">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border border-neutral-200 px-2 py-1">First Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Last Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Student ID</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Passport/National ID</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">E-mail</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Classroom Name</th>
|
||||
<th className="border border-neutral-200 px-2 py-1">Country</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 bg-gray-100 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<IoInformationCircleOutline className={`w-5 h-5 text-mti-purple-light`} />
|
||||
<h2 className="text-lg font-semibold">
|
||||
Note that:
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="flex flex-col pl-10 gap-2">
|
||||
<li className="text-gray-700 list-disc">
|
||||
all incorrect e-mails will be ignored.
|
||||
</li>
|
||||
<li className="text-gray-700 list-disc">
|
||||
all already registered e-mails will be ignored.
|
||||
</li>
|
||||
<li className="text-gray-700 list-disc">
|
||||
the spreadsheet may have a header row with the format above, however, it is not necessary as long the columns are in the <b>right order</b>.
|
||||
</li>
|
||||
<li className="text-gray-700 list-disc">
|
||||
all of the e-mails in the file will receive an e-mail to join EnCoach with the role selected below.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-gray-100 rounded-lg p-4">
|
||||
<p className="text-gray-600">
|
||||
{`The downloadable template is an example of a file that can be imported. Your document doesn't need to be a carbon copy of the template - it can have different styling but it must adhere to the previous requirements.`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mt-6 gap-8">
|
||||
<Button color="purple" onClick={() => setShowHelp(false)} variant="outline" className="self-end w-full bg-white">
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button color="purple" onClick={handleTemplateDownload} variant="solid" className="self-end w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<FaFileDownload size={24} />
|
||||
Download Template
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Modal>
|
||||
<div className="border-mti-gray-platinum flex flex-col gap-4 rounded-xl border p-4">
|
||||
<div className="grid grid-cols-2 -md:grid-cols-1 gap-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-end justify-between">
|
||||
<label className="text-mti-gray-dim text-base font-normal">Choose an Excel file</label>
|
||||
<div className="tooltip cursor-pointer" data-tip="Excel File Format" onClick={() => setShowHelp(true)}>
|
||||
<BsQuestionCircleFill />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowHelp(true)}
|
||||
className="tooltip cursor-pointer p-1.5 hover:bg-gray-200 rounded-full transition-colors duration-200"
|
||||
data-tip="Excel File Format"
|
||||
>
|
||||
<IoInformationCircleOutline size={24} />
|
||||
</button>
|
||||
</div>
|
||||
<Button onClick={openFilePicker} isLoading={isLoading} disabled={isLoading}>
|
||||
{filesContent.length > 0 ? filesContent[0].name : "Choose a file"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={clsx("flex flex-col gap-4")}>
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
<Select
|
||||
defaultValue={{ value: (entities || [])[0]?.id, label: (entities || [])[0]?.label }}
|
||||
options={entities.map((e) => ({ value: e.id, label: e.label }))}
|
||||
onChange={(e) => setEntity(e?.value || undefined)}
|
||||
isClearable={checkAccess(user, ["admin", "developer"])}
|
||||
/>
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
<Select
|
||||
defaultValue={{ value: (entities || [])[0]?.id, label: (entities || [])[0]?.label }}
|
||||
options={entities.map((e) => ({ value: e.id, label: e.label }))}
|
||||
onChange={(e) => setEntity(e?.value || undefined)}
|
||||
isClearable={checkAccess(user, ["admin", "developer"])}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{user && checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && (
|
||||
|
||||
Reference in New Issue
Block a user