mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-08 22:58:18 +00:00
feat: integrate PdfPreviewModal across apps
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
Paper,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
@@ -36,6 +35,7 @@ import {
|
||||
useGetMySeaServiceRecordsQuery,
|
||||
useGetMyMedicalCertificatesQuery,
|
||||
} from '@ema-platform/api';
|
||||
import { PdfPreviewModal } from '@ema-platform/ui';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mock data
|
||||
@@ -353,21 +353,12 @@ export function CertificatesPage() {
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
{/* Preview modal */}
|
||||
<Modal
|
||||
<PdfPreviewModal
|
||||
opened={!!previewUrl}
|
||||
onClose={() => setPreviewUrl(null)}
|
||||
title={<Text fw={700} fz="sm">{previewTitle}</Text>}
|
||||
size="95vw"
|
||||
radius="lg"
|
||||
fullScreen
|
||||
>
|
||||
<iframe
|
||||
src={previewUrl ?? ''}
|
||||
style={{ width: '100%', height: '90vh', border: 'none', borderRadius: 8 }}
|
||||
title={previewTitle}
|
||||
/>
|
||||
</Modal>
|
||||
url={previewUrl ?? ''}
|
||||
title={previewTitle}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
type DocumentRequirement,
|
||||
} from '@ema-platform/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PdfPreviewModal } from '@ema-platform/ui';
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
||||
|
||||
@@ -61,6 +62,9 @@ export function DocumentSlots({
|
||||
const { t } = useTranslation();
|
||||
const [busy, setBusy] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [preview, setPreview] = useState<{ url: string; title: string } | null>(
|
||||
null,
|
||||
);
|
||||
const resetRefs = useRef<Record<string, () => void>>({});
|
||||
|
||||
const required = requirements.filter(
|
||||
@@ -100,6 +104,7 @@ export function DocumentSlots({
|
||||
{required.map((requirement) => {
|
||||
const existing = attachments.find((a) => a.documentKey === requirement.key);
|
||||
const uploaded = Boolean(existing?.files?.length);
|
||||
const fileUrl = existing?.files?.[0]?.url;
|
||||
const flagRemark = flagged[requirement.key];
|
||||
const locked = readOnly || (restrictToFlagged && !flagRemark);
|
||||
|
||||
@@ -153,13 +158,16 @@ export function DocumentSlots({
|
||||
</div>
|
||||
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{existing?.files?.[0]?.url && (
|
||||
{fileUrl && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
component="a"
|
||||
href={existing.files[0].url}
|
||||
target="_blank"
|
||||
onClick={() =>
|
||||
setPreview({
|
||||
url: fileUrl,
|
||||
title: localized(requirement.name),
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('licensing.documents.view')}
|
||||
</Button>
|
||||
@@ -198,6 +206,12 @@ export function DocumentSlots({
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
<PdfPreviewModal
|
||||
opened={Boolean(preview)}
|
||||
onClose={() => setPreview(null)}
|
||||
url={preview?.url ?? ''}
|
||||
title={preview?.title}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, FileButton, Group, Loader } from '@mantine/core';
|
||||
import { ActionIcon, Button, FileButton, Group, Loader } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { IconCheck, IconEye } from '@tabler/icons-react';
|
||||
import {
|
||||
useLocalized,
|
||||
uploadDocument,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type StaffEvidenceRequirement,
|
||||
} from '@ema-platform/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PdfPreviewModal } from '@ema-platform/ui';
|
||||
|
||||
const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
||||
|
||||
@@ -31,6 +32,9 @@ export function StaffEvidence({ staffId, evidence, readOnly, onUploaded }: Props
|
||||
ownerId: staffId,
|
||||
});
|
||||
const [busy, setBusy] = useState<string | null>(null);
|
||||
const [preview, setPreview] = useState<{ url: string; title: string } | null>(
|
||||
null,
|
||||
);
|
||||
const localized = useLocalized();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -39,12 +43,14 @@ export function StaffEvidence({ staffId, evidence, readOnly, onUploaded }: Props
|
||||
return (
|
||||
<Group gap="xs">
|
||||
{evidence.map((item) => {
|
||||
const uploaded = attachments.some(
|
||||
const attachment = attachments.find(
|
||||
(a) => a.documentKey === item.docKey && a.files?.length,
|
||||
);
|
||||
const uploaded = Boolean(attachment);
|
||||
const fileUrl = attachment?.files?.[0]?.url;
|
||||
return (
|
||||
<Group key={item.docKey} gap={4} wrap="nowrap">
|
||||
<FileButton
|
||||
key={item.docKey}
|
||||
accept="application/pdf,image/jpeg,image/png"
|
||||
onChange={async (file) => {
|
||||
if (!file) return;
|
||||
@@ -89,8 +95,27 @@ export function StaffEvidence({ staffId, evidence, readOnly, onUploaded }: Props
|
||||
</Button>
|
||||
)}
|
||||
</FileButton>
|
||||
{uploaded && fileUrl && (
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
aria-label={t('licensing.documents.view', 'View')}
|
||||
onClick={() =>
|
||||
setPreview({ url: fileUrl, title: localized(item.label) })
|
||||
}
|
||||
>
|
||||
<IconEye size={14} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
})}
|
||||
<PdfPreviewModal
|
||||
opened={Boolean(preview)}
|
||||
onClose={() => setPreview(null)}
|
||||
url={preview?.url ?? ''}
|
||||
title={preview?.title}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,13 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AdvancedTable, AmharicDatePicker, notify, useServerTable } from '@ema-platform/ui';
|
||||
import {
|
||||
AdvancedTable,
|
||||
AmharicDatePicker,
|
||||
notify,
|
||||
PdfPreviewModal,
|
||||
useServerTable,
|
||||
} from '@ema-platform/ui';
|
||||
import { useDateDisplayer } from '@ema-platform/shared';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
@@ -74,6 +80,9 @@ function EvidenceModal({
|
||||
{ ownerType, ownerId: ownerId ?? '' },
|
||||
{ skip: !ownerId },
|
||||
);
|
||||
const [preview, setPreview] = useState<{ url: string; title: string } | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const files = (attachments ?? []).flatMap((a) => a.files);
|
||||
|
||||
@@ -91,7 +100,14 @@ function EvidenceModal({
|
||||
<Group key={file.id} gap="xs">
|
||||
<IconPaperclip size={16} />
|
||||
{file.url ? (
|
||||
<Anchor href={file.url} target="_blank" size="sm">
|
||||
<Anchor
|
||||
component="button"
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setPreview({ url: file.url as string, title: file.originalName })
|
||||
}
|
||||
>
|
||||
{file.originalName}
|
||||
</Anchor>
|
||||
) : (
|
||||
@@ -101,6 +117,12 @@ function EvidenceModal({
|
||||
))
|
||||
)}
|
||||
</Stack>
|
||||
<PdfPreviewModal
|
||||
opened={Boolean(preview)}
|
||||
onClose={() => setPreview(null)}
|
||||
url={preview?.url ?? ''}
|
||||
title={preview?.title}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user