Add Monday.com board fields and expand column mapper
- Add followUp, notes, serviceTime, planningCenter, iSaidYesBookSent, ftGuestLetterSent fields to ResponseCard to match Monday.com board - Expand column mapper from 8 fields to all 32 ResponseCard fields with friendly labels matching the Easter Survey spreadsheet columns - Add Workflow & Tracking section on card detail page for the new fields - Fix pre-existing Buffer type error in images API route Made-with: Cursor
This commit is contained in:
parent
58f38d92ed
commit
d58285785e
5 changed files with 102 additions and 5 deletions
|
|
@ -42,6 +42,14 @@ model ResponseCard {
|
||||||
howHeardOther String?
|
howHeardOther String?
|
||||||
serviceAttended String?
|
serviceAttended String?
|
||||||
|
|
||||||
|
// Monday.com / Workflow fields
|
||||||
|
followUp String?
|
||||||
|
notes String?
|
||||||
|
serviceTime String?
|
||||||
|
planningCenter String?
|
||||||
|
iSaidYesBookSent Boolean @default(false)
|
||||||
|
ftGuestLetterSent Boolean @default(false)
|
||||||
|
|
||||||
// Meta
|
// Meta
|
||||||
sourceFile String?
|
sourceFile String?
|
||||||
frontImagePath String?
|
frontImagePath String?
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ export async function PUT(
|
||||||
"campusPreferenceOther",
|
"campusPreferenceOther",
|
||||||
"howHeardOther",
|
"howHeardOther",
|
||||||
"serviceAttended",
|
"serviceAttended",
|
||||||
|
"followUp",
|
||||||
|
"notes",
|
||||||
|
"serviceTime",
|
||||||
|
"planningCenter",
|
||||||
"ocrStatus",
|
"ocrStatus",
|
||||||
"reviewStatus",
|
"reviewStatus",
|
||||||
"ocrError",
|
"ocrError",
|
||||||
|
|
@ -87,6 +91,8 @@ export async function PUT(
|
||||||
|
|
||||||
if (body.prayerForTeam != null) data.prayerForTeam = Boolean(body.prayerForTeam);
|
if (body.prayerForTeam != null) data.prayerForTeam = Boolean(body.prayerForTeam);
|
||||||
if (body.prayerConfidential != null) data.prayerConfidential = Boolean(body.prayerConfidential);
|
if (body.prayerConfidential != null) data.prayerConfidential = Boolean(body.prayerConfidential);
|
||||||
|
if (body.iSaidYesBookSent != null) data.iSaidYesBookSent = Boolean(body.iSaidYesBookSent);
|
||||||
|
if (body.ftGuestLetterSent != null) data.ftGuestLetterSent = Boolean(body.ftGuestLetterSent);
|
||||||
if (body.ocrConfidence != null) data.ocrConfidence = Number(body.ocrConfidence);
|
if (body.ocrConfidence != null) data.ocrConfidence = Number(body.ocrConfidence);
|
||||||
if (body.messageTopics != null) data.messageTopics = body.messageTopics;
|
if (body.messageTopics != null) data.messageTopics = body.messageTopics;
|
||||||
if (body.nextStep != null) data.nextStep = body.nextStep;
|
if (body.nextStep != null) data.nextStep = body.nextStep;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function GET(
|
||||||
|
|
||||||
const buffer = await getBuffer(key);
|
const buffer = await getBuffer(key);
|
||||||
|
|
||||||
return new NextResponse(buffer, {
|
return new NextResponse(new Uint8Array(buffer), {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": contentType,
|
"Content-Type": contentType,
|
||||||
"Cache-Control": "private, max-age=3600",
|
"Cache-Control": "private, max-age=3600",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
ScanLine,
|
ScanLine,
|
||||||
User,
|
User,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
|
ClipboardCheck,
|
||||||
Code,
|
Code,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
|
@ -71,6 +72,12 @@ type CardData = {
|
||||||
howHeard: string[] | null;
|
howHeard: string[] | null;
|
||||||
howHeardOther: string | null;
|
howHeardOther: string | null;
|
||||||
serviceAttended: string | null;
|
serviceAttended: string | null;
|
||||||
|
followUp: string | null;
|
||||||
|
notes: string | null;
|
||||||
|
serviceTime: string | null;
|
||||||
|
planningCenter: string | null;
|
||||||
|
iSaidYesBookSent: boolean;
|
||||||
|
ftGuestLetterSent: boolean;
|
||||||
ocrStatus: string;
|
ocrStatus: string;
|
||||||
reviewStatus: string;
|
reviewStatus: string;
|
||||||
ocrConfidence: number | null;
|
ocrConfidence: number | null;
|
||||||
|
|
@ -434,6 +441,47 @@ export default function CardDetailPage() {
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card variant="glass" className="xl:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
|
<ClipboardCheck className="size-4 text-primary" />
|
||||||
|
Workflow & Tracking
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<Field label="Follow-Up" value={getValue("followUp")} onChange={(v) => setField("followUp", v)} />
|
||||||
|
<Field label="Service Time" value={getValue("serviceTime")} onChange={(v) => setField("serviceTime", v)} />
|
||||||
|
<Field label="Planning Center" value={getValue("planningCenter")} onChange={(v) => setField("planningCenter", v)} />
|
||||||
|
<div>
|
||||||
|
<Label className="mb-1.5 block text-xs font-medium text-muted-foreground">I Said Yes Book Sent</Label>
|
||||||
|
<Badge variant={card.iSaidYesBookSent ? "default" : "secondary"}>
|
||||||
|
{card.iSaidYesBookSent ? "Yes" : "No"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label className="mb-1.5 block text-xs font-medium text-muted-foreground">FT Guest Letter Sent</Label>
|
||||||
|
<Badge variant={card.ftGuestLetterSent ? "default" : "secondary"}>
|
||||||
|
{card.ftGuestLetterSent ? "Yes" : "No"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{getValue("notes") && (
|
||||||
|
<>
|
||||||
|
<Separator className="my-4 opacity-50" />
|
||||||
|
<div>
|
||||||
|
<Label className="mb-1.5 block text-xs font-medium text-muted-foreground">Notes</Label>
|
||||||
|
<Textarea
|
||||||
|
value={getValue("notes")}
|
||||||
|
onChange={(e) => setField("notes", e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{card.rawOcrResponse && (
|
{card.rawOcrResponse && (
|
||||||
<Card variant="glass" className="xl:col-span-2">
|
<Card variant="glass" className="xl:col-span-2">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,41 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
||||||
type MondayColumn = { id: string; title: string; type: string };
|
type MondayColumn = { id: string; title: string; type: string };
|
||||||
|
|
||||||
|
const MONDAY_MAPPABLE_FIELDS = [
|
||||||
|
{ field: "name", label: "Name" },
|
||||||
|
{ field: "gender", label: "Gender" },
|
||||||
|
{ field: "dateOfBirth", label: "Date of Birth" },
|
||||||
|
{ field: "maritalStatus", label: "Marital Status" },
|
||||||
|
{ field: "visitType", label: "Visit Type" },
|
||||||
|
{ field: "followUp", label: "Follow-Up" },
|
||||||
|
{ field: "cellPhone", label: "Cell Phone" },
|
||||||
|
{ field: "homePhone", label: "Home Phone" },
|
||||||
|
{ field: "email", label: "Email" },
|
||||||
|
{ field: "address", label: "Address" },
|
||||||
|
{ field: "aptNumber", label: "Apt #" },
|
||||||
|
{ field: "city", label: "City" },
|
||||||
|
{ field: "state", label: "State" },
|
||||||
|
{ field: "zip", label: "Zip" },
|
||||||
|
{ field: "prayerRequests", label: "Prayer Requests" },
|
||||||
|
{ field: "prayerForTeam", label: "For Prayer Team" },
|
||||||
|
{ field: "prayerConfidential", label: "Confidential" },
|
||||||
|
{ field: "messageTopics", label: "Message Topics" },
|
||||||
|
{ field: "messageTopicsOther", label: "Other - Topics" },
|
||||||
|
{ field: "nextStep", label: "Next Step" },
|
||||||
|
{ field: "attendanceDuration", label: "Attendance Duration" },
|
||||||
|
{ field: "campusPreference", label: "Campus Preference" },
|
||||||
|
{ field: "campusPreferenceOther", label: "Other Location" },
|
||||||
|
{ field: "howHeard", label: "How Did You Hear" },
|
||||||
|
{ field: "howHeardOther", label: "Other - How Heard" },
|
||||||
|
{ field: "serviceAttended", label: "A B C D" },
|
||||||
|
{ field: "serviceTime", label: "Service Time" },
|
||||||
|
{ field: "notes", label: "Notes" },
|
||||||
|
{ field: "planningCenter", label: "Planning Center" },
|
||||||
|
{ field: "iSaidYesBookSent", label: "I Said Yes Book Sent" },
|
||||||
|
{ field: "ftGuestLetterSent", label: "FT Guest Letter Sent" },
|
||||||
|
{ field: "reviewStatus", label: "Review Status" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
type SettingsData = {
|
type SettingsData = {
|
||||||
ollamaUrl: string;
|
ollamaUrl: string;
|
||||||
model: string;
|
model: string;
|
||||||
|
|
@ -878,10 +913,10 @@ export default function SettingsPage() {
|
||||||
{mondayColumns.length > 0 && (
|
{mondayColumns.length > 0 && (
|
||||||
<div className="rounded-xl border border-border/50 bg-muted/20 p-3 space-y-2">
|
<div className="rounded-xl border border-border/50 bg-muted/20 p-3 space-y-2">
|
||||||
<p className="text-xs font-medium text-muted-foreground mb-2">Column Mapping</p>
|
<p className="text-xs font-medium text-muted-foreground mb-2">Column Mapping</p>
|
||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div className="grid gap-2 sm:grid-cols-2 max-h-[28rem] overflow-y-auto pr-1">
|
||||||
{["name", "email", "cellPhone", "gender", "visitType", "city", "state", "reviewStatus"].map((field) => (
|
{MONDAY_MAPPABLE_FIELDS.map(({ field, label }) => (
|
||||||
<div key={field} className="flex items-center gap-2">
|
<div key={field} className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground w-24 shrink-0 capitalize">{field}</span>
|
<span className="text-xs text-muted-foreground w-36 shrink-0">{label}</span>
|
||||||
<Select
|
<Select
|
||||||
value={(settings.mondayColumnMap || {})[field] || "__none__"}
|
value={(settings.mondayColumnMap || {})[field] || "__none__"}
|
||||||
onValueChange={(v) => setColumnMapping(field, !v || v === "__none__" ? "" : v)}
|
onValueChange={(v) => setColumnMapping(field, !v || v === "__none__" ? "" : v)}
|
||||||
|
|
@ -899,7 +934,7 @@ export default function SettingsPage() {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground w-24 shrink-0">Files (images)</span>
|
<span className="text-xs text-muted-foreground w-36 shrink-0">Files (images)</span>
|
||||||
<Select
|
<Select
|
||||||
value={(settings.mondayColumnMap || {})._files || "__none__"}
|
value={(settings.mondayColumnMap || {})._files || "__none__"}
|
||||||
onValueChange={(v) => setColumnMapping("_files", !v || v === "__none__" ? "" : v)}
|
onValueChange={(v) => setColumnMapping("_files", !v || v === "__none__" ? "" : v)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue