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>
Options editor used a single toggle-based bilingual field (BilingualInput)
that swapped English/Amharic in place via a tiny, easy-to-miss button.
Authors were filling English only and never noticing Amharic was empty.
- QuestionOptionsEditor: show separate always-visible English/Amharic
TextInputs per option instead of the toggle field.
- BilingualInput (shared): for other callers still using the toggle,
add a tooltip and a red dot indicator when the hidden language is
empty, so the gap is visible without switching.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The approved single-page register -- with the detail modal that shows a
seafarer's full standing -- had been replaced by a three-file grid that
carried no profile view. That is why the seafarer profile appeared
missing from the backoffice entirely.
Restores his page and points it at /seafarer-registry. The list is
served whole and filtered in the browser: the filters are instant facets
over a page-sized list, and a round trip per keystroke would make the
search feel slower than the data it searches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seafarer and vessel registration were filtered out of the operations
step by `requiresOperatorMode !== false`, so someone registering as a
seafarer or vessel owner landed on an onboarding screen that did not
describe them. Both now appear, grouped apart from the company modes:
declaring "I am a seafarer" is a different kind of statement from "my
company forwards freight".
The designer's preview was gated on the Handlebars source alone, which a
canvas layout does not have until the server compiles it on save -- so
the button was dead for exactly the designs the canvas exists for.
Editing looked broken rather than deliberately read-only: every seeded
template is PUBLISHED, and a published design is immutable because
certificates were issued from it. Says so, and offers the new-version
action that is the way forward.
Reviewing officers saw company, capital and staff tabs on seafarer
certificate applications, because PRESENTATION is keyed by the generic
licence keys and the fifty-odd rank-specific CoC/CoP keys fell through
to the company default. Matched by prefix instead, so a certificate
configured tomorrow gets the right presentation without a code change.
Seaman book and BTC are wired to the newly seeded licence types and no
longer marked "soon".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Implemented ExamStageActions component to manage actions related to exam booking and payment based on application status.
- Added mock-base-query for development, providing a partial mock backend for various API endpoints.
- Introduced mock-data for simulating responses in the mock-base-query, covering profiles, vessels, applications, licenses, exams, and notifications.
- Introduced `landing-copy.ts` containing text for the public landing page, supporting both English and Amharic languages.
- Added `landing.css` for styling the landing page, ensuring scoped styles to prevent conflicts with other app styles.
- Implemented smooth scrolling and animations for a better user experience.
Backend now supports MCQ options + a separate answer-key table
(PUT /questions/:id/options). Frontend side of that, authoring only —
no candidate exam-taking UI.
- Question type gains options?: QuestionOption[] (no correctness field —
the API never returns one, matching the backend's split-table design)
- New question-api hooks: getQuestionWithOptions, setQuestionOptions
- New QuestionOptionsEditor component (separate file, not inlined into
QuestionPage) — add/remove/reorder options, mark correct, bilingual text
- Wired into the question edit form, shown only for an existing CHOICE
question (options attach to an id, matching the backend's replace
endpoint)
- en/am i18n strings for the new editor
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Record Result (POST /results) and the detail-modal Save (PUT /results/:id)
had no RequirePermission gate, unlike every other action on this page —
both routes are backend-guarded (RECORD_EXAM_RESULT, or
RECORD_EXAM_RESULT|MODERATE_EXAM_RESULT for the update), the buttons just
never mirrored it client-side.
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 useLocalized hook to provide a stable function for retrieving bilingual values based on the current language.
- Updated various components across the portal and backoffice to utilize the new useLocalized hook for consistent bilingual label rendering.
- Refactored localized function in licensing.helpers to handle empty Amharic strings correctly.
- Enhanced localization handling in LicenseApplicationPage, ProfilePage, and other components to ensure proper language switching.
- Integrated i18n for seafarer registry page, including translations for various UI elements and statuses.
- Updated SeafarerDetailDrawer and StatusModal components to utilize translation hooks.
- Enhanced user feedback messages for status updates and errors.
- Added new translations for Amharic and English locales, covering seafarer statuses, drawer labels, modal content, and table headers.
localization for backoffice
- 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.
fix(layouts): import baseApi in BackofficeLayout and PortalLayout for API state management
refactor(auth): remove clearCurrentProfile call on logout and improve cookie removal logic
chore(auth-storage): enhance cookie management with secure and sameSite attributes, and streamline cleanup process