feat(i18n): add new translations for refresh, view, toggle columns, and no results

refactor(payment): remove payment API client and related tests, consolidating payment logic

refactor(payment): delete payment modal and associated constants, hooks, and types

fix(ui): enhance AdvancedTable with refresh button and column toggle functionality

fix(ui): implement pagination in useServerTable for better data handling
This commit is contained in:
estifanos
2026-08-03 09:22:02 +00:00
parent 52e2f4c009
commit 599dc4aa83
19 changed files with 452 additions and 1395 deletions

View File

@@ -5,7 +5,6 @@ import {
Title,
Group,
Button,
Table,
Badge,
ActionIcon,
Modal,
@@ -13,8 +12,7 @@ import {
TextInput,
Textarea,
Paper,
Loader,
Center,
Card,
Alert,
Select,
NumberInput,
@@ -33,7 +31,7 @@ import {
IconClipboardList,
IconDetails,
} from "@tabler/icons-react";
import { notify, useErrorHandler } from "@ema-platform/ui";
import { notify, useErrorHandler, AdvancedTable, useServerTable, type AdvancedColumn } from "@ema-platform/ui";
import { useGetCertificationsQuery } from "../../certification/api/certification-api";
import {
useGetExamsQuery,
@@ -356,7 +354,8 @@ export function ExamPage() {
const { handleError } = useErrorHandler();
const locale = i18n.language as "en" | "am";
const { data: certRes } = useGetCertificationsQuery();
const { data, isLoading, isError } = useGetExamsQuery();
const { data, isFetching, isError, refetch } = useGetExamsQuery();
const { setPageIndex, pageSize, paginate } = useServerTable({ pageSize: 25 });
const [createExam, { isLoading: isCreating }] = useCreateExamMutation();
const [updateExam, { isLoading: isUpdating }] = useUpdateExamMutation();
const [deleteExam] = useDeleteExamMutation();
@@ -431,12 +430,6 @@ export function ExamPage() {
}
};
if (isLoading)
return (
<Center py="xl">
<Loader />
</Center>
);
if (isError)
return (
<Alert
@@ -446,6 +439,109 @@ export function ExamPage() {
/>
);
const columns: AdvancedColumn<Exam>[] = [
{
header: t("exam.columns.title"),
cell: ({ row }) => (
<Text
fz="sm"
fw={500}
c="blue"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/exams/${row.original.id}`)}
>
{row.original.title[locale]}
</Text>
),
},
{
header: t("exam.columns.certification"),
cell: ({ row }) => <Text fz="sm">{getCertName(row.original.certificationId)}</Text>,
},
{
header: t("exam.columns.date"),
cell: ({ row }) => <Text fz="sm">{row.original.date}</Text>,
},
{
header: t("exam.columns.type"),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={row.original.type === "WRITTEN" ? "blue" : "orange"}>
{t(`exam.type.${row.original.type}`)}
</Badge>
),
},
{
header: t("exam.columns.form"),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={row.original.form === "ESSAY" ? "blue" : "violet"}>
{t(`exam.formType.${row.original.form}`)}
</Badge>
),
},
{
header: t("exam.columns.venue"),
cell: ({ row }) => <Text fz="sm">{row.original.venue}</Text>,
},
{
header: t("exam.columns.questions"),
cell: ({ row }) => (
<Badge size="sm" variant="light" color="gray">
{row.original.questions?.length ?? 0}
</Badge>
),
},
{
header: t("exam.columns.status"),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={STATUS_COLOR[row.original.status]}>
{t(`exam.status.${row.original.status}`)}
</Badge>
),
},
{
header: t("exam.columns.actions"),
align: "right",
cell: ({ row }) => (
<Group gap="xs">
<ActionIcon
variant="subtle"
color="blue"
size="sm"
onClick={() => {
setEditing(row.original);
setShowForm(true);
}}
>
<IconEdit size={14} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => {
setDeleteTarget(row.original);
openDelete();
}}
>
<IconTrash size={14} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => {
navigate(`/exams/${row.original.id}`);
}}
>
<IconDetails size={14} />
</ActionIcon>
</Group>
),
},
];
const page = paginate(exams);
return (
<Stack gap="lg">
<Group justify="space-between" align="flex-end">
@@ -477,127 +573,20 @@ export function ExamPage() {
/>
)}
<Paper withBorder radius="md">
<Table striped highlightOnHover>
<Table.Thead bg="var(--mantine-color-default-hover)">
<Table.Tr>
<Table.Th>{t("exam.columns.title")}</Table.Th>
<Table.Th>{t("exam.columns.certification")}</Table.Th>
<Table.Th>{t("exam.columns.date")}</Table.Th>
<Table.Th>{t("exam.columns.type")}</Table.Th>
<Table.Th>{t("exam.columns.form")}</Table.Th>
<Table.Th>{t("exam.columns.venue")}</Table.Th>
<Table.Th>{t("exam.columns.questions")}</Table.Th>
<Table.Th>{t("exam.columns.status")}</Table.Th>
<Table.Th>{t("exam.columns.actions")}</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{exams.map((exam) => (
<Table.Tr key={exam.id}>
<Table.Td>
<Text
fz="sm"
fw={500}
c="blue"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/exams/${exam.id}`)}
>
{exam.title[locale]}
</Text>
</Table.Td>
<Table.Td>
<Text fz="sm">{getCertName(exam.certificationId)}</Text>
</Table.Td>
<Table.Td>
<Text fz="sm">{exam.date}</Text>
</Table.Td>
<Table.Td>
<Badge
size="sm"
variant="light"
color={exam.type === "WRITTEN" ? "blue" : "orange"}
>
{t(`exam.type.${exam.type}`)}
</Badge>
</Table.Td>
<Table.Td>
<Badge
size="sm"
variant="light"
color={exam.form === "ESSAY" ? "blue" : "violet"}
>
{t(`exam.formType.${exam.form}`)}
</Badge>
</Table.Td>
<Table.Td>
<Text fz="sm">{exam.venue}</Text>
</Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color="gray">
{exam.questions?.length ?? 0}
</Badge>
</Table.Td>
<Table.Td>
<Badge
size="sm"
variant="light"
color={STATUS_COLOR[exam.status]}
>
{t(`exam.status.${exam.status}`)}
</Badge>
</Table.Td>
<Table.Td>
<Group gap="xs">
<ActionIcon
variant="subtle"
color="blue"
size="sm"
onClick={() => {
setEditing(exam);
setShowForm(true);
}}
>
<IconEdit size={14} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => {
setDeleteTarget(exam);
openDelete();
}}
>
<IconTrash size={14} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => {
navigate(`/exams/${exam.id}`);
}}
>
<IconDetails size={14} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
))}
{exams.length === 0 && (
<Table.Tr>
<Table.Td colSpan={9}>
<Text c="dimmed" ta="center" py="xl">
{t("exam.noItems")}
</Text>
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
</Paper>
<Card withBorder padding={0}>
<AdvancedTable
columns={columns}
data={page.rows}
tableName={t("exam.title")}
itemCount={page.itemCount}
pageIndex={page.pageIndex}
onPageChange={setPageIndex}
pageSize={pageSize}
refresh={refetch}
isLoading={isFetching}
emptyText={t("exam.noItems")}
/>
</Card>
{/* Delete confirmation */}
<Modal