Cleared of the stuff the EnCoach team wanted changed

This commit is contained in:
Tiago Ribeiro
2024-10-28 14:40:26 +00:00
parent 0becd295b0
commit fa0c502467
34 changed files with 166 additions and 107 deletions

View File

@@ -194,7 +194,7 @@ export default function BatchCreateUser({user, users, entities = [], permissions
<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>
{user?.type !== "corporate" && <th className="border border-neutral-200 px-2 py-1">Corporate (e-mail)</th>}
<th className="border border-neutral-200 px-2 py-1">Group Name</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>

View File

@@ -39,7 +39,7 @@ export default function ExamLoader() {
setExams([exam]);
setSelectedModules([selectedModule]);
router.push("/exercises");
router.push("/exam");
}
setIsLoading(false);

View File

@@ -20,6 +20,8 @@ import Modal from "@/components/Modal";
import {checkAccess} from "@/utils/permissions";
import useGroups from "@/hooks/useGroups";
import Button from "@/components/Low/Button";
import { EntityWithRoles } from "@/interfaces/entity";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
const searchFields = [["module"], ["id"], ["createdBy"]];
@@ -56,13 +58,36 @@ const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam
);
};
export default function ExamList({user}: {user: User}) {
export default function ExamList({user, entities = []}: {user: User, entities: EntityWithRoles[]}) {
const [selectedExam, setSelectedExam] = useState<Exam>();
const {exams, reload} = useExams();
const {users} = useUsers();
const {groups} = useGroups({admin: user?.id, userType: user?.type});
const canViewReading = useAllowedEntities(user, entities, 'view_reading')
const canViewListening = useAllowedEntities(user, entities, 'view_listening')
const canViewWriting = useAllowedEntities(user, entities, 'view_writing')
const canViewSpeaking = useAllowedEntities(user, entities, 'view_speaking')
const canViewLevel = useAllowedEntities(user, entities, 'view_level')
const canDeleteReading = useAllowedEntities(user, entities, 'delete_reading')
const canDeleteListening = useAllowedEntities(user, entities, 'delete_listening')
const canDeleteWriting = useAllowedEntities(user, entities, 'delete_writing')
const canDeleteSpeaking = useAllowedEntities(user, entities, 'delete_speaking')
const canDeleteLevel = useAllowedEntities(user, entities, 'delete_level')
const modulePermissions = useMemo(() => ({
reading: {view: canViewReading.length > 0, delete: canDeleteReading.length > 0},
listening: {view: canViewListening.length > 0, delete: canDeleteListening.length > 0},
writing: {view: canViewWriting.length > 0, delete: canDeleteWriting.length > 0},
speaking: {view: canViewSpeaking.length > 0, delete: canDeleteSpeaking.length > 0},
level: {view: canViewLevel.length > 0, delete: canDeleteLevel.length > 0},
}),
[
canDeleteLevel.length, canDeleteListening.length, canDeleteReading.length, canDeleteSpeaking.length, canDeleteWriting.length, canViewLevel.length, canViewListening.length, canViewReading.length, canViewSpeaking.length, canViewWriting.length
])
const filteredCorporates = useMemo(() => {
const participantsAndAdmins = uniq(groups.flatMap((x) => [...x.participants, x.admin])).filter((x) => x !== user?.id);
return users.filter((x) => participantsAndAdmins.includes(x.id) && x.type === "corporate");
@@ -84,7 +109,10 @@ export default function ExamList({user}: {user: User}) {
});
}, [exams, users]);
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, parsedExams);
const filteredExams = useMemo(() => parsedExams.filter(({module}) => modulePermissions[module].view),
[parsedExams, modulePermissions])
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, filteredExams);
const setExams = useExamStore((state) => state.setExams);
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
@@ -104,7 +132,7 @@ export default function ExamList({user}: {user: User}) {
setExams([exam]);
setSelectedModules([module]);
router.push("/exercises");
router.push("/exam");
};
const privatizeExam = async (exam: Exam) => {
@@ -245,7 +273,7 @@ export default function ExamList({user}: {user: User}) {
onClick={async () => await loadExam(row.original.module, row.original.id)}>
<BsUpload className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</button>
{PERMISSIONS.examManagement.delete.includes(user.type) && (
{modulePermissions[row.original.module].delete && (
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteExam(row.original)}>
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>

View File

@@ -8,16 +8,24 @@ import GroupList from "./GroupList";
import PackageList from "./PackageList";
import UserList from "./UserList";
import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions";
import {PermissionType} from "@/interfaces/permissions";
import { EntityWithRoles } from "@/interfaces/entity";
import { useAllowedEntitiesSomePermissions } from "@/hooks/useEntityPermissions";
import { useMemo } from "react";
interface Props {
user: User;
users: User[];
entities: EntityWithRoles[]
permissions: PermissionType[];
}
export default function Lists({user, users, permissions}: Props) {
export default function Lists({user, entities = [], permissions}: Props) {
const entitiesViewExams = useAllowedEntitiesSomePermissions(user, entities, [
'view_reading', 'view_listening', 'view_writing', 'view_speaking', 'view_level'
])
const canViewExams = useMemo(() => entitiesViewExams.length > 0, [entitiesViewExams])
return (
<TabGroup>
<TabList className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">
@@ -32,7 +40,7 @@ export default function Lists({user, users, permissions}: Props) {
}>
User List
</Tab>
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate", "teacher"]) && (
{canViewExams && (
<Tab
className={({selected}) =>
clsx(
@@ -45,17 +53,6 @@ export default function Lists({user, users, permissions}: Props) {
Exam List
</Tab>
)}
<Tab
className={({selected}) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out",
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark",
)
}>
Group List
</Tab>
{checkAccess(user, ["developer", "admin", "corporate"]) && (
<Tab
className={({selected}) =>
@@ -100,14 +97,11 @@ export default function Lists({user, users, permissions}: Props) {
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
<UserList user={user} />
</TabPanel>
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate", "teacher"]) && (
{canViewExams && (
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
<ExamList user={user} />
<ExamList user={user} entities={entities} />
</TabPanel>
)}
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
<GroupList user={user} />
</TabPanel>
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], permissions, "viewCodes") && (
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
<CodeList user={user} />

View File

@@ -196,7 +196,7 @@ export default function UserCreator({ user, users, entities = [], permissions, o
)}
<div className={clsx("flex flex-col gap-4")}>
<label className="font-normal text-base text-mti-gray-dim">Group</label>
<label className="font-normal text-base text-mti-gray-dim">Classroom</label>
<Select
options={groups
.filter((x) => x.entity?.id === entity)

View File

@@ -388,7 +388,7 @@ const LevelGeneration = ({ id }: Props) => {
setExams([exam]);
setSelectedModules(["level"]);
router.push("/exercises");
router.push("/exam");
};
const generateExam = () => {

View File

@@ -311,7 +311,7 @@ const ListeningGeneration = ({id}: Props) => {
setExams([exam]);
setSelectedModules(["listening"]);
router.push("/exercises");
router.push("/exam");
};
return (

View File

@@ -296,7 +296,7 @@ const ReadingGeneration = ({id}: Props) => {
setExams([exam]);
setSelectedModules(["reading"]);
router.push("/exercises");
router.push("/exam");
};
const submitExam = () => {

View File

@@ -311,7 +311,7 @@ const SpeakingGeneration = ({id}: Props) => {
setExams([exam]);
setSelectedModules(["speaking"]);
router.push("/exercises");
router.push("/exam");
};
return (

View File

@@ -113,7 +113,7 @@ const WritingGeneration = ({id}: Props) => {
setExams([exam]);
setSelectedModules(["writing"]);
router.push("/exercises");
router.push("/exam");
};
const submitExam = () => {

View File

@@ -19,7 +19,7 @@ export default function App({Component, pageProps}: AppProps) {
const router = useRouter();
useEffect(() => {
if (router.pathname !== "/exercises") reset();
if (router.pathname !== "/exam" && router.pathname !== "/exercises") reset();
}, [router.pathname, reset]);
useEffect(() => {

View File

@@ -195,7 +195,7 @@ export default function AssignmentView({user, users, entity, assignment}: Props)
.sort(sortByModule)
.map((x) => x!.module),
);
router.push("/exercises");
router.push("/exam");
}
});
};

View File

@@ -76,7 +76,7 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment.exams.map((e) => e.module));
const [assignees, setAssignees] = useState<string[]>(assignment.assignees);
const [teachers, setTeachers] = useState<string[]>(assignment.teachers || []);
const [entity, setEntity] = useState<string | undefined>(entities[0]?.id);
const [entity, setEntity] = useState<string | undefined>(assignment.entity || entities[0]?.id);
const [name, setName] = useState(assignment.name);
const [isLoading, setIsLoading] = useState(false);

View File

@@ -153,7 +153,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, enti
{...a}
users={users}
onClick={
entitiesAllowEdit.length > 0
mapBy(entitiesAllowEdit, 'id').includes(a.entity || "")
? () => router.push(`/assignments/creator/${a.id}`)
: undefined
}

View File

@@ -132,14 +132,14 @@ export default function Home({user, group, users, entity}: Props) {
const renameGroup = () => {
if (!canRenameClassroom) return;
const name = prompt("Rename this group:", group.name);
const name = prompt("Rename this classroom:", group.name);
if (!name) return;
setIsLoading(true);
axios
.patch(`/api/groups/${group.id}`, {name})
.then(() => {
toast.success("The group has been updated successfully!");
toast.success("The classroom has been updated successfully!");
router.replace(router.asPath);
})
.catch((e) => {
@@ -151,14 +151,14 @@ export default function Home({user, group, users, entity}: Props) {
const deleteGroup = () => {
if (!canDeleteClassroom) return;
if (!confirm("Are you sure you want to delete this group?")) return;
if (!confirm("Are you sure you want to delete this classroom?")) return;
setIsLoading(true);
axios
.delete(`/api/groups/${group.id}`)
.then(() => {
toast.success("This group has been successfully deleted!");
toast.success("This classroom has been successfully deleted!");
router.replace("/classrooms");
})
.catch((e) => {
@@ -203,14 +203,14 @@ export default function Home({user, group, users, entity}: Props) {
disabled={isLoading || !canRenameClassroom}
className="flex items-center gap-1 px-2 py-2 border rounded-full hover:bg-neutral-100 disabled:hover:bg-transparent disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsTag />
<span className="text-xs">Rename Group</span>
<span className="text-xs">Rename Classroom</span>
</button>
<button
onClick={deleteGroup}
disabled={isLoading || !canDeleteClassroom}
className="flex items-center gap-1 px-2 py-2 border border-mti-rose rounded-full bg-mti-rose-light text-white hover:bg-mti-rose-dark disabled:hover:bg-mti-rose-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsTrash />
<span className="text-xs">Delete Group</span>
<span className="text-xs">Delete Classroom</span>
</button>
</div>
)}

View File

@@ -85,7 +85,7 @@ export default function Home({user, groups, entities}: Props) {
href={`/classrooms/create`}
className="p-4 border hover:text-mti-purple rounded-xl flex flex-col items-center justify-center gap-0 hover:border-mti-purple transition ease-in-out duration-300 text-left cursor-pointer">
<BsPlus size={40} />
<span className="font-semibold">Create Group</span>
<span className="font-semibold">Create Classroom</span>
</Link>
);

View File

@@ -100,7 +100,7 @@ export default function Dashboard({user, entities, assignments, stats, invites,
);
setAssignment(assignment);
router.push("/exercises");
router.push("/exam");
}
};

View File

@@ -111,7 +111,13 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
<IconCard Icon={BsPersonFill} label="Students" value={students.length} color="purple" />
<IconCard
Icon={BsPersonFill}
onClick={() => router.push("/users?type=student")}
label="Students"
value={students.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/classrooms")}
Icon={BsPeople}

View File

@@ -45,14 +45,19 @@ const USER_MANAGEMENT: PermissionLayout[] = [
]
const EXAM_MANAGEMENT: PermissionLayout[] = [
{label: "View Reading", key: "view_reading"},
{label: "Generate Reading", key: "generate_reading"},
{label: "Delete Reading", key: "delete_reading"},
{label: "View Listening", key: "view_listening"},
{label: "Generate Listening", key: "generate_listening"},
{label: "Delete Listening", key: "delete_listening"},
{label: "View Writing", key: "view_writing"},
{label: "Generate Writing", key: "generate_writing"},
{label: "Delete Writing", key: "delete_writing"},
{label: "View Speaking", key: "view_speaking"},
{label: "Generate Speaking", key: "generate_speaking"},
{label: "Delete Speaking", key: "delete_speaking"},
{label: "View Level", key: "view_level"},
{label: "Generate Level", key: "generate_level"},
{label: "Delete Level", key: "delete_level"},
]
@@ -192,7 +197,7 @@ export default function Role({user, entity, role, userCount}: Props) {
.finally(() => setIsLoading(false));
}
const togglePermissions = (p: string) => setPermissions(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p])
const togglePermissions = (p: RolePermission) => setPermissions(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p])
return (
<>
@@ -278,7 +283,7 @@ export default function Role({user, entity, role, userCount}: Props) {
</Checkbox>
</div>
<Separator />
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-3 gap-4">
{EXAM_MANAGEMENT.map(({label, key}) => (
<Checkbox disabled={!canEditPermissions} key={key} isChecked={permissions.includes(key)} onChange={() => togglePermissions(key)}>
{ label }

View File

@@ -33,7 +33,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) =
if (assignmentID) {
const assignment = await getAssignment(assignmentID)
if (!assignment) return redirect("/exercises")
if (!assignment) return redirect("/exam")
if (!["admin", "developer"].includes(user.type) && !assignment.assignees.includes(user.id)) return redirect("/exercises")
const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
@@ -44,7 +44,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) =
moment(assignment.startDate).isBefore(moment()) ||
moment(assignment.endDate).isAfter(moment())
)
return redirect("/exercises")
return redirect("/exam")
return {
props: serialize({user, assignment, exams, session})

View File

@@ -141,7 +141,7 @@ export default function Admin({ user, entities, permissions }: Props) {
)}
</section>
<section className="w-full">
<Lists user={user} users={users} permissions={permissions} />
<Lists user={user} entities={entities} permissions={permissions} />
</section>
</Layout>
</>

View File

@@ -136,7 +136,7 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
.sort(sortByModule)
.map((x) => x!.module),
);
router.push("/exercises");
router.push("/exam");
}
});
};