162 lines
8.0 KiB
TypeScript
162 lines
8.0 KiB
TypeScript
import { Module } from "@/interfaces";
|
|
import clsx from "clsx";
|
|
import { ReactNode, useEffect, useState } from "react";
|
|
import { MdDelete, MdEdit, MdEditOff, MdRefresh, MdSave, MdSignalCellularAlt } from "react-icons/md";
|
|
import { HiOutlineClipboardCheck, HiOutlineClipboardList } from "react-icons/hi";
|
|
import { Difficulty } from "@/interfaces/exam";
|
|
import Option from "@/interfaces/option";
|
|
import ReactSelect, { components } from "react-select";
|
|
|
|
interface Props {
|
|
title: string;
|
|
description: string;
|
|
editing: boolean;
|
|
difficulty?: Difficulty;
|
|
saveDifficulty?: (diff: Difficulty) => void;
|
|
module?: Module;
|
|
handleSave: () => void;
|
|
handleDiscard: () => void;
|
|
handleDelete?: () => void;
|
|
handlePractice?: () => void;
|
|
handleEdit?: () => void;
|
|
isEvaluationEnabled?: boolean;
|
|
children?: ReactNode;
|
|
}
|
|
|
|
const Header: React.FC<Props> = ({
|
|
title, description, editing, difficulty, saveDifficulty, isEvaluationEnabled, handleSave, handleDiscard, handleDelete, handleEdit, handlePractice, children, module
|
|
}) => {
|
|
const DIFFICULTIES: Difficulty[] = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
|
const difficultyOptions: Option[] = DIFFICULTIES.map(level => ({
|
|
label: level,
|
|
value: level
|
|
}));
|
|
|
|
return (
|
|
<div className="flex flex-col md:flex-row items-start md:items-center mb-6 text-sm">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-800">{title}</h1>
|
|
<p className="text-gray-600 mt-1">{description}</p>
|
|
</div>
|
|
<div className="flex w-[50%] ml-auto justify-end flex-wrap gap-2">
|
|
<div className="flex flex-wrap gap-2 justify-end">
|
|
<button
|
|
onClick={handleSave}
|
|
disabled={!editing}
|
|
className={
|
|
clsx("px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
|
editing ? 'bg-green-500 text-white hover:bg-green-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
)}
|
|
>
|
|
<MdSave size={18} />
|
|
Save
|
|
</button>
|
|
<button
|
|
onClick={handleDiscard}
|
|
disabled={!editing}
|
|
className={clsx(
|
|
"px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
|
editing ? 'bg-gray-500 text-white hover:bg-gray-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
)}
|
|
>
|
|
<MdRefresh size={18} />
|
|
Discard
|
|
</button>
|
|
{handleEdit && (
|
|
<button
|
|
onClick={handleEdit}
|
|
className={`px-3 py-2 bg-ielts-${module}/80 text-white hover:bg-ielts-${module} rounded-lg transition-all duration-200 flex items-center gap-1 min-w-[90px] justify-center`}
|
|
>
|
|
{editing ? <MdEditOff size={18} /> : <MdEdit size={18} />}
|
|
Edit
|
|
</button>
|
|
)}
|
|
{handlePractice &&
|
|
<button
|
|
onClick={handlePractice}
|
|
className={clsx(
|
|
"px-3 py-2 rounded-lg flex items-center gap-1 transition-all duration-200 min-w-[90px] justify-center",
|
|
isEvaluationEnabled
|
|
? 'bg-amber-500 text-white hover:bg-amber-600'
|
|
: 'bg-gray-200 text-gray-600 hover:bg-gray-300'
|
|
)}
|
|
>
|
|
{isEvaluationEnabled ? <HiOutlineClipboardCheck size={18} /> : <HiOutlineClipboardList size={18} />}
|
|
{isEvaluationEnabled ? 'Graded' : 'Practice'}
|
|
</button>
|
|
}
|
|
{handleDelete && (
|
|
<button
|
|
onClick={handleDelete}
|
|
className="px-3 py-2 bg-white border border-red-500 text-red-500 hover:bg-red-50 rounded-lg transition-all duration-200 flex items-center gap-1 min-w-[90px] justify-center"
|
|
>
|
|
<MdDelete size={18} />
|
|
Delete
|
|
</button>
|
|
)}
|
|
{difficulty !== undefined && (
|
|
<div className="w-[92px]">
|
|
<ReactSelect
|
|
options={difficultyOptions}
|
|
value={difficultyOptions.find(opt => opt.value === difficulty)}
|
|
onChange={(value) => saveDifficulty!(value!.value as Difficulty)}
|
|
menuPortalTarget={document?.body}
|
|
components={{
|
|
IndicatorSeparator: null,
|
|
ValueContainer: ({ children, ...props }) => (
|
|
<components.ValueContainer {...props}>
|
|
<div className="flex flex-row gap-2 items-center pl-0.5">
|
|
<MdSignalCellularAlt size={14} className="text-gray-600" />
|
|
{children}
|
|
</div>
|
|
</components.ValueContainer>
|
|
)
|
|
}}
|
|
styles={{
|
|
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
|
control: (styles) => ({
|
|
...styles,
|
|
minHeight: '40px',
|
|
border: '1px solid #e5e7eb',
|
|
borderRadius: '0.5rem',
|
|
boxShadow: 'none',
|
|
backgroundColor: '#f3f4f6',
|
|
cursor: 'pointer',
|
|
'&:hover': {
|
|
border: '1px solid #e5e7eb',
|
|
}
|
|
}),
|
|
valueContainer: (styles) => ({
|
|
...styles,
|
|
padding: '0 8px',
|
|
display: 'flex',
|
|
alignItems: 'center'
|
|
}),
|
|
input: (styles) => ({
|
|
...styles,
|
|
margin: '0',
|
|
padding: '0'
|
|
}),
|
|
dropdownIndicator: (styles) => ({
|
|
...styles,
|
|
padding: '8px'
|
|
}),
|
|
option: (styles, state) => ({
|
|
...styles,
|
|
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
|
|
color: state.isFocused ? "black" : styles.color,
|
|
}),
|
|
}}
|
|
className="text-sm"
|
|
/>
|
|
</div>
|
|
)}
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Header;
|