From df110031a406d6997043cf7d16f7378976c490c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Diamond?= <32074058+Andre-Diamond@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:04:01 +0200 Subject: [PATCH 1/7] feat: Add lodash and react-modal dependencies; refactor WorkingDocs component for improved type safety and functionality --- components/SummaryAgendaItems.tsx | 504 +++++++++------ components/SummaryMeetingInfo.tsx | 700 +++++++++++---------- components/SummaryTemplate.tsx | 615 +++++++++--------- components/WorkingDocs.tsx | 91 ++- package-lock.json | 55 +- package.json | 5 +- pages/submit-meeting-summary/index.tsx | 833 ++++++++++++++++++------- 7 files changed, 1747 insertions(+), 1056 deletions(-) diff --git a/components/SummaryAgendaItems.tsx b/components/SummaryAgendaItems.tsx index fc0cc78..944568d 100644 --- a/components/SummaryAgendaItems.tsx +++ b/components/SummaryAgendaItems.tsx @@ -6,111 +6,137 @@ import { getDefaultAgendaItem } from '../utils/getDefaultAgendaItem'; import { filterFormData } from '../utils/filterFormData'; const SummaryAgendaItems = ({ onUpdate }: any) => { - const { myVariable, setMyVariable } = useMyVariable(); - const formData = filterFormData(myVariable?.summary); - const initialAgendaItems = formData.agendaItems?.map((item: any) => ({ - ...getDefaultAgendaItem(), - ...item - })) || [getDefaultAgendaItem()]; + const { myVariable } = useMyVariable(); - const [agendaItems, setAgendaItems] = useState(initialAgendaItems); + // Track current `meeting_id` in local state so we only + // re‐initialize agenda items when the ID changes. + const [localMeetingId, setLocalMeetingId] = useState(myVariable.summary?.meeting_id || null); - useEffect(() => { - onUpdate(agendaItems); - //console.log("agendaItems", agendaItems) - }, [agendaItems]); + // Run `filterFormData` once for our initial agenda items: + const initialFilteredData = myVariable.summary + ? filterFormData(myVariable.summary) + : {}; - useEffect(() => { - const agendaItemsFromVariable = formData.agendaItems?.map((item: any) => ({ + const [agendaItems, setAgendaItems] = useState( + initialFilteredData.agendaItems?.map((item: any) => ({ ...getDefaultAgendaItem(), ...item - })) || [getDefaultAgendaItem()]; + })) || [getDefaultAgendaItem()] + ); - setAgendaItems(agendaItemsFromVariable); - }, [myVariable.summary?.agendaItems]); + /** + * If the meeting_id in context changes (meaning user selected + * a new summary or the backend assigned a new ID), + * then we re-run `filterFormData` and re‐init local state. + */ + useEffect(() => { + const currentId = myVariable.summary?.meeting_id; + if (currentId && currentId !== localMeetingId) { + const newFiltered = filterFormData(myVariable.summary); + const newAgendaItems = newFiltered.agendaItems?.map((item: any) => ({ + ...getDefaultAgendaItem(), + ...item + })) || [getDefaultAgendaItem()]; + setAgendaItems(newAgendaItems); + setLocalMeetingId(currentId); + } + }, [myVariable.summary?.meeting_id, localMeetingId]); + + /** + * Notify the parent component (`SummaryTemplate`) whenever + * local `agendaItems` change. + */ + useEffect(() => { + onUpdate(agendaItems); + }, [agendaItems, onUpdate]); + + // Utility to add an entire new "agenda item" section const addAgendaItem = () => { - setAgendaItems([...agendaItems, { agenda: "", status: "carry over", townHallUpdates: "", narrative: "", gameRules: "", leaderboard: [""], issues: [""], actionItems: [{ text: "", assignee: "", dueDate: "", status: "todo" }], decisionItems: [{ decision: "", rationale: "", opposing: "", effect: "" }], discussionPoints: [""], learningPoints: [""] }]); + setAgendaItems((prev) => [ + ...prev, + { ...getDefaultAgendaItem() } + ]); }; + // Remove one entire "agenda item" section const removeAgendaItem = (index: number) => { - const newAgendaItems = [...agendaItems]; - newAgendaItems.splice(index, 1); - setAgendaItems(newAgendaItems); + setAgendaItems((prev) => { + const updated = [...prev]; + updated.splice(index, 1); + return updated; + }); }; + // Add a sub‐item (actionItems, discussionPoints, etc.) const addItem = (type: string, agendaIndex: number) => { - const newAgendaItems: any = [...agendaItems]; - if (type === 'actionItems') { - newAgendaItems[agendaIndex][type].push({ text: "", assignee: "", dueDate: "", status: "todo" }); - } else if (type === 'decisionItems') { - newAgendaItems[agendaIndex][type].push({ decision: "", rationale: "", opposing: "", effect: "" }); - } else { - newAgendaItems[agendaIndex][type].push(""); - } - setAgendaItems(newAgendaItems); + setAgendaItems((prev) => { + const updated = [...prev]; + if (type === 'actionItems') { + updated[agendaIndex][type].push({ + text: "", + assignee: "", + dueDate: "", + status: "todo" + }); + } else if (type === 'decisionItems') { + updated[agendaIndex][type].push({ + decision: "", + rationale: "", + opposing: "", + effect: "" + }); + } else { + // e.g. discussionPoints, issues, meetingTopics, leaderboard + updated[agendaIndex][type].push(""); + } + return updated; + }); }; - + // Remove a sub‐item const removeItem = (type: string, agendaIndex: number, itemIndex: number) => { - const newAgendaItems: any = [...agendaItems]; - newAgendaItems[agendaIndex][type].splice(itemIndex, 1); - setAgendaItems(newAgendaItems); + setAgendaItems((prev) => { + const updated = [...prev]; + updated[agendaIndex][type].splice(itemIndex, 1); + return updated; + }); }; - const handleItemUpdate = (type: any, agendaIdx: any, itemIdx: any, updatedItem: any) => { - setAgendaItems((prevAgendaItems: any) => { - const newAgendaItems = JSON.parse(JSON.stringify(prevAgendaItems)); + // Update a sub‐item (like one actionItem, or one discussionPoint, etc.) + const handleItemUpdate = ( + type: string, + agendaIdx: number, + itemIdx: number, + updatedItem: any + ) => { + setAgendaItems((prev) => { + const cloned = JSON.parse(JSON.stringify(prev)); - // Check if the type is 'narrative' - if (type === 'townHallUpdates' || type === 'narrative' || type === 'gameRules' || type === 'townHallSummary' || type === 'discussion') { - if (newAgendaItems[agendaIdx]) { - newAgendaItems[agendaIdx][type] = updatedItem[type]; // Directly set the narrative or gameRules string - } + // Some fields (narrative, discussion, etc.) are single text fields + // stored directly rather than in an array. + if ( + type === 'townHallUpdates' || + type === 'narrative' || + type === 'gameRules' || + type === 'townHallSummary' || + type === 'discussion' + ) { + cloned[agendaIdx][type] = updatedItem[type]; } else { - // Handle other types as before - if (newAgendaItems[agendaIdx] && newAgendaItems[agendaIdx][type]) { - newAgendaItems[agendaIdx][type][itemIdx] = updatedItem; - } + // Arrays: discussionPoints, actionItems, etc. + cloned[agendaIdx][type][itemIdx] = updatedItem; } - - return newAgendaItems; + return cloned; }); }; - - const getHeading = (itemType: any, workgroup: any) => { - switch (itemType) { - // Only check this section when changing or adding new workgroup - case "issues": - if (workgroup === "Onboarding Workgroup") return "To carry over for next meeting"; - if (workgroup === "AI Sandbox/Think-tank") return "To carry over for next meeting"; - if (workgroup === "WG Sync Call") return "To carry over for next meeting"; - // Add more specific conditions for "issues" if needed - return "Issues"; // Default for "issues" - - case "discussionPoints": - if (workgroup === "Onboarding Workgroup") return "In this meeting we discussed"; - if (workgroup === "AI Sandbox/Think-tank") return "In this meeting we discussed"; - return "Discussion Points"; - - case "meetingTopics": - if (workgroup === "Research and Development Guild") return "Agenda Items"; - if (workgroup === "Education Workgroup") return "In this meeting we discussed"; - if (workgroup === "WG Sync Call") return "Agenda Items"; - return "Meeting Topics"; - // Add cases for other item types as needed - - default: - return "Items"; - } - } - + // You have your "template" logic to show/hide sections: const itemTypesConfig = [ { type: "townHallUpdates", - isEnabled: (template: any) => template?.townHallUpdates === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.townHallUpdates === 1, + render: (item: any, agendaIndex: number) => ( <>

Town Hall Updates from this meeting

@@ -119,7 +145,9 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { item={item.townHallUpdates} agendaIndex={agendaIndex} itemIndex={0} - onUpdate={(agendaIdx: any, itemIdx: any, updatedItem: any) => handleItemUpdate('townHallUpdates', agendaIdx, itemIdx, updatedItem)} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("townHallUpdates", aIdx, iIdx, updated) + } onRemove={removeItem} />
@@ -128,8 +156,8 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { }, { type: "narrative", - isEnabled: (template: any) => template?.narrative === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.narrative === 1, + render: (item: any, agendaIndex: number) => ( <>

Narrative

@@ -138,7 +166,9 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { item={item.narrative} agendaIndex={agendaIndex} itemIndex={0} - onUpdate={(agendaIdx: any, itemIdx: any, updatedItem: any) => handleItemUpdate('narrative', agendaIdx, itemIdx, updatedItem)} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("narrative", aIdx, iIdx, updated) + } onRemove={removeItem} />
@@ -147,8 +177,8 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { }, { type: "discussion", - isEnabled: (template: any) => template?.discussion === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.discussion === 1, + render: (item: any, agendaIndex: number) => ( <>

Discussion

@@ -157,7 +187,9 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { item={item.discussion} agendaIndex={agendaIndex} itemIndex={0} - onUpdate={(agendaIdx: any, itemIdx: any, updatedItem: any) => handleItemUpdate('discussion', agendaIdx, itemIdx, updatedItem)} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("discussion", aIdx, iIdx, updated) + } onRemove={removeItem} />
@@ -166,96 +198,128 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { }, { type: "meetingTopics", - isEnabled: (template: any) => template?.meetingTopics === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.meetingTopics === 1, + render: (item: any, agendaIndex: number) => ( <> -

{getHeading("meetingTopics", myVariable.workgroup?.preferred_template?.workgroup)}

+

Meeting Topics

- {item.meetingTopics.map((point: any, pointIndex: any) => ( + {item.meetingTopics.map((topic: any, idx: number) => ( handleItemUpdate('meetingTopics', agendaIdx, itemIdx, updatedItem)} + itemIndex={idx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("meetingTopics", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))}
- + ) }, { type: "discussionPoints", - isEnabled: (template: any) => template?.discussionPoints === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.discussionPoints === 1, + render: (item: any, agendaIndex: number) => ( <> -

{getHeading("discussionPoints", myVariable.workgroup?.preferred_template?.workgroup)}

+

Discussion Points

- {item.discussionPoints.map((point: any, pointIndex: any) => ( + {item.discussionPoints?.map((pt: any, idx: number) => ( handleItemUpdate('discussionPoints', agendaIdx, itemIdx, updatedItem)} + itemIndex={idx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("discussionPoints", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))}
- + ) }, { type: "actionItems", - isEnabled: (template: any) => template?.actionItems === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.actionItems === 1, + render: (item: any, agendaIndex: number) => ( <>

Action items

- {item?.actionItems?.map((action: any, actionIndex: any) => ( + {item.actionItems?.map((action: any, actionIdx: number) => ( handleItemUpdate('actionItems', agendaIdx, itemIdx, updatedItem)} + itemIndex={actionIdx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("actionItems", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))} - + ) }, { type: "decisionItems", - isEnabled: (template: any) => template?.decisionItems === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.decisionItems === 1, + render: (item: any, agendaIndex: number) => ( <>

Decisions

- {item?.decisionItems?.map((decision: any, decisionIndex: any) => ( + {item.decisionItems?.map((dec: any, decIdx: number) => ( handleItemUpdate('decisionItems', agendaIdx, itemIdx, updatedItem)} + itemIndex={decIdx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("decisionItems", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))} - + ) }, { type: "gameRules", - isEnabled: (template: any) => template?.gameRules === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.gameRules === 1, + render: (item: any, agendaIndex: number) => ( <>

Game Rules

@@ -264,7 +328,9 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { item={item.gameRules} agendaIndex={agendaIndex} itemIndex={0} - onUpdate={(agendaIdx: any, itemIdx: any, updatedItem: any) => handleItemUpdate('gameRules', agendaIdx, itemIdx, updatedItem)} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("gameRules", aIdx, iIdx, updated) + } onRemove={removeItem} />
@@ -273,8 +339,8 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { }, { type: "townHallSummary", - isEnabled: (template: any) => template?.townHallSummary === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.townHallSummary === 1, + render: (item: any, agendaIndex: number) => ( <>

Town Hall Summary (Optional)

@@ -283,7 +349,9 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { item={item.townHallSummary} agendaIndex={agendaIndex} itemIndex={0} - onUpdate={(agendaIdx: any, itemIdx: any, updatedItem: any) => handleItemUpdate('townHallSummary', agendaIdx, itemIdx, updatedItem)} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("townHallSummary", aIdx, iIdx, updated) + } onRemove={removeItem} />
@@ -292,125 +360,138 @@ const SummaryAgendaItems = ({ onUpdate }: any) => { }, { type: "leaderboard", - isEnabled: (template: any) => template?.leaderboard === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.leaderboard === 1, + render: (item: any, agendaIndex: number) => ( <>

Leaderboard

- {item.leaderboard.map((point: any, pointIndex: any) => ( + {item.leaderboard.map((lb: any, lbIdx: number) => ( handleItemUpdate('leaderboard', agendaIdx, itemIdx, updatedItem)} + itemIndex={lbIdx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("leaderboard", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))}
- + ) }, { type: "learningPoints", - isEnabled: (template: any) => template?.learningPoints === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.learningPoints === 1, + render: (item: any, agendaIndex: number) => ( <>

Learning Points

- {item.learningPoints.map((point: any, pointIndex: any) => ( + {item.learningPoints.map((lp: any, lpIdx: number) => ( handleItemUpdate('learningPoints', agendaIdx, itemIdx, updatedItem)} + itemIndex={lpIdx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("learningPoints", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))}
- + ) }, { type: "issues", - isEnabled: (template: any) => template?.issues === 1, - render: (item: any, agendaIndex: any) => ( + isEnabled: (t: any) => t?.issues === 1, + render: (item: any, agendaIndex: number) => ( <> -

{getHeading("issues", myVariable.workgroup?.preferred_template?.workgroup)}

+

Issues

- {item?.issues?.map((issue: any, issueIndex: any) => ( + {item.issues?.map((issue: any, issueIdx: number) => ( handleItemUpdate('issues', agendaIdx, itemIdx, updatedItem)} + itemIndex={issueIdx} + onUpdate={(aIdx, iIdx, updated) => + handleItemUpdate("issues", aIdx, iIdx, updated) + } onRemove={removeItem} /> ))} - +
) }, - /*{ - type: "issues", - isEnabled: (template: any) => template.issues === 1, - render: (item: any, agendaIndex: any) => ( - // ... rendering logic for issues ... - ) - },*/ ]; + // If the user’s workgroup is in `agendaItemOrder`, reorder itemTypes accordingly + const orderMapping = myVariable?.agendaItemOrder; type WorkgroupKey = keyof typeof orderMapping; - - const orderMapping = myVariable?.agendaItemOrder; - - const reorderItemTypesConfig = (orderKey: WorkgroupKey) => { + if ( + typeof myVariable.workgroup?.workgroup === 'string' && + myVariable.workgroup?.workgroup in orderMapping + ) { + const orderKey = myVariable.workgroup.workgroup as WorkgroupKey; const order = orderMapping[orderKey]; - if (!order) { - console.warn("Order key not found in mapping"); - return; + if (order) { + itemTypesConfig.sort((a, b) => { + const indexA = order.indexOf(a.type); + const indexB = order.indexOf(b.type); + if (indexA === -1 && indexB === -1) return 0; + if (indexA === -1) return 1; + if (indexB === -1) return -1; + return indexA - indexB; + }); } - - itemTypesConfig.sort((a, b) => { - const indexA = order.indexOf(a.type); - const indexB = order.indexOf(b.type); - - if (indexA === -1 && indexB === -1) return 0; // Both types are not in the order list, keep their relative order - if (indexA === -1) return 1; // Type A is not in the list, move it towards the end - if (indexB === -1) return -1; // Type B is not in the list, move it towards the end - - return indexA - indexB; - }); - - }; - - if (typeof myVariable.workgroup?.workgroup === 'string' && myVariable.workgroup?.workgroup in orderMapping) { - reorderItemTypesConfig(myVariable.workgroup?.workgroup as WorkgroupKey); } else { - console.warn("Invalid workgroup key"); + console.warn("Invalid or unmapped workgroup key - using default item order."); } return (
- {myVariable.workgroup?.preferred_template?.agendaItems[0]?.agenda == 1 && (

Agenda Items

)} - {agendaItems.map((item: any, agendaIndex: any) => ( + {myVariable.workgroup?.preferred_template?.agendaItems[0]?.agenda === 1 && ( +

Agenda Items

+ )} + + {agendaItems.map((item: any, agendaIndex: number) => (
- {myVariable.workgroup?.preferred_template?.agendaItems[0]?.agenda == 1 && ( + + {/* If your template says "agenda=1", show agenda title + status */} + {myVariable.workgroup?.preferred_template?.agendaItems[0]?.agenda === 1 && ( <>

Agenda item {agendaIndex + 1}

- + { value={item.agenda} autoComplete="off" onChange={(e) => { - const newAgenda = [...agendaItems]; - newAgenda[agendaIndex].agenda = e.target.value; - setAgendaItems(newAgenda); + const updated = [...agendaItems]; + updated[agendaIndex].agenda = e.target.value; + setAgendaItems(updated); }} />
- + - )} + {myVariable.workgroup?.preferred_template?.meetingInfo?.date == 1 && ( + <> + + + + )}
+
+ {/* Facilitator */} {myVariable.workgroup?.preferred_template?.meetingInfo?.host == 1 && ( -
- - handleSelection('host', selectedNames)} - initialValue={meetingInfo.host || ""} - /> -
)} +
+ + handleSelection('host', selected)} + initialValue={meetingInfo.host || ""} + /> +
+ )} + + {/* Documenter */} {myVariable.workgroup?.preferred_template?.meetingInfo?.documenter == 1 && ( -
- - handleSelection('documenter', selectedNames)} - initialValue={meetingInfo.documenter || ""} - /> -
)} +
+ + handleSelection('documenter', selected)} + initialValue={meetingInfo.documenter || ""} + /> +
+ )} + + {/* Translator */} {myVariable.workgroup?.preferred_template?.meetingInfo?.translator == 1 && ( -
- - handleSelection('translator', selectedNames)} - initialValue={meetingInfo.translator || ""} - /> -
)} +
+ + handleSelection('translator', selected)} + initialValue={meetingInfo.translator || ""} + /> +
+ )}
+ + {/* People present */}
-
- {myVariable.workgroup?.preferred_template?.meetingInfo?.peoplePresent == 1 && (<> - - handleSelection('peoplePresent', selectedNames)} - initialValue={meetingInfo.peoplePresent || ""} - /> - )} + {myVariable.workgroup?.preferred_template?.meetingInfo?.peoplePresent == 1 && ( +
+ + handleSelection('peoplePresent', selected)} + initialValue={meetingInfo.peoplePresent || ""} + /> +
+ )}
+ + {/* Right‐side links column */} +
+ {myVariable.workgroup?.preferred_template?.meetingInfo?.purpose == 1 && ( + <> + +