mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-05 04:53:39 +00:00
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import {
|
|
Badge,
|
|
Divider,
|
|
Group,
|
|
Grid,
|
|
Paper,
|
|
Stack,
|
|
Text,
|
|
Title,
|
|
} from "@mantine/core";
|
|
import {
|
|
conditionHolds,
|
|
displayFieldValue,
|
|
type Attachment,
|
|
type FormFieldConfig,
|
|
type FormSectionConfig,
|
|
type LicenseTypeRequirements,
|
|
} from "@ema-platform/api";
|
|
import { useDateDisplayer } from "@ema-platform/shared";
|
|
import { useTranslation } from "react-i18next";
|
|
import { DocumentSlots } from "./DocumentSlots";
|
|
|
|
interface Props {
|
|
/** Every form section (not just the "review" group) in wizard-step order. */
|
|
sections: FormSectionConfig[];
|
|
formData: Record<string, Record<string, unknown>>;
|
|
localized: (value: { en?: string; am?: string } | undefined) => string;
|
|
config: LicenseTypeRequirements;
|
|
attachments: Attachment[];
|
|
applicationId: string;
|
|
}
|
|
|
|
/**
|
|
* Read-only "what was filed" view for a submitted application: every
|
|
* answered section as a labelled table, then the uploaded documents.
|
|
*
|
|
* Shown instead of the wizard once there is nothing left to step through —
|
|
* the stepper is for filling a form in, not for re-reading one that is
|
|
* already someone else's decision to make.
|
|
*/
|
|
export function ApplicationSummary({
|
|
sections,
|
|
formData,
|
|
localized,
|
|
config,
|
|
attachments,
|
|
applicationId,
|
|
}: Props) {
|
|
const showDate = useDateDisplayer();
|
|
const { i18n } = useTranslation();
|
|
|
|
// Shared with the officer's review screen, so the applicant and the reviewer
|
|
// never read the same answer two different ways.
|
|
const display = (field: FormFieldConfig, raw: unknown) =>
|
|
displayFieldValue(field, raw, {
|
|
language: i18n.language,
|
|
showDate,
|
|
currency: config.feeCurrency,
|
|
}) || "—";
|
|
|
|
return (
|
|
<Stack gap="md">
|
|
{sections.map((section) => {
|
|
const fields = (section.fields ?? []).filter((f) =>
|
|
conditionHolds(f.showWhen, formData),
|
|
);
|
|
if (fields.length === 0) return null;
|
|
|
|
return (
|
|
<Paper withBorder p="lg" radius="md" key={section.key}>
|
|
<Group justify="space-between" align="center" mb="xs">
|
|
<Title order={5}>{localized(section.title)}</Title>
|
|
<Badge variant="light" color="gray" size="sm">
|
|
{fields.length} {fields.length === 1 ? "detail" : "details"}
|
|
</Badge>
|
|
</Group>
|
|
{localized(section.description) && (
|
|
<Text fz="xs" c="dimmed" mb="sm">
|
|
{localized(section.description)}
|
|
</Text>
|
|
)}
|
|
<Divider mb="md" />
|
|
|
|
{/* Label above value in two columns — a definition list reads far
|
|
better than a bordered grid when most answers are short. */}
|
|
<Grid gutter="md">
|
|
{fields.map((field) => {
|
|
const value = display(
|
|
field,
|
|
formData[section.key]?.[field.key],
|
|
);
|
|
const answered = value !== "—";
|
|
return (
|
|
<Grid.Col
|
|
span={{
|
|
base: 12,
|
|
sm: field.type === "TEXTAREA" ? 12 : 6,
|
|
}}
|
|
key={field.key}
|
|
>
|
|
<Text fz="xs" c="dimmed" tt="uppercase" fw={600}>
|
|
{localized(field.label)}
|
|
</Text>
|
|
<Text
|
|
fz="sm"
|
|
mt={2}
|
|
c={answered ? undefined : "dimmed"}
|
|
fs={answered ? undefined : "italic"}
|
|
style={{ wordBreak: "break-word" }}
|
|
>
|
|
{answered ? value : "Not provided"}
|
|
</Text>
|
|
</Grid.Col>
|
|
);
|
|
})}
|
|
</Grid>
|
|
</Paper>
|
|
);
|
|
})}
|
|
|
|
<Paper withBorder p="lg" radius="md">
|
|
<Title order={5} mb="sm">
|
|
Documents
|
|
</Title>
|
|
<Divider mb="md" />
|
|
<DocumentSlots
|
|
requirements={config.documentRequirements}
|
|
attachments={attachments}
|
|
formData={formData}
|
|
ownerType="APPLICATION"
|
|
ownerId={applicationId}
|
|
readOnly
|
|
onUploaded={() => {
|
|
// Read-only here — nothing to react to, but DocumentSlots
|
|
// requires the callback.
|
|
}}
|
|
/>
|
|
</Paper>
|
|
</Stack>
|
|
);
|
|
}
|