From f361df489b40cd775841cdd8c8f44591f54578f6 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 9 Jun 2026 22:12:59 -0700 Subject: [PATCH 1/4] add tehsil in plan dropdown --- src/pages/moderation.jsx | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index 2ef1175..474abba 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -48,6 +48,7 @@ import { stripSystemFields, } from "./moderation/constants"; import { getDynamicMarkerIcon } from "./moderation/helper"; +import { getBlocks } from "./base_function"; const getToken = () => sessionStorage.getItem("accessToken"); @@ -134,6 +135,7 @@ const SelectionPage = ({ const [organizations, setOrganizations] = useState([]); const [selectedPlan, setSelectedPlan] = useState(initialPlan); const [selectedForm, setSelectedForm] = useState(initialForm); + const [blocksMap, setBlocksMap] = useState({}); useEffect(() => { if (!isSuperAdmin) return; @@ -216,6 +218,34 @@ const SelectionPage = ({ } }, [plans, initialPlan]); + useEffect(() => { + if (!plans || plans.length === 0) return; + + const uniqueDistricts = [ + ...new Set(plans.map((p) => p.district_soi).filter(Boolean)), + ]; + + const fetchAllBlocks = async () => { + for (const districtCode of uniqueDistricts) { + try { + const blocks = await getBlocks(districtCode); + const blockObj = {}; + blocks.forEach((block) => { + blockObj[block.id] = block.block_name; + }); + setBlocksMap((prev) => ({ + ...prev, + ...blockObj, + })); + } catch (err) { + console.error(`Failed to fetch blocks for district ${districtCode}`, err); + } + } + }; + + fetchAllBlocks(); + }, [plans]); + const formatPlansForDropdown = (rawPlans = []) => rawPlans.map((p) => ({ plan_id: p.id || p.plan_id, @@ -224,6 +254,8 @@ const SelectionPage = ({ year: p.created_at ? new Date(p.created_at).getFullYear() : "", village: p.village || p.village_name || "", created_at: p.created_at || "", + tehsil_soi: p.tehsil_soi, + district_soi: p.district_soi, })); const handleProjectChange = (e) => { @@ -463,6 +495,24 @@ const SelectionPage = ({ {date} )} + {plan.tehsil_soi && ( + + + + + {blocksMap[plan.tehsil_soi] || `Tehsil (${plan.tehsil_soi})`} + + )} ); From 45f2dabd66a6f0cde9d76b2ed5a686b229fe8e4f Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Tue, 9 Jun 2026 23:14:08 -0700 Subject: [PATCH 2/4] add language drop down in moderation --- src/pages/moderation.jsx | 800 +++++++++++++++--------------- src/pages/moderation/constants.js | 128 ++--- 2 files changed, 463 insertions(+), 465 deletions(-) diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index 474abba..5296b9b 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -46,6 +46,7 @@ import { getFormTemplate, shouldHideBeneficiaryName, stripSystemFields, + LANGUAGE_MAP, } from "./moderation/constants"; import { getDynamicMarkerIcon } from "./moderation/helper"; import { getBlocks } from "./base_function"; @@ -337,8 +338,8 @@ const SelectionPage = ({ value={ selectedOrg ? organizations - .map((org) => ({ value: org.id, label: org.name })) - .find((o) => o.value === selectedOrg) + .map((org) => ({ value: org.id, label: org.name })) + .find((o) => o.value === selectedOrg) : null } onChange={(opt) => { @@ -365,11 +366,11 @@ const SelectionPage = ({ value={ selectedProject ? projects - .map((p) => ({ - value: p.id || p.project_id, - label: p.project_name || p.name, - })) - .find((p) => p.value === selectedProject) + .map((p) => ({ + value: p.id || p.project_id, + label: p.project_name || p.name, + })) + .find((p) => p.value === selectedProject) : null } onChange={(opt) => @@ -406,12 +407,12 @@ const SelectionPage = ({ value={ selectedPlan ? plans - .map((plan) => ({ - value: plan.plan_id, - label: plan.plan, - plan, - })) - .find((p) => p.value === Number(selectedPlan)) + .map((plan) => ({ + value: plan.plan_id, + label: plan.plan, + plan, + })) + .find((p) => p.value === Number(selectedPlan)) : null } onChange={(opt) => setSelectedPlan(opt?.value || "")} @@ -425,10 +426,10 @@ const SelectionPage = ({ } const date = plan.created_at ? new Date(plan.created_at).toLocaleDateString("en-IN", { - day: "2-digit", - month: "short", - year: "numeric", - }) + day: "2-digit", + month: "short", + year: "numeric", + }) : null; return (
@@ -532,11 +533,11 @@ const SelectionPage = ({ value={ selectedForm ? forms - .map((form) => ({ - value: form.name, - label: FORM_DISPLAY_NAMES[form.name] || form.name, - })) - .find((f) => f.value === selectedForm) + .map((form) => ({ + value: form.name, + label: FORM_DISPLAY_NAMES[form.name] || form.name, + })) + .find((f) => f.value === selectedForm) : null } onChange={(opt) => setSelectedForm(opt?.value || "")} @@ -579,6 +580,7 @@ const FormViewPage = ({ const [isEditing, setIsEditing] = useState(false); const [dprExpanded, setDprExpanded] = useState(false); const [dprEmail, setDprEmail] = useState(""); + const [dprLanguage, setDprLanguage] = useState("en"); const [dprLoading, setDprLoading] = useState(false); const [dprNotification, setDprNotification] = useState(null); const [planDetails, setPlanDetails] = useState(null); @@ -601,7 +603,7 @@ const FormViewPage = ({ const [validationLoading, setValidationLoading] = useState({}); const [saveStatus, setSaveStatus] = useState("idle"); const [saveError, setSaveError] = useState(""); - const [deleteStatus, setDeleteStatus] = useState("idle"); + const [deleteStatus, setDeleteStatus] = useState("idle"); const [deleteError, setDeleteError] = useState(""); const [submissionToDelete, setSubmissionToDelete] = useState(null); const [formTemplateLoading, setFormTemplateLoading] = useState(false); @@ -666,8 +668,8 @@ const FormViewPage = ({ if (!res.ok) { throw new Error( data?.message || - data?.error || - "Failed to fetch DPR workflow status.", + data?.error || + "Failed to fetch DPR workflow status.", ); } return data; @@ -686,7 +688,7 @@ const FormViewPage = ({ }); }, [selectedPlan]); - const reloadSubmissions = (resetToPage1 = false) => { + const reloadSubmissions = (resetToPage1 = false) => { if (viewMode === "map") { fetchSubmissions(1, "map"); } else { @@ -848,216 +850,216 @@ const FormViewPage = ({ }; // Generic function to analyze form schema and identify field types -const analyzeFormSchema = (schema) => { - const fieldTypes = {}; - - const analyzeElement = (element, parentName = "") => { - const elementName = - element.name.startsWith(parentName + "-") - ? element.name - : parentName - ? `${parentName}-${element.name}` - : element.name; - - if (element.type === "checkbox") { - fieldTypes[elementName] = "checkbox"; - fieldTypes[element.name] = "checkbox"; // also register bare name - } else if (element.type === "radiogroup") { - fieldTypes[elementName] = "radio"; - fieldTypes[element.name] = "radio"; - } else if (element.type === "multipletext") { - fieldTypes[elementName] = "multipletext"; - fieldTypes[element.name] = "multipletext"; - } else if (element.type === "panel") { - if (element.elements) { - element.elements.forEach((child) => { - if (child.name.includes("-")) { - analyzeElement(child, ""); - } else { - analyzeElement(child, element.name); - } - }); + const analyzeFormSchema = (schema) => { + const fieldTypes = {}; + + const analyzeElement = (element, parentName = "") => { + const elementName = + element.name.startsWith(parentName + "-") + ? element.name + : parentName + ? `${parentName}-${element.name}` + : element.name; + + if (element.type === "checkbox") { + fieldTypes[elementName] = "checkbox"; + fieldTypes[element.name] = "checkbox"; // also register bare name + } else if (element.type === "radiogroup") { + fieldTypes[elementName] = "radio"; + fieldTypes[element.name] = "radio"; + } else if (element.type === "multipletext") { + fieldTypes[elementName] = "multipletext"; + fieldTypes[element.name] = "multipletext"; + } else if (element.type === "panel") { + if (element.elements) { + element.elements.forEach((child) => { + if (child.name.includes("-")) { + analyzeElement(child, ""); + } else { + analyzeElement(child, element.name); + } + }); + } } - } - if (element.items && Array.isArray(element.items)) { - fieldTypes[elementName] = "multipletext"; - fieldTypes[element.name] = "multipletext"; - } - }; - - if (schema.pages) { - schema.pages.forEach((page) => { - if (page.elements) { - page.elements.forEach((element) => analyzeElement(element)); + if (element.items && Array.isArray(element.items)) { + fieldTypes[elementName] = "multipletext"; + fieldTypes[element.name] = "multipletext"; } - }); - } - - return fieldTypes; -}; + }; - const buildChoiceMap = (schema) => { - const choiceMap = {}; - const processElement = (element) => { - if ( - (element.type === "radiogroup" || - element.type === "checkbox" || - element.type === "dropdown") && - Array.isArray(element.choices) - ) { - const map = {}; - element.choices.forEach((choice) => { - if (typeof choice === "object" && choice.value !== undefined) { - map[String(choice.value).toLowerCase().trim()] = choice.value; - const text = - typeof choice.text === "object" - ? choice.text?.default ?? Object.values(choice.text)[0] - : choice.text; - if (text) map[String(text).toLowerCase().trim()] = choice.value; - } else if (typeof choice === "string") { - map[choice.toLowerCase().trim()] = choice; + if (schema.pages) { + schema.pages.forEach((page) => { + if (page.elements) { + page.elements.forEach((element) => analyzeElement(element)); } }); - choiceMap[element.name] = map; } - if (element.elements) element.elements.forEach(processElement); + + return fieldTypes; }; - if (schema.pages) - schema.pages.forEach((page) => - (page.elements || []).forEach(processElement) - ); - return choiceMap; -}; -const resolveCheckboxValues = (rawString, fieldChoices) => { - if (!rawString || typeof rawString !== "string") return []; - const trimmed = rawString.trim(); - if (!trimmed) return []; - if (!fieldChoices) - return trimmed.split(" ").filter((v) => v.trim().length > 0); - - // Strategy 1: space-split — if ALL tokens match known values - const spaceSplit = trimmed.split(" ").filter((v) => v.trim().length > 0); - const allMatch = spaceSplit.every( - (t) => fieldChoices[t.toLowerCase().trim()] !== undefined - ); - if (allMatch) - return spaceSplit.map((t) => fieldChoices[t.toLowerCase().trim()]); - - // Strategy 2: capital-letter split for multi-word values - const capitalSplit = trimmed - .split(/(?=[A-Z])/) - .map((t) => t.trim()) - .filter((t) => t.length > 0); - return capitalSplit.map((t) => fieldChoices[t.toLowerCase().trim()] ?? t); -}; + const buildChoiceMap = (schema) => { + const choiceMap = {}; + const processElement = (element) => { + if ( + (element.type === "radiogroup" || + element.type === "checkbox" || + element.type === "dropdown") && + Array.isArray(element.choices) + ) { + const map = {}; + element.choices.forEach((choice) => { + if (typeof choice === "object" && choice.value !== undefined) { + map[String(choice.value).toLowerCase().trim()] = choice.value; + const text = + typeof choice.text === "object" + ? choice.text?.default ?? Object.values(choice.text)[0] + : choice.text; + if (text) map[String(text).toLowerCase().trim()] = choice.value; + } else if (typeof choice === "string") { + map[choice.toLowerCase().trim()] = choice; + } + }); + choiceMap[element.name] = map; + } + if (element.elements) element.elements.forEach(processElement); + }; + if (schema.pages) + schema.pages.forEach((page) => + (page.elements || []).forEach(processElement) + ); + return choiceMap; + }; -const resolveRadioValue = (rawValue, fieldChoices) => { - if (!rawValue || typeof rawValue !== "string") return rawValue; - if (!fieldChoices) return rawValue; - const resolved = fieldChoices[rawValue.toLowerCase().trim()]; - return resolved !== undefined ? resolved : rawValue; -}; + const resolveCheckboxValues = (rawString, fieldChoices) => { + if (!rawString || typeof rawString !== "string") return []; + const trimmed = rawString.trim(); + if (!trimmed) return []; + if (!fieldChoices) + return trimmed.split(" ").filter((v) => v.trim().length > 0); + + // Strategy 1: space-split — if ALL tokens match known values + const spaceSplit = trimmed.split(" ").filter((v) => v.trim().length > 0); + const allMatch = spaceSplit.every( + (t) => fieldChoices[t.toLowerCase().trim()] !== undefined + ); + if (allMatch) + return spaceSplit.map((t) => fieldChoices[t.toLowerCase().trim()]); + + // Strategy 2: capital-letter split for multi-word values + const capitalSplit = trimmed + .split(/(?=[A-Z])/) + .map((t) => t.trim()) + .filter((t) => t.length > 0); + return capitalSplit.map((t) => fieldChoices[t.toLowerCase().trim()] ?? t); + }; + + const resolveRadioValue = (rawValue, fieldChoices) => { + if (!rawValue || typeof rawValue !== "string") return rawValue; + if (!fieldChoices) return rawValue; + const resolved = fieldChoices[rawValue.toLowerCase().trim()]; + return resolved !== undefined ? resolved : rawValue; + }; // Transform API data to SurveyJS format -const transformApiToSurvey = (submission, formSchema) => { - const fieldTypes = analyzeFormSchema(formSchema); - const choiceMap = buildChoiceMap(formSchema); - const transformedData = { ...submission }; - - const processObject = (obj, parentKey = "") => { - Object.keys(obj).forEach((key) => { - const value = obj[key]; - const fullKey = parentKey ? `${parentKey}-${key}` : key; - - if (value && typeof value === "object" && !Array.isArray(value)) { - // GPS_point special handling - if (key === "GPS_point") { - const coordsObj = - value.point_mapsappearance || value.point_mapappearance; - if (coordsObj?.coordinates) { - const coords = coordsObj.coordinates; - transformedData["GPS_point"] = { - longitude: coords[0], - latitude: coords[1], - }; - return; + const transformApiToSurvey = (submission, formSchema) => { + const fieldTypes = analyzeFormSchema(formSchema); + const choiceMap = buildChoiceMap(formSchema); + const transformedData = { ...submission }; + + const processObject = (obj, parentKey = "") => { + Object.keys(obj).forEach((key) => { + const value = obj[key]; + const fullKey = parentKey ? `${parentKey}-${key}` : key; + + if (value && typeof value === "object" && !Array.isArray(value)) { + // GPS_point special handling + if (key === "GPS_point") { + const coordsObj = + value.point_mapsappearance || value.point_mapappearance; + if (coordsObj?.coordinates) { + const coords = coordsObj.coordinates; + transformedData["GPS_point"] = { + longitude: coords[0], + latitude: coords[1], + }; + return; + } + if (value.latitude !== undefined && value.longitude !== undefined) { + transformedData["GPS_point"] = value; + return; + } } + if (value.latitude !== undefined && value.longitude !== undefined) { - transformedData["GPS_point"] = value; + transformedData[fullKey] = value; return; } - } - if (value.latitude !== undefined && value.longitude !== undefined) { - transformedData[fullKey] = value; - return; - } + // multipletext → keep as nested object, never flatten + const isMultipleText = + fieldTypes[key] === "multipletext" || + fieldTypes[fullKey] === "multipletext"; - // multipletext → keep as nested object, never flatten - const isMultipleText = - fieldTypes[key] === "multipletext" || - fieldTypes[fullKey] === "multipletext"; - - if (isMultipleText) { - transformedData[key] = value; - return; - } + if (isMultipleText) { + transformedData[key] = value; + return; + } - // Regular nested object (panel) → flatten with "-" separator - processObject(value, key); - } else { - if (typeof value === "string" && value.trim().length > 0) { - const fieldType = fieldTypes[fullKey] || fieldTypes[key]; - const fieldChoices = choiceMap[fullKey] || choiceMap[key]; - - if (fieldType === "checkbox") { - transformedData[fullKey] = resolveCheckboxValues(value, fieldChoices); - } else if (fieldType === "radio") { - transformedData[fullKey] = resolveRadioValue(value, fieldChoices); + // Regular nested object (panel) → flatten with "-" separator + processObject(value, key); + } else { + if (typeof value === "string" && value.trim().length > 0) { + const fieldType = fieldTypes[fullKey] || fieldTypes[key]; + const fieldChoices = choiceMap[fullKey] || choiceMap[key]; + + if (fieldType === "checkbox") { + transformedData[fullKey] = resolveCheckboxValues(value, fieldChoices); + } else if (fieldType === "radio") { + transformedData[fullKey] = resolveRadioValue(value, fieldChoices); + } else { + // text/number input — use as-is + transformedData[fullKey] = value; + } } else { - // text/number input — use as-is + // null, undefined, number, boolean — pass through directly transformedData[fullKey] = value; } - } else { - // null, undefined, number, boolean — pass through directly - transformedData[fullKey] = value; } + }); + }; + + processObject(submission); + + // Legacy key mappings + const legacyKeyMap = { + "Livestock-is_demand_livestock": "select_one_demand_promoting_livestock", + "Livestock-demands_promoting_livestock": "select_one_promoting_livestock", + "Livestock-select_one_promoting_livestock_other": + "select_one_promoting_livestock_other", + "kitchen_gardens-area_kg": "area_didi_badi", + "kitchen_gardens-assets_kg": "indi_assets", + "fisheries-is_demand_fisheries": "select_one_demand_promoting_fisheries", + "fisheries-demands_promoting_fisheries": "select_one_promoting_fisheries", + "fisheries-demands_promoting_fisheries_other": + "select_one_promoting_fisheries_other", + }; + + Object.entries(legacyKeyMap).forEach(([newKey, oldKey]) => { + const oldValue = submission[oldKey]; + const currentValue = transformedData[newKey]; + const isCurrentEmpty = + currentValue === null || currentValue === undefined || currentValue === ""; + const isOldValueReal = + oldValue !== null && oldValue !== undefined && oldValue !== ""; + if (isCurrentEmpty && isOldValueReal) { + transformedData[newKey] = oldValue; } }); - }; - processObject(submission); - - // Legacy key mappings - const legacyKeyMap = { - "Livestock-is_demand_livestock": "select_one_demand_promoting_livestock", - "Livestock-demands_promoting_livestock": "select_one_promoting_livestock", - "Livestock-select_one_promoting_livestock_other": - "select_one_promoting_livestock_other", - "kitchen_gardens-area_kg": "area_didi_badi", - "kitchen_gardens-assets_kg": "indi_assets", - "fisheries-is_demand_fisheries": "select_one_demand_promoting_fisheries", - "fisheries-demands_promoting_fisheries": "select_one_promoting_fisheries", - "fisheries-demands_promoting_fisheries_other": - "select_one_promoting_fisheries_other", + return transformedData; }; - Object.entries(legacyKeyMap).forEach(([newKey, oldKey]) => { - const oldValue = submission[oldKey]; - const currentValue = transformedData[newKey]; - const isCurrentEmpty = - currentValue === null || currentValue === undefined || currentValue === ""; - const isOldValueReal = - oldValue !== null && oldValue !== undefined && oldValue !== ""; - if (isCurrentEmpty && isOldValueReal) { - transformedData[newKey] = oldValue; - } - }); - - return transformedData; -}; - // Transform SurveyJS data back to API format const transformSurveyToApi = (surveyData, originalSubmission, formSchema) => { const fieldTypes = analyzeFormSchema(formSchema); @@ -1481,100 +1483,100 @@ const transformApiToSurvey = (submission, formSchema) => { }; }, [submissions]); -const handleViewSubmission = async (submission) => { - setFormTemplateLoading(true); - const formTemplate = await getFormTemplate(selectedForm); - setFormTemplateLoading(false); - if (!formTemplate) { - alert(`No template found for form: ${selectedForm}`); - return; - } - - const cleanTemplate = stripSystemFields(formTemplate); - const transformedData = transformApiToSurvey(submission, formTemplate); - - setSelectedSubmission(submission); - setIsEditing(false); - - const model = new Model(cleanTemplate); - model.mode = "display"; - model.data = transformedData; - setSurveyModel(model); -}; + const handleViewSubmission = async (submission) => { + setFormTemplateLoading(true); + const formTemplate = await getFormTemplate(selectedForm); + setFormTemplateLoading(false); + if (!formTemplate) { + alert(`No template found for form: ${selectedForm}`); + return; + } + const cleanTemplate = stripSystemFields(formTemplate); + const transformedData = transformApiToSurvey(submission, formTemplate); -const handleEditSubmission = async (submission) => { - setFormTemplateLoading(true); - const formTemplate = await getFormTemplate(selectedForm); - setFormTemplateLoading(false); - if (!formTemplate) { - alert(`No template found for form: ${selectedForm}`); - return; - } + setSelectedSubmission(submission); + setIsEditing(false); - const cleanTemplate = stripSystemFields(formTemplate); - const transformedData = transformApiToSurvey(submission, formTemplate); + const model = new Model(cleanTemplate); + model.mode = "display"; + model.data = transformedData; + setSurveyModel(model); + }; - setSelectedSubmission(submission); - setIsEditing(true); - const model = new Model(cleanTemplate); - model.showCompletedPage = false; - model.data = transformedData; + const handleEditSubmission = async (submission) => { + setFormTemplateLoading(true); + const formTemplate = await getFormTemplate(selectedForm); + setFormTemplateLoading(false); + if (!formTemplate) { + alert(`No template found for form: ${selectedForm}`); + return; + } - model.onComplete.add((sender) => { - const saveData = transformSurveyToApi( - sender.data, - submission, - formTemplate, - ); - const uuid = getSubmissionUUID(submission); - handleSaveSubmission(uuid, saveData); - }); + const cleanTemplate = stripSystemFields(formTemplate); + const transformedData = transformApiToSurvey(submission, formTemplate); - setSurveyModel(model); -}; + setSelectedSubmission(submission); + setIsEditing(true); -const handleSaveSubmission = async (uuid, data) => { - setSaveStatus("saving"); - setSaveError(""); - try { - const response = await fetch( - `${BASEURL}api/v1/submissions/${selectedForm}/${uuid}/modify/`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${sessionStorage.getItem("accessToken")}`, + const model = new Model(cleanTemplate); + model.showCompletedPage = false; + model.data = transformedData; + + model.onComplete.add((sender) => { + const saveData = transformSurveyToApi( + sender.data, + submission, + formTemplate, + ); + const uuid = getSubmissionUUID(submission); + handleSaveSubmission(uuid, saveData); + }); + + setSurveyModel(model); + }; + + const handleSaveSubmission = async (uuid, data) => { + setSaveStatus("saving"); + setSaveError(""); + try { + const response = await fetch( + `${BASEURL}api/v1/submissions/${selectedForm}/${uuid}/modify/`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${sessionStorage.getItem("accessToken")}`, + }, + body: JSON.stringify(data), }, - body: JSON.stringify(data), - }, - ); + ); + + const result = await response.json(); + + if (!response.ok) { + setSaveStatus("error"); + setSaveError(result?.message || result?.error || "Save failed. Please try again."); + return; + } + + setSaveStatus("success"); + reloadSubmissions(true); - const result = await response.json(); + // Auto close after 1.5s on success + setTimeout(() => { + setSelectedSubmission(null); + setSurveyModel(null); + setSaveStatus("idle"); + }, 1500); - if (!response.ok) { + } catch (error) { + console.error("Save error:", error); setSaveStatus("error"); - setSaveError(result?.message || result?.error || "Save failed. Please try again."); - return; + setSaveError("Network error. Please try again."); } - - setSaveStatus("success"); - reloadSubmissions(true); - - // Auto close after 1.5s on success - setTimeout(() => { - setSelectedSubmission(null); - setSurveyModel(null); - setSaveStatus("idle"); - }, 1500); - - } catch (error) { - console.error("Save error:", error); - setSaveStatus("error"); - setSaveError("Network error. Please try again."); - } -}; + }; const handleValidateSubmission = async (submission) => { const uuid = getSubmissionUUID(submission); @@ -1634,7 +1636,7 @@ const handleSaveSubmission = async (uuid, data) => { setDeleteStatus("error"); setDeleteError("Network error. Please try again."); } -}; + }; const handleGenerateDPR = async () => { if (!dprEmail || !selectedPlan) return; @@ -1650,6 +1652,7 @@ const handleSaveSubmission = async (uuid, data) => { body: JSON.stringify({ plan_id: Number(selectedPlan), email_id: dprEmail, + language: dprLanguage, }), }); const data = await response.json(); @@ -1659,6 +1662,7 @@ const handleSaveSubmission = async (uuid, data) => { message: data.message || "DPR generation request sent successfully!", }); setDprEmail(""); + setDprLanguage("en"); setDprExpanded(false); } else { setDprNotification({ @@ -1699,8 +1703,8 @@ const handleSaveSubmission = async (uuid, data) => { if (!response.ok) { throw new Error( data?.message || - data?.error || - "Failed to update DPR workflow status.", + data?.error || + "Failed to update DPR workflow status.", ); } @@ -1876,22 +1880,20 @@ const handleSaveSubmission = async (uuid, data) => {
+
+ +
+
@@ -2087,24 +2099,21 @@ const handleSaveSubmission = async (uuid, data) => { ) } disabled={planReviewLoading} - className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${ - planDetails?.is_reviewed + className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${planDetails?.is_reviewed ? "bg-blue-700" : "bg-slate-300" - } ${ - planReviewLoading + } ${planReviewLoading ? "cursor-not-allowed opacity-60" : "cursor-pointer" - }`} + }`} aria-pressed={Boolean(planDetails?.is_reviewed)} aria-label="Is DPR Reviewed?" > @@ -2113,11 +2122,10 @@ const handleSaveSubmission = async (uuid, data) => { {planReviewNotification && (
{planReviewNotification.message} @@ -2154,27 +2162,24 @@ const handleSaveSubmission = async (uuid, data) => { dprWorkflowMissing || dprWorkflowLoading === "status-submitted" } - className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${ - dprWorkflowStatus?.status === "SUBMITTED" + className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "SUBMITTED" ? "bg-blue-700" : "bg-slate-300" - } ${ - dprWorkflowMissing || - dprWorkflowLoading === "status-submitted" + } ${dprWorkflowMissing || + dprWorkflowLoading === "status-submitted" ? "cursor-not-allowed opacity-60" : "cursor-pointer" - }`} + }`} aria-pressed={ dprWorkflowStatus?.status === "SUBMITTED" } aria-label="DPR submitted" >
@@ -2219,27 +2224,24 @@ const handleSaveSubmission = async (uuid, data) => { dprWorkflowMissing || dprWorkflowLoading === "status-approved" } - className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${ - dprWorkflowStatus?.status === "APPROVED" + className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "APPROVED" ? "bg-emerald-700" : "bg-slate-300" - } ${ - dprWorkflowMissing || - dprWorkflowLoading === "status-approved" + } ${dprWorkflowMissing || + dprWorkflowLoading === "status-approved" ? "cursor-not-allowed opacity-60" : "cursor-pointer" - }`} + }`} aria-pressed={ dprWorkflowStatus?.status === "APPROVED" } aria-label="DPR approved" > @@ -2266,27 +2268,24 @@ const handleSaveSubmission = async (uuid, data) => { dprWorkflowMissing || dprWorkflowLoading === "status-rejected" } - className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${ - dprWorkflowStatus?.status === "REJECTED" + className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "REJECTED" ? "bg-red-700" : "bg-slate-300" - } ${ - dprWorkflowMissing || - dprWorkflowLoading === "status-rejected" + } ${dprWorkflowMissing || + dprWorkflowLoading === "status-rejected" ? "cursor-not-allowed opacity-60" : "cursor-pointer" - }`} + }`} aria-pressed={ dprWorkflowStatus?.status === "REJECTED" } aria-label="DPR rejected" > @@ -2304,11 +2303,10 @@ const handleSaveSubmission = async (uuid, data) => { {dprWorkflowNotification && (
{dprWorkflowNotification.message} @@ -2323,18 +2321,16 @@ const handleSaveSubmission = async (uuid, data) => { {/* Floating toast notification */} {dprNotification && (
{dprNotification.type === "success" ? ( { {selectedSubmission && surveyModel && (
- + {/* Header */}
@@ -2412,7 +2408,7 @@ const handleSaveSubmission = async (uuid, data) => { Submitted:{" "} {formatToIST( selectedSubmission.__system?.submissionDate || - selectedSubmission.submission_time, + selectedSubmission.submission_time, )}

@@ -2489,7 +2485,7 @@ const handleSaveSubmission = async (uuid, data) => { {submissionToDelete && deleteStatus !== "idle" && (
- + {/* Header */}

Delete Submission

@@ -2712,25 +2708,22 @@ const handleSaveSubmission = async (uuid, data) => { > {/* Left accent stripe */}
{/* Top row: status badge + date */}
{isModerated ? "Moderated" : ""} @@ -2739,7 +2732,7 @@ const handleSaveSubmission = async (uuid, data) => { {formatToIST( submission.__system?.submissionDate || - submission.submission_time, + submission.submission_time, )}
@@ -2786,7 +2779,7 @@ const handleSaveSubmission = async (uuid, data) => { {value}
- ))} + ))}
@@ -2797,12 +2790,11 @@ const handleSaveSubmission = async (uuid, data) => { Site Validation
{validationResults[uuid].finalDecision} @@ -2813,13 +2805,12 @@ const handleSaveSubmission = async (uuid, data) => { ).map(([param, category]) => ( {param} → {category.replace("_", " ")} @@ -2876,31 +2867,31 @@ const handleSaveSubmission = async (uuid, data) => { "Surface Water Body Remotely Sensed Maintenance", "Well", ].includes(selectedForm) && ( - - )} + + )} - + )}
@@ -2927,11 +2918,10 @@ const handleSaveSubmission = async (uuid, data) => { diff --git a/src/pages/moderation/constants.js b/src/pages/moderation/constants.js index 7ede168..b0215fb 100644 --- a/src/pages/moderation/constants.js +++ b/src/pages/moderation/constants.js @@ -93,7 +93,7 @@ export const getFormTemplate = async (formName) => { const res = await fetch(`${S3_BASE}/${filename}`); if (!res.ok) throw new Error(`Failed to fetch ${filename}`); const json = await res.json(); - formTemplateCache[formName] = json; + formTemplateCache[formName] = json; return json; } catch (err) { console.error(`Form template fetch error for ${formName}:`, err); @@ -177,39 +177,39 @@ export const CARD_DISPLAY_FIELDS = { ], Livelihood: [ - { - key: "beneficiary_settlement", - label: "Name of Beneficiary's Settlement", - }, - { - key: "beneficiary_name", - label: "Beneficiary's Name", - }, - { - key: "Livestock-is_demand_livestock", - label: "Livestock Demand?", - }, - { - key: "Livestock-ben_livestock", - label: "Livestock Beneficiary", - }, - { - key: "kitchen_gardens-assets_kg", - label: "Kitchen Garden Demand?", - }, - { - key: "kitchen_gardens-ben_kitchen_gardens", - label: "Kitchen Garden Beneficiary", - }, - { - key: "fisheries-is_demand_fisheries", - label: "Fisheries Demand?", - }, - { - key: "fisheries-ben_fisheries", - label: "Fisheries Beneficiary", - }, -], + { + key: "beneficiary_settlement", + label: "Name of Beneficiary's Settlement", + }, + { + key: "beneficiary_name", + label: "Beneficiary's Name", + }, + { + key: "Livestock-is_demand_livestock", + label: "Livestock Demand?", + }, + { + key: "Livestock-ben_livestock", + label: "Livestock Beneficiary", + }, + { + key: "kitchen_gardens-assets_kg", + label: "Kitchen Garden Demand?", + }, + { + key: "kitchen_gardens-ben_kitchen_gardens", + label: "Kitchen Garden Beneficiary", + }, + { + key: "fisheries-is_demand_fisheries", + label: "Fisheries Demand?", + }, + { + key: "fisheries-ben_fisheries", + label: "Fisheries Beneficiary", + }, + ], Agri: [ { @@ -299,29 +299,29 @@ export const CARD_DISPLAY_FIELDS = { export const structureRules = { - "Staggered Contour trenches (SCT)":"staggered_contour_trenches_sct", - "Check dam":"check_dam", - "Percolation tank":"percolation_tank", - "Earthen gully plug":"earthen_gully_plug", - "Drainage/soakage channels":"drainage_soakage_channels", - "Recharge pits":"recharge_pits", - "Soakage pits":"soakage_pits", - "Trench cum bund network":"trench_cum_bund_network", - "Continuous contour trenches (CCT)":"continuous_contour_trenches_cct", - "Water absorption trenches (WAT)":"water_absorption_trenches_wat", - "Loose Boulder Structure":"loose_boulder_structure", - "Rock fill dam":"rock_fill_dam", - "Stone bunding":"stone_bunding", - "Diversion drains":"diversion_drains", - "Bunding:Contour bunds/ graded bunds":"contour_bund", - "5% model structure":"5_percent_model_structure", - "30-40 model structure":"30_40_model_structure", - "Farm pond":"farm_pond", - "Community Pond":"community_pond", - "Well":"well", - "Canal":"canal", - "Farm bund":"farm_bund", - "Large water body":"large_water_body" + "Staggered Contour trenches (SCT)": "staggered_contour_trenches_sct", + "Check dam": "check_dam", + "Percolation tank": "percolation_tank", + "Earthen gully plug": "earthen_gully_plug", + "Drainage/soakage channels": "drainage_soakage_channels", + "Recharge pits": "recharge_pits", + "Soakage pits": "soakage_pits", + "Trench cum bund network": "trench_cum_bund_network", + "Continuous contour trenches (CCT)": "continuous_contour_trenches_cct", + "Water absorption trenches (WAT)": "water_absorption_trenches_wat", + "Loose Boulder Structure": "loose_boulder_structure", + "Rock fill dam": "rock_fill_dam", + "Stone bunding": "stone_bunding", + "Diversion drains": "diversion_drains", + "Bunding:Contour bunds/ graded bunds": "contour_bund", + "5% model structure": "5_percent_model_structure", + "30-40 model structure": "30_40_model_structure", + "Farm pond": "farm_pond", + "Community Pond": "community_pond", + "Well": "well", + "Canal": "canal", + "Farm bund": "farm_bund", + "Large water body": "large_water_body" } // Config: if this field has this value → hide Beneficiary_Name in card @@ -331,7 +331,7 @@ export const BENEFICIARY_VISIBILITY_RULES = { hideWhenValue: "Public well", }, Groundwater: { - field: "demand_type", + field: "demand_type", hideWhenValue: "Community_demand", }, Agrohorticulture: { @@ -381,7 +381,7 @@ const SYSTEM_FIELD_NAMES = new Set([ "crop_note_1", "crop_note_2", "note", - "question1", + "question1", "crop_Grid_id", "work_id", "corresponding_work_id" @@ -414,4 +414,12 @@ export const stripSystemFields = (schema) => { elements: filterElements(page.elements || []), })), }; -}; \ No newline at end of file +}; + +export const LANGUAGE_MAP = { + "en": "English", + "hi": "Hindi", + "gu": "Gujarati", + "kn": "Kannada", + "or": "Odia", +}; From 207baa1575471041ac3f18e18593d58987cd76ce Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Wed, 10 Jun 2026 22:16:18 -0700 Subject: [PATCH 3/4] code format --- src/pages/moderation.jsx | 90 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index 5296b9b..8ef2e0e 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -1881,8 +1881,8 @@ const FormViewPage = ({ @@ -2100,8 +2100,8 @@ const FormViewPage = ({ } disabled={planReviewLoading} className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${planDetails?.is_reviewed - ? "bg-blue-700" - : "bg-slate-300" + ? "bg-blue-700" + : "bg-slate-300" } ${planReviewLoading ? "cursor-not-allowed opacity-60" : "cursor-pointer" @@ -2111,8 +2111,8 @@ const FormViewPage = ({ > @@ -2123,8 +2123,8 @@ const FormViewPage = ({ {planReviewNotification && (
@@ -2163,8 +2163,8 @@ const FormViewPage = ({ dprWorkflowLoading === "status-submitted" } className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "SUBMITTED" - ? "bg-blue-700" - : "bg-slate-300" + ? "bg-blue-700" + : "bg-slate-300" } ${dprWorkflowMissing || dprWorkflowLoading === "status-submitted" ? "cursor-not-allowed opacity-60" @@ -2177,8 +2177,8 @@ const FormViewPage = ({ > @@ -2225,8 +2225,8 @@ const FormViewPage = ({ dprWorkflowLoading === "status-approved" } className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "APPROVED" - ? "bg-emerald-700" - : "bg-slate-300" + ? "bg-emerald-700" + : "bg-slate-300" } ${dprWorkflowMissing || dprWorkflowLoading === "status-approved" ? "cursor-not-allowed opacity-60" @@ -2239,8 +2239,8 @@ const FormViewPage = ({ > @@ -2269,8 +2269,8 @@ const FormViewPage = ({ dprWorkflowLoading === "status-rejected" } className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-2 ${dprWorkflowStatus?.status === "REJECTED" - ? "bg-red-700" - : "bg-slate-300" + ? "bg-red-700" + : "bg-slate-300" } ${dprWorkflowMissing || dprWorkflowLoading === "status-rejected" ? "cursor-not-allowed opacity-60" @@ -2283,8 +2283,8 @@ const FormViewPage = ({ > @@ -2304,8 +2304,8 @@ const FormViewPage = ({ {dprWorkflowNotification && (
@@ -2322,14 +2322,14 @@ const FormViewPage = ({ {dprNotification && (
{dprNotification.type === "success" ? ( @@ -2717,8 +2717,8 @@ const FormViewPage = ({
{validationResults[uuid].finalDecision} @@ -2806,10 +2806,10 @@ const FormViewPage = ({ {param} → {category.replace("_", " ")} @@ -2919,8 +2919,8 @@ const FormViewPage = ({ key={i} onClick={() => fetchSubmissions(i + 1)} className={`px-5 py-2.5 rounded-xl font-bold transition-all shadow-md ${page === i + 1 - ? "bg-gradient-to-r from-indigo-600 to-blue-600 text-white" - : "bg-white border-2 border-slate-300 hover:bg-slate-50" + ? "bg-gradient-to-r from-indigo-600 to-blue-600 text-white" + : "bg-white border-2 border-slate-300 hover:bg-slate-50" }`} > {i + 1} From 4bc2b4deb2aadea1eb3f1d7d74414d86a783ff57 Mon Sep 17 00:00:00 2001 From: pawangramvaani Date: Fri, 12 Jun 2026 00:32:31 -0700 Subject: [PATCH 4/4] add more info in moderation selection --- src/pages/moderation.jsx | 243 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 234 insertions(+), 9 deletions(-) diff --git a/src/pages/moderation.jsx b/src/pages/moderation.jsx index 8ef2e0e..8c7d773 100644 --- a/src/pages/moderation.jsx +++ b/src/pages/moderation.jsx @@ -137,6 +137,9 @@ const SelectionPage = ({ const [selectedPlan, setSelectedPlan] = useState(initialPlan); const [selectedForm, setSelectedForm] = useState(initialForm); const [blocksMap, setBlocksMap] = useState({}); + const [orgPlanCounts, setOrgPlanCounts] = useState({}); + const [projectPlanCounts, setProjectPlanCounts] = useState({}); + const [formSubmissionCounts, setFormSubmissionCounts] = useState({}); useEffect(() => { if (!isSuperAdmin) return; @@ -145,7 +148,32 @@ const SelectionPage = ({ .then((res) => res.json()) .then((data) => { const list = data.data || data.results || data; - setOrganizations(Array.isArray(list) ? list : []); + const orgList = Array.isArray(list) ? list : []; + setOrganizations(orgList); + + // Fetch plan counts for each org + orgList.forEach((org) => { + Promise.all([ + fetch( + `${BASEURL}api/v1/plan_count/?org_id=${org.id}`, + { headers: getHeaders() } + ).then((r) => r.json()), + fetch( + `${BASEURL}api/v1/plan_count/?org_id=${org.id}&is_completed=true`, + { headers: getHeaders() } + ).then((r) => r.json()) + ]) + .then(([totalData, completedData]) => { + const total = totalData.plan_count || 0; + const completed = completedData.plan_count || 0; + + setOrgPlanCounts((prev) => ({ + ...prev, + [org.id]: { total, completed }, + })); + }) + .catch(() => { }); + }); }) .catch((err) => console.error("Org fetch error", err)); }, [isSuperAdmin]); @@ -157,7 +185,33 @@ const SelectionPage = ({ .then((res) => res.json()) .then((data) => { const list = data.data || data.projects || data; - setProjects(Array.isArray(list) ? list : []); + const projList = Array.isArray(list) ? list : []; + setProjects(projList); + + // Fetch plan counts for each project + projList.forEach((proj) => { + const pid = proj.id || proj.project_id; + Promise.all([ + fetch( + `${BASEURL}api/v1/plan_count/?project_id=${pid}`, + { headers: getHeaders() } + ).then((r) => r.json()), + fetch( + `${BASEURL}api/v1/plan_count/?project_id=${pid}&is_completed=true`, + { headers: getHeaders() } + ).then((r) => r.json()) + ]) + .then(([totalData, completedData]) => { + const total = totalData.plan_count || 0; + const completed = completedData.plan_count || 0; + + setProjectPlanCounts((prev) => ({ + ...prev, + [pid]: { total, completed }, + })); + }) + .catch(() => { }); + }); }) .catch((err) => console.log(err)); }, [isSuperAdmin]); @@ -175,7 +229,33 @@ const SelectionPage = ({ .then((res) => res.json()) .then((data) => { const list = data.data || data.projects || data; - setProjects(Array.isArray(list) ? list : []); + const projList = Array.isArray(list) ? list : []; + setProjects(projList); + + // Fetch plan counts for each project + projList.forEach((proj) => { + const pid = proj.id || proj.project_id; + Promise.all([ + fetch( + `${BASEURL}api/v1/plan_count/?project_id=${pid}`, + { headers: getHeaders() } + ).then((r) => r.json()), + fetch( + `${BASEURL}api/v1/plan_count/?project_id=${pid}&is_completed=true`, + { headers: getHeaders() } + ).then((r) => r.json()) + ]) + .then(([totalData, completedData]) => { + const total = totalData.plan_count || 0; + const completed = completedData.plan_count || 0; + + setProjectPlanCounts((prev) => ({ + ...prev, + [pid]: { total, completed }, + })); + }) + .catch(() => { }); + }); }) .catch((err) => console.log(err)); }, [isSuperAdmin, selectedOrg]); @@ -257,6 +337,9 @@ const SelectionPage = ({ created_at: p.created_at || "", tehsil_soi: p.tehsil_soi, district_soi: p.district_soi, + is_completed: p.is_completed ?? false, + is_dpr_reviewed: p.is_dpr_reviewed ?? false, + is_dpr_approved: p.is_dpr_approved ?? false, })); const handleProjectChange = (e) => { @@ -300,6 +383,29 @@ const SelectionPage = ({ ); }; + // Fetch submission counts for all forms when a plan is selected + useEffect(() => { + if (!selectedPlan) { + setFormSubmissionCounts({}); + return; + } + + forms.forEach((form) => { + fetch( + `${BASEURL}api/v1/submissions/${form.name}/${selectedPlan}/`, + { headers: getHeaders() }, + ) + .then((res) => res.json()) + .then((data) => { + setFormSubmissionCounts((prev) => ({ + ...prev, + [form.name]: data.total_objects ?? data.data?.length ?? 0, + })); + }) + .catch(() => { }); + }); + }, [selectedPlan, forms]); + const handleLoadSubmissions = () => { if (!selectedForm || !selectedPlan) return; @@ -310,6 +416,27 @@ const SelectionPage = ({ onLoadSubmissions(selectedProject, selectedPlan, selectedForm, planName); }; + const getPlanCategory = (plan) => { + if (plan.is_completed) return "Completed"; + return "In Progress"; + }; + + const PLAN_CATEGORY_ORDER = ["Completed", "In Progress"]; + + const groupPlansForDropdown = (plans) => { + const groups = {}; + + plans.forEach((plan) => { + const category = getPlanCategory(plan); + if (!groups[category]) groups[category] = []; + groups[category].push({ value: plan.plan_id, label: plan.plan, plan }); + }); + + return PLAN_CATEGORY_ORDER + .filter((cat) => groups[cat]) + .map((cat) => ({ label: cat, options: groups[cat] })); + }; + return (
@@ -334,11 +461,12 @@ const SelectionPage = ({ options={organizations.map((org) => ({ value: org.id, label: org.name, + org, }))} value={ selectedOrg ? organizations - .map((org) => ({ value: org.id, label: org.name })) + .map((org) => ({ value: org.id, label: org.name, org })) .find((o) => o.value === selectedOrg) : null } @@ -347,6 +475,40 @@ const SelectionPage = ({ setSelectedProject(""); setPlans([]); }} + formatOptionLabel={({ org, label }, { context }) => { + const counts = orgPlanCounts[org?.id]; + if (context === "value") { + return ( + + {label} + {counts && ( + + ({counts.total} plans, {counts.completed} completed) + + )} + + ); + } + return ( +
+
+ {label} +
+ {counts && ( +
+ + + {counts.total} Plans + + + + {counts.completed} Completed + +
+ )} +
+ ); + }} isClearable />
@@ -362,6 +524,7 @@ const SelectionPage = ({ options={projects.map((p) => ({ value: p.id || p.project_id, label: p.project_name || p.name, + project: p, }))} value={ selectedProject @@ -369,6 +532,7 @@ const SelectionPage = ({ .map((p) => ({ value: p.id || p.project_id, label: p.project_name || p.name, + project: p, })) .find((p) => p.value === selectedProject) : null @@ -376,6 +540,41 @@ const SelectionPage = ({ onChange={(opt) => handleProjectChange({ target: { value: opt?.value || "" } }) } + formatOptionLabel={({ project, label }, { context }) => { + const pid = project?.id || project?.project_id; + const counts = projectPlanCounts[pid]; + if (context === "value") { + return ( + + {label} + {counts && ( + + ({counts.total} plans, {counts.completed} completed) + + )} + + ); + } + return ( +
+
+ {label} +
+ {counts && ( +
+ + + {counts.total} Plans + + + + {counts.completed} Completed + +
+ )} +
+ ); + }} isClearable />
@@ -399,11 +598,7 @@ const SelectionPage = ({ }), }} placeholder="-- Choose Plan --" - options={plans.map((plan) => ({ - value: plan.plan_id, - label: plan.plan, - plan, - }))} + options={groupPlansForDropdown(plans)} value={ selectedPlan ? plans @@ -541,6 +736,36 @@ const SelectionPage = ({ : null } onChange={(opt) => setSelectedForm(opt?.value || "")} + formatOptionLabel={({ value, label }, { context }) => { + const count = formSubmissionCounts[value]; + if (context === "value") { + return ( + + {label} + {count !== undefined && ( + + ({count} submissions) + + )} + + ); + } + return ( +
+
+ {label} +
+ {count !== undefined && ( +
+ + + {count} Submissions + +
+ )} +
+ ); + }} isClearable />