Submitting from Basic Info complained that required fields on the Settings
tab were empty — a correct error the user could not act on, since Settings
had not been shown yet.
The primary action now reads "Next" while Basic Info is open and validates
only that tab before moving on; Create/Update appears on Settings, the final
step, alongside a Back button. Submit still checks both tabs, since the tab
headers stay clickable and a user can reach Settings without going through
Next.
The detail page fired assign/draw requests blind and surfaced the raw
backend key to the user — a locked paper showed up as the literal string
"paper_locked_after_registration" in a toast.
Manage Questions is now disabled once any candidate has registered, with a
tooltip and badge explaining why; both the manual assign and the random draw
live behind that modal, so one guard covers the same ground the backend's
assertPaperEditable does. The empty-paper alert now explains the deadlock
instead of telling the user to click a button that will fail, and both
error handlers translate paper_locked_after_registration in case someone
registers while the modal is open.
Registrations come from the query ExamCandidatesPanel already runs on this
page, so RTK Query serves it from cache rather than issuing a second request.
Frontend half of the backend change: ExamPage no longer disables the
form Select or force-resets it to CHOICE when administrationMethod is
ONLINE (backend's assertOnlineIsChoiceOnly is gone, matching gate
removed here). Also wires up GET .../grading-sheet as
useGetGradingSheetQuery — RecordResultModal wiring (show candidate
answers, prefill auto-computed CHOICE scores) is next.
eligibleQuestions filtered by q.form === exam.form, which matched the
backend's own restriction (assertUsable) only for ONLINE exams. Once
exam.form gained the "BOTH" value for OFFLINE mixed papers, no question
ever has form "BOTH", so the picker silently emptied out or, for a
plain CHOICE-form exam, looked CHOICE-only regardless of administration
method. Now mirrors the backend: CHOICE-only gate applies only when
administrationMethod is ONLINE.
body had its own 40px padding + max-width:800px for the on-screen preview
tab, which stacked with @page's own margin once actual printing kicked in
— the preview tab never shows @page at all, so it looked like one
continuous page while the real printed output wasted ~150px+ of page
height per side and was capped to 800px wide regardless of the physical
page width. In @media print specifically: padding:0, max-width:none —
@page's margin is the only inset that should apply once printing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Footer was just the last thing in document flow, so on a short paper it
landed mid-page instead of at the bottom. position:fixed in @media print
(the standard trick for a repeating page footer) plus a matching @page
bottom margin so it never overlaps the last question's text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the new backend rule. Field caps at 100 and shows a hint the
moment Percentage is selected, instead of letting you type 150 and only
finding out later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two follow-ups on the dropdown-actions pass:
- All four action menus (exams, results, questions, candidates panel) now
trigger off a plain three-dot icon button instead of a text 'Actions'
button with a chevron.
- Exam status change no longer lists all six statuses flat in the dropdown
(bad UX, looked like six always-visible options). 'Status' is now one
menu item that opens a small picker modal (current status + a Select +
Update) rather than the full edit form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same treatment as the questions table, applied consistently: exams list,
exam results list, and the exam-detail candidates panel all now show a
single Actions button opening a dropdown instead of a row of inline
buttons/icons. Also fixes the exams list's action column header, which was
rendering the literal untranslated key ('exam.columns.actions') — missing
a fallback string, same pattern already used elsewhere in this file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the new backend rule (online_exam_requires_choice_form) — picking
Online in the exam form now auto-sets Form to Choice and disables that
field with an explanatory hint, instead of letting the user pick Essay
and only finding out at save time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two UX gaps found in testing:
- Exams list: changing status (e.g. PENDING -> ACTIVE) required opening the
full Edit modal for a one-field change. Added a status menu (teal toggle
icon) directly in the row actions, calling PUT /exams/:id with just
{status} — the backend DTO was already PartialType, nothing to change
there.
- Exam Results: publishing required first picking the exam in a page-level
filter to un-grey the header Publish button — not discoverable, and an
extra step disconnected from the row you actually care about. Added a
Publish action directly on any APPROVED result's row, confirm-gated since
it publishes every approved result for that exam (not just the one row)
and notifies every candidate. The header filter+button still works too,
for publishing a batch at once.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two fixes surfaced by actual candidate-flow testing:
- Portal 'My registrations' always showed 'Take exam' regardless of whether
the candidate had already finished — clicking it just hit
attempt_already_submitted server-side. The registrations/mine response
now carries the attempt's status; the row shows a Completed/Time expired
badge once finished, or 'Resume exam' while still in progress, and only
invites a fresh start when there's genuinely nothing yet.
- Backoffice candidates panel: a Regrade button next to a SUBMITTED/EXPIRED
sitting, calling the new POST /exam-attempts/:id/regrade — the officer's
path to the same staff-triggered regrade, no SQL needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Print showed blank A/B/C/D fill-in lines for every CHOICE question instead
of the real options — the detail page never fetched option data at all
(GET /exams/:id?i=questions, no nested options), and the print template
hardcoded the blanks regardless. Now requests
?i=questions,questions.options and prints the real option text, falling
back to blank lines only when a question genuinely has none on record.
Also fixed the print-time cutting-point guard: it compared raw point sum
to cutting point regardless of evaluationMethod, wrongly blocking valid
low-point-sum PERCENTAGE papers from printing. Now branches the same way
RecordResultModal's grading preview already does, and surfaces the
matching backend validation error (paper_cannot_reach_cutting_point) added
server-side for the same reason.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three bugs compounded into a button that looked unwired:
- cuttingPoint carried a native HTML required attribute while sitting on
the hidden Settings tab (Mantine keeps inactive tab panels mounted with
display:none). The browser blocked form submission entirely, silently
- no toast, no error, nothing. Replaced required with withAsterisk and
moved validation into JS.
- Manual required-field validation never checked type/form/adminMethod/
evalMethod/cuttingPoint at all, so even without the native block above
a half-filled Settings tab would silently no-op. Validation now covers
every required field on both tabs and switches to whichever tab has
the problem.
- Filling only the English Direction field (leaving Amharic blank) passed
frontend validation but the backend hard-rejects it, producing a 400
with a confusing error. Frontend now requires both-or-neither, matching
the same rule already applied to MCQ options.
Also translates the previously hardcoded validation message in both
locales.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Added RequirePermission component to manage access based on user permissions.
- Integrated permission checks in MySeaRecordsPage, SeafarerRegistrationPage, VesselRegistrationPage, WaiverPage, and PortalLayout.
- Updated router to enforce permissions for specific routes.
- Introduced PORTAL_PERMISSIONS and LICENSE_PERMISSIONS constants for consistent permission management.
- Enhanced usePermissions hook to fetch permissions from the server and determine access rights.
- Refactored UI components to conditionally render based on user permissions, improving security and user experience.
Resolved by unifying on the lib/data AdvancedTable (PR #10) as the canonical
table component: kept its API plus teammate i18n/feature work, kept the
folder-per-table structure (index.tsx + columns.tsx + actions.tsx) across all
27 tables, removed the parallel lib/table implementation, and fixed
pre-existing compile breaks in ExamDetailPage/QuestionAssigner/RecordResultModal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(waiver): implement waiver page with application tracking and letter download feature
feat(ui): introduce AdvancedTable component for enhanced table functionality across the application
chore: update package-lock.json to remove unnecessary dependencies
- Added `useDateDisplayer` hook and `dateDisplayer` function to format dates consistently based on the user's language.
- Updated multiple components and pages in both backoffice and portal applications to utilize the new date display functionality, ensuring proper formatting for dates in lists, tables, and detail views.
- Introduced Ethiopian date formatting for Amharic language support.
- Refactored date handling in components such as ExamAppealsPage, ResultPage, SeafarerRegistryPage, and others to improve localization and user experience.
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
- Added `useErrorHandler` hook to centralize error handling logic.
- Updated components in the backoffice and portal applications to utilize the new error handling mechanism, replacing direct notify calls with `handleError` for improved error messaging.
- Enhanced localization files to include generic error messages for better user feedback.
- Refactored error handling in forms and API interactions to ensure consistent user experience across the application.