From d7420d2de6e7873c2e7f183975afbc31b8762962 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Fri, 26 Jun 2026 12:10:38 +0300 Subject: [PATCH] feat: added exam modules --- .../certification/api/certification-api.ts | 45 +++ .../certification/pages/CertificationPage.tsx | 202 ++++++++++ .../certification/types/certification.ts | 30 ++ .../src/app/features/exam/api/exam-api.ts | 55 +++ .../exam/components/QuestionAssigner.tsx | 158 ++++++++ .../features/exam/pages/ExamDetailPage.tsx | 381 ++++++++++++++++++ .../src/app/features/exam/pages/ExamPage.tsx | 379 +++++++++++++++++ .../src/app/features/exam/types/exam.ts | 81 ++++ .../app/features/question/api/question-api.ts | 45 +++ .../features/question/pages/QuestionPage.tsx | 239 +++++++++++ .../app/features/question/types/question.ts | 45 +++ .../src/app/features/result/api/result-api.ts | 45 +++ .../app/features/result/pages/ResultPage.tsx | 194 +++++++++ .../src/app/features/result/types/result.ts | 42 ++ .../src/app/layouts/BackofficeLayout.tsx | 8 + apps/backoffice/src/app/router/index.tsx | 10 + 16 files changed, 1959 insertions(+) create mode 100644 apps/backoffice/src/app/features/certification/api/certification-api.ts create mode 100644 apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx create mode 100644 apps/backoffice/src/app/features/certification/types/certification.ts create mode 100644 apps/backoffice/src/app/features/exam/api/exam-api.ts create mode 100644 apps/backoffice/src/app/features/exam/components/QuestionAssigner.tsx create mode 100644 apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx create mode 100644 apps/backoffice/src/app/features/exam/pages/ExamPage.tsx create mode 100644 apps/backoffice/src/app/features/exam/types/exam.ts create mode 100644 apps/backoffice/src/app/features/question/api/question-api.ts create mode 100644 apps/backoffice/src/app/features/question/pages/QuestionPage.tsx create mode 100644 apps/backoffice/src/app/features/question/types/question.ts create mode 100644 apps/backoffice/src/app/features/result/api/result-api.ts create mode 100644 apps/backoffice/src/app/features/result/pages/ResultPage.tsx create mode 100644 apps/backoffice/src/app/features/result/types/result.ts diff --git a/apps/backoffice/src/app/features/certification/api/certification-api.ts b/apps/backoffice/src/app/features/certification/api/certification-api.ts new file mode 100644 index 000000000..878947756 --- /dev/null +++ b/apps/backoffice/src/app/features/certification/api/certification-api.ts @@ -0,0 +1,45 @@ +import { baseApi } from '@ema-platform/api'; +import type { + Certification, + ListResponse, + CreateCertificationPayload, + UpdateCertificationPayload, +} from '../types/certification'; + +const certificationApi = baseApi.injectEndpoints({ + endpoints: (builder) => ({ + getCertifications: builder.query, void>({ + query: () => '/certifications', + providesTags: ['Api'], + }), + getCertification: builder.query({ + query: (id) => `/certifications/${id}`, + providesTags: ['Api'], + }), + createCertification: builder.mutation({ + query: (body) => ({ url: '/certifications', method: 'POST', body }), + invalidatesTags: ['Api'], + }), + updateCertification: builder.mutation({ + query: ({ id, ...body }) => ({ + url: `/certifications/${id}`, + method: 'PUT', + body, + }), + invalidatesTags: ['Api'], + }), + deleteCertification: builder.mutation({ + query: (id) => ({ url: `/certifications/${id}`, method: 'DELETE' }), + invalidatesTags: ['Api'], + }), + }), + overrideExisting: false, +}); + +export const { + useGetCertificationsQuery, + useGetCertificationQuery, + useCreateCertificationMutation, + useUpdateCertificationMutation, + useDeleteCertificationMutation, +} = certificationApi; diff --git a/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx b/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx new file mode 100644 index 000000000..7f18fb7f6 --- /dev/null +++ b/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx @@ -0,0 +1,202 @@ +import { useState } from 'react'; +import { + Stack, + Title, + Group, + Button, + Table, + Badge, + ActionIcon, + Modal, + Text, + TextInput, + Textarea, + Paper, + Loader, + Center, + Alert, +} from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCertificate } from '@tabler/icons-react'; +import { notify } from '@ema-platform/ui'; +import { + useGetCertificationsQuery, + useCreateCertificationMutation, + useUpdateCertificationMutation, + useDeleteCertificationMutation, +} from '../api/certification-api'; +import type { Certification } from '../types/certification'; + +function CertificationForm({ + editing, + isSubmitting, + onSubmit, + onCancel, +}: { + editing: Certification | null; + isSubmitting: boolean; + onSubmit: (values: { nameEn: string; nameAm: string; descEn: string; descAm: string }, isEdit: boolean) => void; + onCancel: () => void; +}) { + const [nameEn, setNameEn] = useState(editing?.name?.en ?? ''); + const [nameAm, setNameAm] = useState(editing?.name?.am ?? ''); + const [descEn, setDescEn] = useState(editing?.description?.en ?? ''); + const [descAm, setDescAm] = useState(editing?.description?.am ?? ''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!nameEn || !nameAm) { + notify.error('Name fields are required'); + return; + } + onSubmit({ nameEn, nameAm, descEn, descAm }, !!editing); + }; + + return ( + +
+ + setNameEn(e.currentTarget.value)} size="sm" required /> + setNameAm(e.currentTarget.value)} size="sm" required /> +