Prefill file fields with their file_name joined from CiviCRM
CiviCRM APIv4 file custom fields return a bare file id by default; an
extra '.file_name' join is required to get the human-readable filename.
Both the form prefill walk (lib/prefill.ts) and the report walk
(app/api/report/route.ts) now request '<civiField>.file_name' for every
file-type field alongside the primary value, and wrap the prefill into
a { id, file_name } object so downstream UI has both. Falls back to
file_name undefined when the join returns null (eg orphaned id).
The form's FilePriorIndicator already accepts the object shape, so it
now shows the filename inline. The report's FormattedValue gets a
matching case: renders the file_name string if present, falls back to
'Attachment #<id>' when only the id came through.
This commit is contained in:
+17
-1
@@ -158,15 +158,26 @@ export async function GET(req: NextRequest) {
|
||||
const orgId = orgs[0].contact_id_b;
|
||||
|
||||
// Org name + activity walk + option groups, in parallel.
|
||||
// For file-type fields, also request the joined `.file_name` so the
|
||||
// report can surface a human-readable filename rather than the raw
|
||||
// file id that APIv4 returns by default.
|
||||
const civiFieldNames = Array.from(
|
||||
new Set(allFields.map((f) => f.civiField).filter((f): f is string => !!f)),
|
||||
);
|
||||
const fileFieldRefs = Array.from(
|
||||
new Set(
|
||||
allFields
|
||||
.filter((f) => f.type === "file" && f.civiField)
|
||||
.map((f) => `${f.civiField!}.file_name`),
|
||||
),
|
||||
);
|
||||
const select = [
|
||||
"id",
|
||||
"activity_date_time",
|
||||
"subject",
|
||||
ACTIVITY_STAGE_FIELD,
|
||||
...civiFieldNames,
|
||||
...fileFieldRefs,
|
||||
];
|
||||
|
||||
const [orgRes, activityRes, options] = await Promise.all([
|
||||
@@ -227,10 +238,15 @@ export async function GET(req: NextRequest) {
|
||||
for (const row of rows) {
|
||||
const v = row[f.civiField];
|
||||
if (v === null || v === undefined || v === "") continue;
|
||||
let value: unknown = v;
|
||||
if (f.type === "file") {
|
||||
const fname = row[`${f.civiField}.file_name`];
|
||||
value = { id: v, file_name: typeof fname === "string" ? fname : undefined };
|
||||
}
|
||||
entries.push({
|
||||
activityId: row.id,
|
||||
date: row.activity_date_time,
|
||||
value: v,
|
||||
value,
|
||||
});
|
||||
}
|
||||
if (entries.length > 0) fieldHistory[f.name] = entries;
|
||||
|
||||
@@ -459,8 +459,18 @@ function FormattedValue({
|
||||
const labels = parts.map((p) => opts?.find((o) => o.value === p)?.label ?? p);
|
||||
return <>{labels.join(", ")}</>;
|
||||
}
|
||||
case "file":
|
||||
case "file": {
|
||||
// Prefill / history wraps file values into { id, file_name } so the
|
||||
// UI can show a human-readable name. Fall back to whatever scalar
|
||||
// came through if the shape is different.
|
||||
if (typeof value === "object" && value !== null) {
|
||||
const o = value as Record<string, unknown>;
|
||||
const fname = o.file_name ?? o.name ?? o.label ?? o.filename;
|
||||
if (typeof fname === "string" && fname.length > 0) return <>{fname}</>;
|
||||
if (typeof o.id !== "undefined") return <>Attachment #{String(o.id)}</>;
|
||||
}
|
||||
return <>{String(value)}</>;
|
||||
}
|
||||
case "textarea":
|
||||
case "text":
|
||||
case "email":
|
||||
|
||||
+22
-3
@@ -39,7 +39,19 @@ export async function loadPrefill(
|
||||
activityTypeName = "Org Engagement Submission",
|
||||
): Promise<PrefillResult> {
|
||||
const civiSelected = fields.filter((f) => f.civiField);
|
||||
const select = ["id", "activity_date_time", ...new Set(civiSelected.map((f) => f.civiField!))];
|
||||
|
||||
// CiviCRM File custom fields return a file id by default. To surface a
|
||||
// human-readable filename in the prefill (so the prior-attachment
|
||||
// indicator can show it), also request the joined `.file_name` for any
|
||||
// file-type field.
|
||||
const fileFieldRefs = new Set(
|
||||
civiSelected.filter((f) => f.type === "file").map((f) => f.civiField!),
|
||||
);
|
||||
|
||||
const selectSet = new Set<string>(["id", "activity_date_time"]);
|
||||
for (const f of civiSelected) selectSet.add(f.civiField!);
|
||||
for (const ref of fileFieldRefs) selectSet.add(`${ref}.file_name`);
|
||||
const select = Array.from(selectSet);
|
||||
|
||||
const res = await civi<ActivityRow>("Activity", "get", {
|
||||
select,
|
||||
@@ -56,10 +68,17 @@ export async function loadPrefill(
|
||||
for (const f of civiSelected) {
|
||||
for (const row of rows) {
|
||||
const v = row[f.civiField!];
|
||||
if (v !== null && v !== undefined && v !== "") {
|
||||
if (v === null || v === undefined || v === "") continue;
|
||||
if (f.type === "file") {
|
||||
const fname = row[`${f.civiField!}.file_name`];
|
||||
out[f.name] = {
|
||||
id: v,
|
||||
file_name: typeof fname === "string" ? fname : undefined,
|
||||
};
|
||||
} else {
|
||||
out[f.name] = v;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user