From 3bf7c84a9e53ab50ce5f1b70e90fe79733ad7e80 Mon Sep 17 00:00:00 2001 From: Ravi Gulhane <> Date: Tue, 15 Sep 2026 13:52:20 +0530 Subject: [PATCH] New Changes --- src/components/DynamicFeedbackForm.tsx | 110 +++++++++++++++++++++- src/lib/api.ts | 2 + src/lib/rankFieldUtils.ts | 123 +++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 src/lib/rankFieldUtils.ts diff --git a/src/components/DynamicFeedbackForm.tsx b/src/components/DynamicFeedbackForm.tsx index bd4caff..4248f57 100644 --- a/src/components/DynamicFeedbackForm.tsx +++ b/src/components/DynamicFeedbackForm.tsx @@ -3,6 +3,15 @@ import { Star } from "lucide-react"; import type { VisitorPublicFeedbackForm, VisitorPublicFeedbackFormField } from "../lib/api"; import { postPublicFormSubmit } from "../lib/api"; import { getAttendeeParticipant } from "../lib/attendeeAuth"; +import { + OTHER_RANK_KEY, + parseRankValue, + rankSlotCount, + rankValueToOrderedLabels, + setRankForKey, + validateRankValue, + type RankFieldValue, +} from "../lib/rankFieldUtils"; function isStarRatingField(f: VisitorPublicFeedbackFormField): boolean { const t = (f.type || "").toLowerCase().trim(); @@ -44,9 +53,23 @@ export function DynamicFeedbackForm({ const validate = (): boolean => { for (const f of fields) { const key = String(f.label).trim(); - if (!f.required) continue; const raw = values[key]; - if ((f.type || "").toLowerCase() === "checkboxes") { + const t = (f.type || "").toLowerCase(); + if (t === "rank") { + const msg = validateRankValue( + parseRankValue(raw), + (f.options || []).map((o) => String(o).trim()).filter(Boolean), + { + required: !!f.required, + allowOther: f.allowOther, + displayLabel: key, + }, + ); + if (msg) return false; + continue; + } + if (!f.required) continue; + if (t === "checkboxes") { if (!Array.isArray(raw) || raw.length === 0) return false; } else if (isStarRatingField(f)) { if (!starValueValid(raw)) return false; @@ -67,7 +90,16 @@ export function DynamicFeedbackForm({ const data: Record = {}; for (const f of fields) { const key = String(f.label).trim(); - data[key] = values[key] ?? ""; + const t = (f.type || "").toLowerCase(); + if (t === "rank") { + data[key] = rankValueToOrderedLabels( + parseRankValue(values[key]), + (f.options || []).map((o) => String(o).trim()).filter(Boolean), + f.allowOther, + ); + } else { + data[key] = values[key] ?? ""; + } } setSubmitting(true); try { @@ -265,6 +297,78 @@ function FieldInput({ ); } + if (t === "rank" && opts.length > 0) { + const parsed = parseRankValue(value); + const slots = rankSlotCount(opts, f.allowOther); + const ranks = Array.from({ length: slots }, (_, i) => i + 1); + const otherLbl = (f.otherLabel || "Other").trim() || "Other"; + const takenByOthers = (exceptKey: string): Set => { + const s = new Set(); + for (const [k, v] of Object.entries(parsed.ranks)) { + if (k === exceptKey) continue; + if (typeof v === "number") s.add(v); + } + return s; + }; + const renderSelect = (key: string) => { + const current = parsed.ranks[key]; + const taken = takenByOthers(key); + return ( + + ); + }; + return ( +
+ + {label} + {f.required ? * : null} + +
+ {opts.map((o) => ( +
+ {o} + +
+ ))} + {f.allowOther ? ( +
+ onChange(label, { ...parsed, otherText: e.target.value } satisfies RankFieldValue)} + className={`${baseInput} min-w-[8rem] flex-1`} + /> + +
+ ) : null} +
+
+ ); + } + if (t === "checkbox") { return (