Staff report: inline lightbox for image/PDF attachments
Adds a /api/staff/file proxy that re-streams Civi attachments with Content-Disposition: inline so a native <dialog> lightbox can preview images and PDFs in place. Office docs keep their plain download link and gain a "View in Google Docs" secondary link (uses the Civi-signed URL so Google can fetch without our staff key). Also threads mime through /api/staff/report (Attachment.get mime_type) so the dispatcher picks the right affordance without relying solely on filename inference.
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
computeDateRange,
|
||||
} from "./report/FieldHistory";
|
||||
import { LoadingState, EmptyState, ErrorState } from "./report/ReportStates";
|
||||
import { FileLink } from "./report/FileLink";
|
||||
|
||||
interface StaffReportViewProps {
|
||||
org: number;
|
||||
@@ -207,6 +208,8 @@ export function StaffReportView({
|
||||
section={section}
|
||||
options={data.options}
|
||||
civiBaseUrl={civiBaseUrl}
|
||||
org={org}
|
||||
authKey={authKey}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -271,10 +274,14 @@ function StaffSection({
|
||||
section,
|
||||
options,
|
||||
civiBaseUrl,
|
||||
org,
|
||||
authKey,
|
||||
}: {
|
||||
section: StaffReportSection;
|
||||
options: Record<number, SelectOption[]>;
|
||||
civiBaseUrl: string;
|
||||
org: number;
|
||||
authKey: string;
|
||||
}) {
|
||||
const filled = section.fields.filter((f) => f.history.length > 0);
|
||||
const empty = section.fields.filter((f) => f.history.length === 0);
|
||||
@@ -328,6 +335,8 @@ function StaffSection({
|
||||
field={f}
|
||||
options={options}
|
||||
civiBaseUrl={civiBaseUrl}
|
||||
org={org}
|
||||
authKey={authKey}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
@@ -374,10 +383,14 @@ function CompactFieldRow({
|
||||
field,
|
||||
options,
|
||||
civiBaseUrl,
|
||||
org,
|
||||
authKey,
|
||||
}: {
|
||||
field: StaffReportField;
|
||||
options: Record<number, SelectOption[]>;
|
||||
civiBaseUrl: string;
|
||||
org: number;
|
||||
authKey: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const latest = field.history[0];
|
||||
@@ -394,6 +407,8 @@ function CompactFieldRow({
|
||||
entry={latest}
|
||||
options={options}
|
||||
civiBaseUrl={civiBaseUrl}
|
||||
org={org}
|
||||
authKey={authKey}
|
||||
/>
|
||||
</span>
|
||||
{latest.date ? (
|
||||
@@ -431,6 +446,8 @@ function CompactFieldRow({
|
||||
entry={e}
|
||||
options={options}
|
||||
civiBaseUrl={civiBaseUrl}
|
||||
org={org}
|
||||
authKey={authKey}
|
||||
/>
|
||||
</span>
|
||||
</li>
|
||||
@@ -446,41 +463,33 @@ function FieldValue({
|
||||
entry,
|
||||
options,
|
||||
civiBaseUrl,
|
||||
org,
|
||||
authKey,
|
||||
}: {
|
||||
field: StaffReportField;
|
||||
entry: FieldHistoryEntry;
|
||||
options: Record<number, SelectOption[]>;
|
||||
civiBaseUrl: string;
|
||||
org: number;
|
||||
authKey: string;
|
||||
}) {
|
||||
if (field.descriptor.render === "file") {
|
||||
const v = entry.value as
|
||||
| { id?: number | string; file_name?: string; url?: string }
|
||||
| { id?: number | string; file_name?: string; url?: string; mime?: string }
|
||||
| null;
|
||||
if (!v || v.id === undefined) return <span>—</span>;
|
||||
const id = String(v.id);
|
||||
const name = v.file_name ?? `file-${id}`;
|
||||
// Prefer the Civi-signed URL (carries the fcs JWT) returned by
|
||||
// Attachment.get. If absent, fall back to the WebForm-mw Civi extension's
|
||||
// file-redirect route — it mints the fcs server-side and 302s to the
|
||||
// real /civicrm/file URL. (Hitting /civicrm/file?id=X bare crashes Civi
|
||||
// on a null fcs JWT decode.)
|
||||
let href = "#";
|
||||
if (v.url) {
|
||||
href = v.url.startsWith("http")
|
||||
? v.url
|
||||
: `${civiBaseUrl}${v.url.startsWith("/") ? "" : "/"}${v.url}`;
|
||||
} else if (civiBaseUrl) {
|
||||
href = `${civiBaseUrl}/civicrm/webform-mw/file?id=${encodeURIComponent(id)}`;
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-ink underline decoration-rule underline-offset-4 hover:decoration-ink"
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
<FileLink
|
||||
fileId={id}
|
||||
fileName={name}
|
||||
civiSignedUrl={v.url}
|
||||
mime={v.mime}
|
||||
org={org}
|
||||
authKey={authKey}
|
||||
civiBaseUrl={civiBaseUrl}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user