Files
emaui/apps/backoffice/src/app/features/certification/pages/CertificationPage.tsx

210 lines
7.7 KiB
TypeScript

import { useState } from 'react';
import {
Stack,
Title,
Group,
Button,
Badge,
ActionIcon,
Modal,
Text,
TextInput,
Textarea,
Card,
Alert,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import { IconEdit, IconTrash, IconPlus, IconInfoCircle, IconCertificate } from '@tabler/icons-react';
import { notify, useErrorHandler, AdvancedTable, useServerTable, ModalFooter, type AdvancedColumn } 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 { t } = useTranslation();
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 (
<Modal opened onClose={onCancel} title={editing ? t('certification.update') : t('certification.add')} size="md">
<form onSubmit={handleSubmit}>
<Stack gap="sm">
<TextInput label={t('certification.form.nameEn')} placeholder={t('certification.form.nameEnPlaceholder')} value={nameEn} onChange={(e) => setNameEn(e.currentTarget.value)} size="sm" required />
<TextInput label={t('certification.form.nameAm')} placeholder={t('certification.form.nameAmPlaceholder')} value={nameAm} onChange={(e) => setNameAm(e.currentTarget.value)} size="sm" required />
<Textarea label={t('certification.form.descEn')} placeholder={t('certification.form.descEnPlaceholder')} value={descEn} onChange={(e) => setDescEn(e.currentTarget.value)} size="sm" autosize minRows={2} />
<Textarea label={t('certification.form.descAm')} placeholder={t('certification.form.descAmPlaceholder')} value={descAm} onChange={(e) => setDescAm(e.currentTarget.value)} size="sm" autosize minRows={2} />
<ModalFooter>
<Button variant="default" onClick={onCancel} size="sm">{t('certification.cancel')}</Button>
<Button type="submit" size="sm" loading={isSubmitting}>{editing ? t('certification.update') : t('certification.create')}</Button>
</ModalFooter>
</Stack>
</form>
</Modal>
);
}
export function CertificationPage() {
const { t, i18n } = useTranslation();
const locale = i18n.language as 'en' | 'am';
const { handleError } = useErrorHandler();
const { data, isFetching, isError, refetch } = useGetCertificationsQuery();
const { setPageIndex, pageSize, setPageSize, paginate } = useServerTable();
const [createCert, { isLoading: isCreating }] = useCreateCertificationMutation();
const [updateCert, { isLoading: isUpdating }] = useUpdateCertificationMutation();
const [deleteCert] = useDeleteCertificationMutation();
const certifications = data?.items ?? [];
const [editing, setEditing] = useState<Certification | null>(null);
const [showForm, setShowForm] = useState(false);
const [deleteTarget, setDeleteTarget] = useState<Certification | null>(null);
const [deleteOpened, { open: openDelete, close: closeDelete }] = useDisclosure(false);
const resetForm = () => {
setEditing(null);
setShowForm(false);
};
const handleSubmit = async (values: { nameEn: string; nameAm: string; descEn: string; descAm: string }, isEdit: boolean) => {
const name = { en: values.nameEn, am: values.nameAm };
const description = { en: values.descEn, am: values.descAm };
try {
if (isEdit && editing) {
await updateCert({ id: editing.id, name, description }).unwrap();
notify.success(t('certification.updated'));
} else {
await createCert({ name, description }).unwrap();
notify.success(t('certification.created'));
}
resetForm();
} catch (e) {
handleError(e);
}
};
const handleDelete = async () => {
if (!deleteTarget) return;
try {
await deleteCert(deleteTarget.id).unwrap();
notify.success(t('certification.deleted'));
closeDelete();
setDeleteTarget(null);
} catch (e) {
handleError(e);
}
};
if (isError) return <Alert icon={<IconInfoCircle size={16} />} color="red" title={t('certification.loadError')} />;
const columns: AdvancedColumn<Certification>[] = [
{
header: t('certification.columns.name'),
cell: ({ row }) => <Text fz="sm" fw={500}>{row.original.name[locale]}</Text>,
},
{
header: t('certification.columns.description'),
cell: ({ row }) => <Text fz="sm" lineClamp={2} maw={250}>{row.original.description[locale]}</Text>,
},
{
header: t('certification.columns.status'),
cell: ({ row }) => (
<Badge size="sm" variant="light" color={row.original.isActive ? 'teal' : 'gray'}>
{row.original.isActive ? t('certification.status.active') : t('certification.status.inactive')}
</Badge>
),
},
{
header: '',
label: t('certification.columns.actions', 'Actions'),
size: 90,
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>
),
},
];
const page = paginate(certifications);
return (
<Stack gap="lg">
<Group justify="space-between" align="flex-end">
<div>
<Title order={2}>{t('certification.title')}</Title>
<Text fz="sm" c="dimmed">{t('certification.subtitle')}</Text>
</div>
{!showForm && (
<Button variant="light" leftSection={<IconPlus size={16} />} onClick={() => setShowForm(true)} size="sm">
{t('certification.add')}
</Button>
)}
</Group>
{showForm && (
<CertificationForm
editing={editing}
isSubmitting={isCreating || isUpdating}
onSubmit={handleSubmit}
onCancel={resetForm}
/>
)}
<Card withBorder padding={0}>
<AdvancedTable
columns={columns}
data={page.rows}
tableName={t('certification.title')}
itemCount={page.itemCount}
pageIndex={page.pageIndex}
onPageChange={setPageIndex}
pageSize={pageSize}
onPageSizeChange={setPageSize}
refresh={refetch}
isLoading={isFetching}
emptyText={t('certification.noItems')}
/>
</Card>
<Modal opened={deleteOpened} onClose={closeDelete} title={t('certification.confirmDelete')} size="sm">
<Text mb="md">{t('certification.deleteConfirmText', { name: deleteTarget?.name?.[locale] })}</Text>
<ModalFooter>
<Button variant="default" onClick={closeDelete} size="sm">{t('certification.cancel')}</Button>
<Button color="red" onClick={handleDelete} size="sm">{t('certification.delete')}</Button>
</ModalFooter>
</Modal>
</Stack>
);
}