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

@@ -4,15 +4,13 @@ import {
Title,
Group,
Button,
Table,
Badge,
ActionIcon,
Modal,
Text,
TextInput,
Paper,
Loader,
Center,
Card,
Alert,
Select,
NumberInput,
@@ -20,7 +18,7 @@ import {
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { IconEdit, IconTrash, IconPlus, IconInfoCircle } 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 {
useGetQuestionsQuery,
@@ -104,7 +102,8 @@ export function QuestionPage() {
const locale = i18n.language as 'en' | 'am';
const { handleError } = useErrorHandler();
const { data: certRes } = useGetCertificationsQuery();
const { data, isLoading, isError } = useGetQuestionsQuery();
const { data, isFetching, isError, refetch } = useGetQuestionsQuery();
const { setPageIndex, pageSize, paginate } = useServerTable({ pageSize: 25 });
const [createQ, { isLoading: isCreating }] = useCreateQuestionMutation();
const [updateQ, { isLoading: isUpdating }] = useUpdateQuestionMutation();
const [deleteQ] = useDeleteQuestionMutation();
@@ -121,6 +120,7 @@ export function QuestionPage() {
const certOptions = certifications.filter((c) => c.isActive).map((c) => ({ value: c.id, label: c.name[locale] }));
const filtered = questions.filter((q) => !certFilter || q.certificationId === certFilter);
const page = paginate(filtered);
const getCertName = (id: string) => certifications.find((c) => c.id === id)?.name?.[locale] ?? '-';
@@ -158,9 +158,53 @@ export function QuestionPage() {
}
};
if (isLoading) return <Center py="xl"><Loader /></Center>;
if (isError) return <Alert icon={<IconInfoCircle size={16} />} color="red" title={t('question.loadError')} />;
const columns: AdvancedColumn<Question>[] = [
{
header: t('question.columns.title'),
cell: ({ row }) => <Text fz="sm" maw={300} lineClamp={2}>{row.original.title[locale]}</Text>,
},
{
header: t('question.columns.certification'),
cell: ({ row }) => <Text fz="sm">{getCertName(row.original.certificationId)}</Text>,
},
{
header: t('question.columns.form'),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={row.original.form === 'ESSAY' ? 'blue' : 'violet'}>
{t(`question.form.${row.original.form === 'ESSAY' ? 'essay' : 'choice'}`)}
</Badge>
),
},
{
header: t('question.columns.points'),
cell: ({ row }) => <Text fz="sm" fw={600}>{row.original.points}</Text>,
},
{
header: t('question.columns.status'),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={row.original.isActive ? 'teal' : 'gray'}>
{row.original.isActive ? t('question.status.active') : t('question.status.inactive')}
</Badge>
),
},
{
header: '',
label: t('question.columns.actions', 'Actions'),
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>
</Group>
),
},
];
return (
<Stack gap="lg">
<Group justify="space-between" align="flex-end">
@@ -182,54 +226,32 @@ export function QuestionPage() {
/>
)}
<Paper withBorder radius="md">
<Card withBorder padding={0}>
<Group p="md" justify="space-between" wrap="wrap" gap="sm">
<Text fw={600}>{t('question.pool')}</Text>
<Select placeholder={t('question.filterByCertification')} data={[{ value: '', label: 'All' }, ...certOptions]} value={certFilter} onChange={(v) => setCertFilter(v ?? null)} size="sm" style={{ width: 280 }} clearable />
<Select
placeholder={t('question.filterByCertification')}
data={[{ value: '', label: 'All' }, ...certOptions]}
value={certFilter}
onChange={(v) => { setCertFilter(v ?? null); setPageIndex(0); }}
size="sm"
style={{ width: 280 }}
clearable
/>
</Group>
<Table striped highlightOnHover>
<Table.Thead bg="var(--mantine-color-default-hover)">
<Table.Tr>
<Table.Th>{t('question.columns.title')}</Table.Th>
<Table.Th>{t('question.columns.certification')}</Table.Th>
<Table.Th>{t('question.columns.form')}</Table.Th>
<Table.Th>{t('question.columns.points')}</Table.Th>
<Table.Th>{t('question.columns.status')}</Table.Th>
<Table.Th />
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{filtered.map((q) => (
<Table.Tr key={q.id}>
<Table.Td><Text fz="sm" maw={300} lineClamp={2}>{q.title[locale]}</Text></Table.Td>
<Table.Td><Text fz="sm">{getCertName(q.certificationId)}</Text></Table.Td>
<Table.Td><Badge size="sm" variant="light" color={q.form === 'ESSAY' ? 'blue' : 'violet'}>{t(`question.form.${q.form === 'ESSAY' ? 'essay' : 'choice'}`)}</Badge></Table.Td>
<Table.Td><Text fz="sm" fw={600}>{q.points}</Text></Table.Td>
<Table.Td>
<Badge size="sm" variant="light" color={q.isActive ? 'teal' : 'gray'}>{q.isActive ? t('question.status.active') : t('question.status.inactive')}</Badge>
</Table.Td>
<Table.Td>
<Group gap="xs">
<ActionIcon variant="subtle" color="blue" size="sm" onClick={() => { setEditing(q); setShowForm(true); }}>
<IconEdit size={14} />
</ActionIcon>
<ActionIcon variant="subtle" color="red" size="sm" onClick={() => { setDeleteTarget(q); openDelete(); }}>
<IconTrash size={14} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
))}
{filtered.length === 0 && (
<Table.Tr>
<Table.Td colSpan={6}>
<Text c="dimmed" ta="center" py="xl">{t('question.noQuestions')}</Text>
</Table.Td>
</Table.Tr>
)}
</Table.Tbody>
</Table>
</Paper>
<AdvancedTable
columns={columns}
data={page.rows}
tableName={t('question.title')}
itemCount={page.itemCount}
pageIndex={page.pageIndex}
onPageChange={setPageIndex}
pageSize={pageSize}
refresh={refetch}
isLoading={isFetching}
emptyText={t('question.noQuestions')}
/>
</Card>
<Modal opened={deleteOpened} onClose={closeDelete} title={t('question.confirmDelete')} size="sm">
<Text mb="md">{t('question.deleteConfirmText')}</Text>