ui: update ui look on exam manag

This commit is contained in:
Mengisteab
2026-06-30 09:09:02 +00:00
parent ae78c54c51
commit ef69448bbd
3 changed files with 7330 additions and 4 deletions

View File

@@ -18,7 +18,7 @@ import {
import { IconInfoCircle, IconCheck, IconX } from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { useApiQuery } from '@ema-platform/api';
import { useCreateResultMutation } from '../api/result-api';
import { useCreateResultMutation, useUpdateResultMutation } from '../api/result-api';
import type { Exam } from '../../exam/types/exam';
function InfoRow({ label, value }: { label: string; value: string }) {
@@ -50,6 +50,7 @@ export function RecordResultModal({
params: { q: 'w=type:=:SEAFARER' },
});
const [createResult, { isLoading: isSaving }] = useCreateResultMutation();
const [updateResult] = useUpdateResultMutation();
const seafarers = profilesRes?.items ?? [];
const questions = exam.questions ?? [];
@@ -85,13 +86,16 @@ export function RecordResultModal({
score: scores[q.id] ?? 0,
remark: questionRemarks[q.id] ?? '',
}));
await createResult({
const created = await createResult({
seafarerId: selectedSeafarerId,
examId: exam.id,
resultBreakdowns: breakdowns,
totalScore,
remark: remark ? { en: remark, am: '' } : undefined,
}).unwrap();
if (passed && created.id) {
await updateResult({ id: created.id, status: 'PASSED' }).unwrap();
}
notify.success(`Result recorded — ${passed ? 'PASSED' : 'FAILED'} (${totalScore}/${exam.cuttingPoint})`);
setSelectedSeafarerId(null);
setScores({});

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, type ElementType } from 'react';
import {
Stack,
Title,
@@ -32,6 +32,10 @@ import {
IconClock,
IconMapPin,
IconPlus,
IconClipboardList,
IconCircleCheck,
IconCircleX,
IconChartBar,
} from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { useGetResultsQuery, useLazyGetResultQuery, useDeleteResultMutation } from '../api/result-api';
@@ -45,6 +49,41 @@ const STATUS_COLOR: Record<string, string> = {
FAILED: 'red',
};
function ResultStat({
label,
value,
sub,
icon: Icon,
color,
}: {
label: string;
value: string;
sub?: string;
icon: ElementType;
color: string;
}) {
return (
<Paper p="lg" radius="lg" withBorder>
<Group justify="space-between" align="flex-start">
<ThemeIcon size={46} radius="md" variant="light" color={color}>
<Icon size={22} />
</ThemeIcon>
{sub && (
<Badge variant="light" color={color} radius="sm">
{sub}
</Badge>
)}
</Group>
<Text fz={30} fw={800} mt="md" lh={1.1}>
{value}
</Text>
<Text size="sm" c="dimmed" mt={4}>
{label}
</Text>
</Paper>
);
}
function InfoRow({ label, value }: { label: string; value: string }) {
return (
<div>
@@ -242,6 +281,14 @@ export function ResultPage() {
const filtered = results.filter((r) => !examFilter || r.examId === examFilter);
const total = results.length;
const passedCount = results.filter((r) => r.status === 'PASSED').length;
const failedCount = total - passedCount;
const passRate = total ? Math.round((passedCount / total) * 100) : 0;
const avgScore = total
? (results.reduce((s, r) => s + Number(r.totalScore || 0), 0) / total).toFixed(1)
: '0';
const getExamTitle = (id: string) => exams.find((e) => e.id === id)?.title?.en ?? '-';
const viewDetail = (result: Result) => {
@@ -276,6 +323,13 @@ export function ResultPage() {
</Button>
</Group>
<SimpleGrid cols={{ base: 2, lg: 4 }} spacing="lg">
<ResultStat label="Total Results" value={String(total)} icon={IconClipboardList} color="blue" />
<ResultStat label="Passed" value={String(passedCount)} sub={`${passRate}%`} icon={IconCircleCheck} color="teal" />
<ResultStat label="Failed" value={String(failedCount)} sub={`${total ? 100 - passRate : 0}%`} icon={IconCircleX} color="red" />
<ResultStat label="Avg Score" value={avgScore} icon={IconChartBar} color="indigo" />
</SimpleGrid>
<Paper withBorder radius="md">
<Group p="md" justify="space-between" wrap="wrap" gap="sm">
<Text fw={600}>Results</Text>
@@ -312,7 +366,20 @@ export function ResultPage() {
<Table.Td><Text fz="sm">{r.exam ? r.exam.title.en : getExamTitle(r.examId)}</Text></Table.Td>
<Table.Td><Text fz="sm" fw={600}>{r.totalScore}</Text></Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color={STATUS_COLOR[r.status]}>{r.status}</Badge>
<Badge
size="sm"
variant="light"
color={STATUS_COLOR[r.status]}
leftSection={
<Box
w={6}
h={6}
style={{ borderRadius: 999, background: `var(--mantine-color-${STATUS_COLOR[r.status]}-6)` }}
/>
}
>
{r.status}
</Badge>
</Table.Td>
<Table.Td><Text fz="sm">{new Date(r.createdAt).toLocaleDateString()}</Text></Table.Td>
<Table.Td>

7255
exam-result.pen Normal file

File diff suppressed because it is too large Load Diff