mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-29 15:18:12 +00:00
@@ -198,7 +198,7 @@ function ConditionArmFields({
|
||||
fz="xs"
|
||||
px={6}
|
||||
py={2}
|
||||
bg="var(--mantine-color-gray-1)"
|
||||
bg="var(--mantine-color-default-hover)"
|
||||
style={{ borderRadius: 4, cursor: 'pointer' }}
|
||||
onClick={() => setInValues((value.in ?? []).filter((_, idx) => idx !== i).map(String))}
|
||||
title={t('certReq.condition.removeValue', 'Click to remove')}
|
||||
|
||||
@@ -127,7 +127,7 @@ export function DocumentRequirementsTab({ licenseType }: { licenseType: LicenseT
|
||||
) : (
|
||||
<Stack gap="xs">
|
||||
{rows.map((req) => (
|
||||
<Card key={req.id} withBorder radius="sm" p="xs" bg="var(--mantine-color-gray-0)">
|
||||
<Card key={req.id} withBorder radius="sm" p="xs" bg="var(--mantine-color-default-hover)">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<Group gap={6}>
|
||||
|
||||
@@ -209,7 +209,7 @@ export function FormSchemaTab({ licenseType }: { licenseType: LicenseType }) {
|
||||
<Card key={section.key} withBorder radius="md" p="md">
|
||||
<Group justify="space-between" align="flex-start" mb="sm">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconGripVertical size={16} color="var(--mantine-color-gray-5)" />
|
||||
<IconGripVertical size={16} color="var(--mantine-color-dimmed)" />
|
||||
<div>
|
||||
<Group gap="xs">
|
||||
<Text fw={700}>{localized(section.title) || section.key}</Text>
|
||||
@@ -237,7 +237,7 @@ export function FormSchemaTab({ licenseType }: { licenseType: LicenseType }) {
|
||||
|
||||
<Stack gap="xs">
|
||||
{section.fields.map((field, fIndex) => (
|
||||
<Card key={field.key} withBorder radius="sm" p="xs" bg="var(--mantine-color-gray-0)">
|
||||
<Card key={field.key} withBorder radius="sm" p="xs" bg="var(--mantine-color-default-hover)">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
|
||||
@@ -1214,18 +1214,6 @@ export function LicenseReviewPage() {
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel value="inspection">
|
||||
{status === "INSPECTION_FAILED" && (
|
||||
<Alert
|
||||
mb="md"
|
||||
color="red"
|
||||
icon={<IconAlertTriangle size={16} />}
|
||||
>
|
||||
{t(
|
||||
"review.inspectionFailedBlocked",
|
||||
"Approval is unavailable because the inspection failed. Schedule a re-inspection, request corrections, or reject the application.",
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
<Paper withBorder p="md">
|
||||
{inspections.length === 0 ? (
|
||||
<Text size="sm" c="dimmed">
|
||||
@@ -1281,6 +1269,18 @@ export function LicenseReviewPage() {
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
{/* Page-level, not inside the inspection tab: a license type
|
||||
configured without an inspection detail section must still show
|
||||
why approval is blocked if it ever lands here. */}
|
||||
{status === "INSPECTION_FAILED" && (
|
||||
<Alert mt="md" color="red" icon={<IconAlertTriangle size={16} />}>
|
||||
{t(
|
||||
"review.inspectionFailedBlocked",
|
||||
"Approval is unavailable because the inspection failed. Schedule a re-inspection, request corrections, or reject the application.",
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{status === "PAYMENT_PENDING" && (
|
||||
<Alert
|
||||
mt="md"
|
||||
|
||||
@@ -26,7 +26,7 @@ export function applicantName(r: Pick<SeafarerRegistration, 'firstName' | 'middl
|
||||
return [r.firstName, r.middleName, r.lastName].filter(Boolean).join(' ') || '—';
|
||||
}
|
||||
|
||||
/** Submitted seafarer registrations, oldest first — click a row to review it. */
|
||||
/** Submitted seafarer registrations, newest first — click a row to review it. */
|
||||
export function SeafarerRegistrationQueuePage() {
|
||||
const navigate = useNavigate();
|
||||
const showDate = useDateDisplayer();
|
||||
@@ -43,6 +43,8 @@ export function SeafarerRegistrationQueuePage() {
|
||||
const { data, isLoading, isFetching, refetch } = useListSeafarerRegistrationsQuery({
|
||||
status: status ?? undefined,
|
||||
search: debouncedSearch || undefined,
|
||||
sortBy: 'submittedAt',
|
||||
sortDir: 'DESC',
|
||||
take: pageSize,
|
||||
skip: page * pageSize,
|
||||
});
|
||||
|
||||
@@ -352,14 +352,18 @@ export function LicenseApplicationPage() {
|
||||
),
|
||||
[roundRemarks],
|
||||
);
|
||||
const hasSectionRemarks = Object.keys(flaggedSections).length > 0;
|
||||
const hasDocRemarks = Object.keys(flaggedDocuments).length > 0;
|
||||
const hasStaffRemarks = roundRemarks.some((r) => r.targetType === "STAFF");
|
||||
// Nothing at all came back for this round (a detail response that predates
|
||||
// the remarks, say) — lock nothing rather than freeze the whole application
|
||||
// with no way forward. Any remark present means the round is itemised, so
|
||||
// only what the officer flagged opens: a documents-only round leaves every
|
||||
// form section frozen, and a sections-only round leaves every document as
|
||||
// filed.
|
||||
const roundIsItemised = isAdjusting && roundRemarks.length > 0;
|
||||
|
||||
// A round that flagged no form sections carries no section locks — mirror of
|
||||
// the server's fallback, without which a documents-only correction round
|
||||
// froze every field and the applicant could not edit anything at all.
|
||||
const isSectionLocked = (sectionKey: string) =>
|
||||
isAdjusting && hasSectionRemarks && !flaggedSections[sectionKey];
|
||||
roundIsItemised && !flaggedSections[sectionKey];
|
||||
const staffLocked = roundIsItemised && !hasStaffRemarks;
|
||||
|
||||
// Sections that share a group collapse onto one step, so the stepper stays
|
||||
// short instead of showing a page per section.
|
||||
@@ -862,7 +866,7 @@ export function LicenseApplicationPage() {
|
||||
complete
|
||||
</Badge>
|
||||
)}
|
||||
{!readOnly && (
|
||||
{!readOnly && !staffLocked && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
@@ -898,7 +902,7 @@ export function LicenseApplicationPage() {
|
||||
: ""}
|
||||
</Text>
|
||||
</div>
|
||||
{!readOnly && (
|
||||
{!readOnly && !staffLocked && (
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="red"
|
||||
@@ -917,7 +921,7 @@ export function LicenseApplicationPage() {
|
||||
<StaffEvidence
|
||||
staffId={member.id}
|
||||
evidence={role.requiredEvidence}
|
||||
readOnly={readOnly}
|
||||
readOnly={readOnly || staffLocked}
|
||||
onUploaded={refetch}
|
||||
/>
|
||||
</Card>
|
||||
@@ -937,7 +941,7 @@ export function LicenseApplicationPage() {
|
||||
ownerType="APPLICATION"
|
||||
ownerId={appId}
|
||||
flagged={flaggedDocuments}
|
||||
restrictToFlagged={isAdjusting && hasDocRemarks}
|
||||
restrictToFlagged={roundIsItemised}
|
||||
readOnly={readOnly}
|
||||
onUploaded={() => {
|
||||
refetchAttachments();
|
||||
|
||||
@@ -10,9 +10,17 @@ const TAG = 'SeafarerRegistration' as const;
|
||||
const LIST = { type: TAG, id: 'LIST' } as const;
|
||||
const item = (id: string) => ({ type: TAG, id }) as const;
|
||||
|
||||
export type SeafarerRegistrationSortField =
|
||||
| 'submittedAt'
|
||||
| 'registrationNumber'
|
||||
| 'lastName'
|
||||
| 'status';
|
||||
|
||||
export interface SeafarerRegistrationListFilter {
|
||||
status?: SeafarerRegistrationStatus;
|
||||
search?: string;
|
||||
sortBy?: SeafarerRegistrationSortField;
|
||||
sortDir?: 'ASC' | 'DESC';
|
||||
take?: number;
|
||||
skip?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user