Merge pull request #36 from Tria-plc/estif-branch-1

comments from tade
This commit is contained in:
Nati Nigussie
2026-08-27 16:44:50 +03:00
committed by GitHub
7 changed files with 41 additions and 27 deletions

View File

@@ -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')}

View File

@@ -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}>

View File

@@ -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 }}>

View File

@@ -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"

View File

@@ -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,
});

View File

@@ -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();

View File

@@ -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;
}