212 lines
7.3 KiB
TypeScript
212 lines
7.3 KiB
TypeScript
/**
|
|
* GET /api/staff/report?org=<id>&key=<secret>
|
|
*
|
|
* Returns a comprehensive read-only StaffReportPayload for the given
|
|
* organization. Field list is built at request time from
|
|
* CustomField.get (no static config).
|
|
*
|
|
* Auth: STAFF_REPORT_KEY must match the `key` query param.
|
|
*
|
|
* STUB MODE: if CiviCRM env vars are unset, returns a fabricated payload
|
|
* exercising every render kind so the staff page is usable in dev.
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { isStaffKeyValid } from "@/lib/staff-auth";
|
|
import type { StaffReportPayload } from "@/types/form";
|
|
|
|
function isCiviStubMode(): boolean {
|
|
return !(
|
|
process.env.CIVI_BASE_URL &&
|
|
process.env.CIVI_API_KEY &&
|
|
process.env.CIVI_SITE_KEY
|
|
);
|
|
}
|
|
|
|
function buildStubPayload(orgId: number): StaffReportPayload {
|
|
const today = new Date();
|
|
const daysAgo = (n: number) =>
|
|
new Date(today.getTime() - n * 24 * 3600 * 1000).toISOString();
|
|
const ymd = (n: number) => {
|
|
const d = new Date(today.getTime() - n * 24 * 3600 * 1000);
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
};
|
|
return {
|
|
orgId,
|
|
orgName: "Sample Co-op (stub)",
|
|
currentStage: "Organizing",
|
|
sections: [
|
|
{
|
|
groupName: "Food_Co_op_Organizing",
|
|
groupTitle: "Organization profile",
|
|
groupKind: "org",
|
|
fields: [
|
|
{
|
|
descriptor: {
|
|
groupName: "Food_Co_op_Organizing",
|
|
groupTitle: "Organization profile",
|
|
groupKind: "org",
|
|
civiField: "Food_Co_op_Organizing.Date_Incorporated",
|
|
name: "Date_Incorporated",
|
|
label: "Date Incorporated",
|
|
render: "date",
|
|
},
|
|
history: [{ activityId: 0, date: "", value: ymd(900) }],
|
|
},
|
|
{
|
|
descriptor: {
|
|
groupName: "Food_Co_op_Organizing",
|
|
groupTitle: "Organization profile",
|
|
groupKind: "org",
|
|
civiField: "Food_Co_op_Organizing.Equity_share",
|
|
name: "Equity_share",
|
|
label: "Equity share (USD)",
|
|
render: "currency",
|
|
},
|
|
history: [{ activityId: 0, date: "", value: 200 }],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
groupName: "Check_in_data__organizing_",
|
|
groupTitle: "Check-in data (organizing)",
|
|
groupKind: "activity",
|
|
fields: [
|
|
{
|
|
descriptor: {
|
|
groupName: "Check_in_data__organizing_",
|
|
groupTitle: "Check-in data (organizing)",
|
|
groupKind: "activity",
|
|
civiField: "Check_in_data__organizing_.Members__current_",
|
|
name: "Members__current_",
|
|
label: "Members (current)",
|
|
render: "number",
|
|
},
|
|
history: [
|
|
{ activityId: 9012, date: daysAgo(3), value: 124 },
|
|
{ activityId: 9008, date: daysAgo(34), value: 109 },
|
|
{ activityId: 8995, date: daysAgo(95), value: 87 },
|
|
],
|
|
},
|
|
{
|
|
descriptor: {
|
|
groupName: "Check_in_data__organizing_",
|
|
groupTitle: "Check-in data (organizing)",
|
|
groupKind: "activity",
|
|
civiField: "Check_in_data__organizing_.Member_Goal_for_current_Stage",
|
|
name: "Member_Goal_for_current_Stage",
|
|
label: "Member goal for current stage",
|
|
render: "number",
|
|
},
|
|
history: [{ activityId: 9008, date: daysAgo(34), value: 200 }],
|
|
},
|
|
{
|
|
descriptor: {
|
|
groupName: "Check_in_data__organizing_",
|
|
groupTitle: "Check-in data (organizing)",
|
|
groupKind: "activity",
|
|
civiField: "Check_in_data__organizing_.Peer_Group_Participation",
|
|
name: "Peer_Group_Participation",
|
|
label: "Peer group participation",
|
|
render: "select",
|
|
optionGroupId: 140,
|
|
},
|
|
history: [
|
|
{ activityId: 9012, date: daysAgo(3), value: "Yes" },
|
|
{ activityId: 9008, date: daysAgo(34), value: "Considering" },
|
|
],
|
|
},
|
|
{
|
|
descriptor: {
|
|
groupName: "Check_in_data__organizing_",
|
|
groupTitle: "Check-in data (organizing)",
|
|
groupKind: "activity",
|
|
civiField: "Check_in_data__organizing_.Internal_Note",
|
|
name: "Internal_Note",
|
|
label: "Internal note",
|
|
render: "longtext",
|
|
},
|
|
history: [
|
|
{
|
|
activityId: 9012,
|
|
date: daysAgo(3),
|
|
value:
|
|
"Strong member momentum this quarter. Need a working group lead before next check-in.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
groupName: "Stage_1",
|
|
groupTitle: "Stage 1",
|
|
groupKind: "activity",
|
|
fields: [
|
|
{
|
|
descriptor: {
|
|
groupName: "Stage_1",
|
|
groupTitle: "Stage 1",
|
|
groupKind: "activity",
|
|
civiField: "Stage_1.Vision_Upload",
|
|
name: "Vision_Upload",
|
|
label: "Vision — Upload",
|
|
render: "file",
|
|
},
|
|
history: [
|
|
{
|
|
activityId: 9012,
|
|
date: daysAgo(3),
|
|
value: { id: 4242, file_name: "co-op-vision.pdf" },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
activities: [
|
|
{ id: 9012, date: daysAgo(3), subject: "Co-op Survey (form submission)", submittedBy: "Jane Doe" },
|
|
{ id: 9008, date: daysAgo(34), subject: "Co-op Survey (form submission)", submittedBy: "Jane Doe" },
|
|
{ id: 9001, date: daysAgo(62), stage: "Organizing", subject: "Stage transition (staff)" },
|
|
{ id: 8995, date: daysAgo(95), subject: "Co-op Survey (form submission)", submittedBy: "John Roe" },
|
|
],
|
|
options: {
|
|
140: [
|
|
{ value: "Yes", label: "Yes" },
|
|
{ value: "No", label: "No" },
|
|
{ value: "Considering", label: "Considering" },
|
|
],
|
|
75: [
|
|
{ value: "Inquiry", label: "Inquiry" },
|
|
{ value: "Organizing", label: "Stage 1 — Convene & Prepare" },
|
|
{ value: "Feasibility", label: "Stage 2 — Grow & Plan" },
|
|
{ value: "Business feasibility", label: "Stage 3 — Connect & Gather" },
|
|
{ value: "Store Implementation", label: "Stage 4 — Excite & Build" },
|
|
{ value: "Stabilize newly opened co-op", label: "Stage 5 — Fulfill & Stabilize" },
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const url = new URL(req.url);
|
|
const key = url.searchParams.get("key");
|
|
const orgStr = url.searchParams.get("org");
|
|
|
|
if (!isStaffKeyValid(key)) {
|
|
// Don't leak whether the route exists.
|
|
return new NextResponse("Not found", { status: 404 });
|
|
}
|
|
|
|
const orgId = Number(orgStr);
|
|
if (!orgStr || !Number.isFinite(orgId) || orgId <= 0) {
|
|
return NextResponse.json({ error: "Missing or invalid org id." }, { status: 400 });
|
|
}
|
|
|
|
if (isCiviStubMode()) {
|
|
return NextResponse.json(buildStubPayload(orgId));
|
|
}
|
|
|
|
// Live Civi branch added in Task 8.
|
|
return NextResponse.json({ error: "Live mode not yet implemented." }, { status: 501 });
|
|
}
|