diff --git a/src/components/focus-group-session/DiscussionGuideViewer.tsx b/src/components/focus-group-session/DiscussionGuideViewer.tsx index 2e4466ce..baf10d22 100644 --- a/src/components/focus-group-session/DiscussionGuideViewer.tsx +++ b/src/components/focus-group-session/DiscussionGuideViewer.tsx @@ -342,6 +342,19 @@ const DiscussionGuideViewer: React.FC = React.memo(( const [editingSubsectionId, setEditingSubsectionId] = useState(null); const [tempSubsectionTitle, setTempSubsectionTitle] = useState(''); + // State for item-level inline editing + interface EditingItemState { + sectionId: string; + subsectionId?: string | null; + itemId: string; + itemType: 'question' | 'activity'; + } + const [editingItem, setEditingItem] = useState(null); + const [editingItemContent, setEditingItemContent] = useState(''); + const [editingItemProbes, setEditingItemProbes] = useState([]); + const [editingItemTimeLimit, setEditingItemTimeLimit] = useState(undefined); + const [isSavingItem, setIsSavingItem] = useState(false); + // Drag and drop state const [activeId, setActiveId] = useState(null); @@ -351,12 +364,12 @@ const DiscussionGuideViewer: React.FC = React.memo(( useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ); - // Notify parent about editing state + // Notify parent about editing state (section or item level) useEffect(() => { if (onEditingChange) { - onEditingChange(!!editingSectionId); + onEditingChange(!!editingSectionId || !!editingItem); } - }, [editingSectionId, onEditingChange]); + }, [editingSectionId, editingItem, onEditingChange]); // Sync editing section with main guide when it changes useEffect(() => { @@ -370,6 +383,11 @@ const DiscussionGuideViewer: React.FC = React.memo(( // Functions for inline section editing const startEditingSection = (section: DiscussionGuideSection) => { + // Prevent section editing if item is being edited + if (editingItem) { + toast.info('Please save or cancel item editing first'); + return; + } setEditingSectionId(section.id); setEditingSection({ ...section }); // Ensure the section is open when editing starts @@ -381,6 +399,95 @@ const DiscussionGuideViewer: React.FC = React.memo(( setEditingSection(null); }; + // Functions for item-level inline editing + const startEditingItem = useCallback(( + sectionId: string, + itemId: string, + itemType: 'question' | 'activity', + item: DiscussionGuideItem, + subsectionId?: string | null + ) => { + // Prevent item editing if section is being edited + if (editingSectionId) { + toast.info('Please save or cancel section editing first'); + return; + } + setEditingItem({ sectionId, subsectionId, itemId, itemType }); + setEditingItemContent(item.content); + setEditingItemProbes(item.probes || []); + setEditingItemTimeLimit(item.time_limit); + }, [editingSectionId]); + + const cancelEditingItem = useCallback(() => { + setEditingItem(null); + setEditingItemContent(''); + setEditingItemProbes([]); + setEditingItemTimeLimit(undefined); + }, []); + + const saveEditingItem = useCallback(async () => { + if (!editingItem || !structuredGuide || !onSave) return; + + setIsSavingItem(true); + try { + const updatedGuide = { + ...structuredGuide, + sections: structuredGuide.sections.map(section => { + if (section.id !== editingItem.sectionId) return section; + + const updateItem = (item: DiscussionGuideItem): DiscussionGuideItem => { + if (item.id !== editingItem.itemId) return item; + return { + ...item, + content: editingItemContent, + probes: editingItem.itemType === 'question' ? editingItemProbes : item.probes, + time_limit: editingItemTimeLimit + }; + }; + + if (editingItem.subsectionId) { + // Update in subsection + return { + ...section, + subsections: section.subsections?.map(sub => { + if (sub.id !== editingItem.subsectionId) return sub; + return { + ...sub, + questions: editingItem.itemType === 'question' + ? sub.questions?.map(updateItem) + : sub.questions, + activities: editingItem.itemType === 'activity' + ? sub.activities?.map(updateItem) + : sub.activities + }; + }) + }; + } + + // Update at section level + return { + ...section, + questions: editingItem.itemType === 'question' + ? section.questions?.map(updateItem) + : section.questions, + activities: editingItem.itemType === 'activity' + ? section.activities?.map(updateItem) + : section.activities + }; + }) + }; + + await onSave(updatedGuide); + cancelEditingItem(); + toast.success('Item updated successfully'); + } catch (error) { + console.error('Error saving item:', error); + toast.error('Failed to save item'); + } finally { + setIsSavingItem(false); + } + }, [editingItem, editingItemContent, editingItemProbes, editingItemTimeLimit, structuredGuide, onSave, cancelEditingItem]); + const updateEditingSection = useCallback((updates: Partial) => { setEditingSection(prevSection => { if (!prevSection) return prevSection; @@ -1040,12 +1147,136 @@ const DiscussionGuideViewer: React.FC = React.memo(( ); } + // Check if this specific item is being edited (item-level editing) + const subsectionId = subsectionIndex !== undefined ? section?.subsections?.[subsectionIndex]?.id : null; + const isItemEditing = editingItem?.itemId === item.id && + editingItem?.sectionId === section?.id && + editingItem?.subsectionId === subsectionId; + + // Render item-level inline edit mode + if (isItemEditing) { + return ( +
+
+ +
+ +
+ {/* Badge row with time limit editor */} +
+ + {itemType === 'activity' ? ( + <> + + {typeof item.type === 'string' ? item.type.replace('_', ' ') : String(item.type || 'unknown')} + + ) : ( + <> + + {typeof item.type === 'string' ? item.type.replace('_', ' ') : String(item.type || 'unknown')} + + )} + + +
+ + setEditingItemTimeLimit(parseInt(e.target.value) || undefined)} + className="w-16 h-6 text-xs" + placeholder="min" + min={1} + /> + min +
+
+ + {/* Content textarea */} +