214 lines
11 KiB
TypeScript
214 lines
11 KiB
TypeScript
import Button from "@/components/Low/Button";
|
|
import { Module } from "@/interfaces";
|
|
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from "@headlessui/react";
|
|
import { capitalize } from "lodash";
|
|
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
|
import { FaFileDownload } from "react-icons/fa";
|
|
import { HiOutlineDocumentText } from "react-icons/hi";
|
|
import { IoInformationCircleOutline } from "react-icons/io5";
|
|
|
|
interface Props {
|
|
module: Module;
|
|
state: { isOpen: boolean, type: "exam" | "solutions" };
|
|
setState: React.Dispatch<React.SetStateAction<{ isOpen: boolean, type: "exam" | "solutions" }>>;
|
|
}
|
|
|
|
const Templates: React.FC<Props> = ({ module, state, setState }) => {
|
|
const [isClosing, setIsClosing] = useState(false);
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (state.isOpen) {
|
|
setMounted(true);
|
|
}
|
|
}, [state]);
|
|
|
|
useEffect(() => {
|
|
if (!state.isOpen && mounted) {
|
|
const timer = setTimeout(() => {
|
|
setMounted(false);
|
|
setIsClosing(false);
|
|
}, 300);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [state, mounted]);
|
|
|
|
const blockMultipleClicksClose = useCallback(() => {
|
|
if (isClosing) return;
|
|
setIsClosing(true);
|
|
setState({ isOpen: false, type: state.type });
|
|
|
|
const timer = setTimeout(() => {
|
|
setIsClosing(false);
|
|
}, 300);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [isClosing, setState, state]);
|
|
|
|
if (!mounted && !state.isOpen) return null;
|
|
|
|
const moduleExercises = {
|
|
"reading": [
|
|
"Multiple Choice",
|
|
"Write Blanks",
|
|
"True False",
|
|
"Paragraph Match",
|
|
"Idea Match"
|
|
],
|
|
"listening": [
|
|
"Multiple Choice",
|
|
"True False",
|
|
"Write Blanks: Questions",
|
|
"Write Blanks: Fill",
|
|
"Write Blanks: Form",
|
|
],
|
|
"level": [
|
|
"Fill Blanks: Multiple Choice",
|
|
"Multiple Choice: Blank Space",
|
|
"Multiple Choice: Underline",
|
|
"Multiple Choice: Reading Passage"
|
|
],
|
|
"writing": [],
|
|
"speaking": [],
|
|
}
|
|
|
|
const handleTemplateDownload = () => {
|
|
const fileName = `${capitalize(module)}${state.type === "exam" ? "Exam" : "Solutions"}Template`;
|
|
const url = `https://firebasestorage.googleapis.com/v0/b/encoach-staging.appspot.com/o/import_templates%2F${fileName}.docx?alt=media&token=b771a535-bf95-4060-889c-a086df65d480`;
|
|
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
|
|
link.download = `${fileName}.docx`;
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
};
|
|
|
|
return (
|
|
<Transition
|
|
show={state.isOpen}
|
|
as={Fragment}
|
|
beforeEnter={() => setIsClosing(false)}
|
|
beforeLeave={() => setIsClosing(true)}
|
|
afterLeave={() => {
|
|
setIsClosing(false);
|
|
setMounted(false);
|
|
}}
|
|
>
|
|
<Dialog onClose={() => blockMultipleClicksClose()} className="relative z-50">
|
|
<TransitionChild
|
|
as={Fragment}
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0">
|
|
<div className="fixed inset-0 bg-black/30" />
|
|
</TransitionChild>
|
|
|
|
<TransitionChild
|
|
as={Fragment}
|
|
enter="ease-out duration-300"
|
|
enterFrom="opacity-0 scale-95"
|
|
enterTo="opacity-100 scale-100"
|
|
leave="ease-in duration-200"
|
|
leaveFrom="opacity-100 scale-100"
|
|
leaveTo="opacity-0 scale-95">
|
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
|
<DialogPanel className={`bg-ielts-${module}-light w-full max-w-6xl h-fit p-8 rounded-xl flex flex-col gap-4`}>
|
|
<DialogTitle className="flex font-bold text-xl justify-center text-gray-700"><span>{`${capitalize(module)} ${state.type === "exam" ? 'Exam' : 'Solutions'} Import`}</span></DialogTitle>
|
|
<div className="flex flex-col w-full mt-4 gap-6">
|
|
{state.type === "exam" ? (
|
|
<>
|
|
<div className="flex flex-col gap-3 bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2">
|
|
<HiOutlineDocumentText className={`w-5 h-5 text-ielts-${module}`} />
|
|
<h2 className="text-lg font-semibold">
|
|
The {module} exam import accepts the following exercise types:
|
|
</h2>
|
|
</div>
|
|
<ul className="flex flex-col pl-10 gap-2">
|
|
{moduleExercises[module].map((item, index) => (
|
|
<li key={index} className="text-gray-700 list-disc">
|
|
{item}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3 bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2">
|
|
<IoInformationCircleOutline className={`w-5 h-5 text-ielts-${module}`} />
|
|
<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 a Word .docx document.
|
|
</li>
|
|
<li className="text-gray-700 list-disc">
|
|
have clear part and exercise delineation (e.g. Part 1, ... , Part X, Question 1 - 10, ... , Question y - x).
|
|
</li>
|
|
{["reading", "level"].includes(module) && (
|
|
<li className="text-gray-700 list-disc">
|
|
a part must only contain a single reading passage and it must be between the part delineator (e.g. Part 1) and the part exercises.
|
|
</li>
|
|
)}
|
|
<li className="text-gray-700 list-disc">
|
|
if solutions are going to be uploaded, the exercise numbers/id's must match the ones in the solutions.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</>
|
|
) :
|
|
<>
|
|
<div className="flex flex-col gap-3 bg-gray-50 rounded-lg p-4">
|
|
<div className="flex items-center gap-2">
|
|
<IoInformationCircleOutline className={`w-5 h-5 text-ielts-${module}`} />
|
|
<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 a Word .docx document.
|
|
</li>
|
|
<li className="text-gray-700 list-disc">
|
|
match the exercise numbers/id's that are in the exam document.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</>
|
|
}
|
|
<div className="bg-gray-50 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 and formatting but it must adhere to the previous requirements${state.type === "exam" ? " and exercises of the same type should have the same formatting" : ""}.`}
|
|
</p>
|
|
</div>
|
|
<div className="w-full flex justify-between mt-4 gap-8">
|
|
<Button color="purple" onClick={() => blockMultipleClicksClose()} 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>
|
|
</DialogPanel>
|
|
</div>
|
|
</TransitionChild>
|
|
</Dialog>
|
|
</Transition>
|
|
);
|
|
}
|
|
|
|
export default Templates;
|