Add rankKey support to certification forms and exams

- Introduced rankKey field in Certification interface and payloads.
- Updated CertificationForm to handle rankKey input.
- Enhanced ScheduleExamModal to filter exams based on rank.
- Added getEligibleExams API query to fetch exams relevant to the application's rank.
- Updated related components and types to accommodate new rankKey functionality.
This commit is contained in:
Nati
2026-08-21 19:10:01 +00:00
parent 21ffca9094
commit c3977897b7
8 changed files with 85 additions and 14 deletions

View File

@@ -16,6 +16,15 @@ export function certificationColumns(
header: t('certification.columns.description'),
cell: ({ row }) => <Text fz="sm" lineClamp={2} maw={250}>{row.original.description[locale]}</Text>,
},
{
header: t('certification.columns.rank', 'Rank'),
cell: ({ row }) =>
row.original.rankKey ? (
<Badge size="sm" variant="outline" color="violet">{row.original.rankKey}</Badge>
) : (
<Text fz="sm" c="dimmed"></Text>
),
},
{
header: t('certification.columns.status'),
cell: ({ row }) => (

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import {Stack, Button, Modal, Text, TextInput, Textarea, Card} from '@mantine/core';
import {Stack, Button, Modal, Text, TextInput, Textarea, Select, Card} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
import {IconPlus} from '@tabler/icons-react';
@@ -10,7 +10,7 @@ import {
useUpdateCertificationMutation,
useDeleteCertificationMutation,
} from '../../api/certification-api';
import type { Certification } from '../../types/certification';
import { RANK_KEY_OPTIONS, type Certification } from '../../types/certification';
import { certificationColumns } from './columns';
import { certificationActionsColumn } from './actions';
@@ -22,7 +22,7 @@ function CertificationForm({
}: {
editing: Certification | null;
isSubmitting: boolean;
onSubmit: (values: { nameEn: string; nameAm: string; descEn: string; descAm: string }, isEdit: boolean) => void;
onSubmit: (values: { nameEn: string; nameAm: string; descEn: string; descAm: string; rankKey: string | null }, isEdit: boolean) => void;
onCancel: () => void;
}) {
const { t } = useTranslation();
@@ -30,6 +30,7 @@ function CertificationForm({
const [nameAm, setNameAm] = useState(editing?.name?.am ?? '');
const [descEn, setDescEn] = useState(editing?.description?.en ?? '');
const [descAm, setDescAm] = useState(editing?.description?.am ?? '');
const [rankKey, setRankKey] = useState<string | null>(editing?.rankKey ?? null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -37,7 +38,7 @@ function CertificationForm({
notify.error('Name fields are required');
return;
}
onSubmit({ nameEn, nameAm, descEn, descAm }, !!editing);
onSubmit({ nameEn, nameAm, descEn, descAm, rankKey }, !!editing);
};
return (
@@ -48,6 +49,17 @@ function CertificationForm({
<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} />
<Select
label={t('certification.form.rankKey', 'STCW rank (for exam scheduling)')}
description={t('certification.form.rankKeyHint', 'Leave blank if this certification is not part of the examined CoC/CoP ladder.')}
placeholder={t('certification.form.rankKeyPlaceholder', 'Not rank-specific')}
data={RANK_KEY_OPTIONS as unknown as { value: string; label: string }[]}
value={rankKey}
onChange={setRankKey}
size="sm"
clearable
searchable
/>
<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>
@@ -80,15 +92,17 @@ export function CertificationPage() {
setShowForm(false);
};
const handleSubmit = async (values: { nameEn: string; nameAm: string; descEn: string; descAm: string }, isEdit: boolean) => {
const handleSubmit = async (values: { nameEn: string; nameAm: string; descEn: string; descAm: string; rankKey: string | null }, 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();
// null clears a previously-set rank; undefined would leave it
// untouched server-side, so the two are not interchangeable here.
await updateCert({ id: editing.id, name, description, rankKey: values.rankKey }).unwrap();
notify.success(t('certification.updated'));
} else {
await createCert({ name, description }).unwrap();
await createCert({ name, description, rankKey: values.rankKey ?? undefined }).unwrap();
notify.success(t('certification.created'));
}
resetForm();