import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { supabase } from '@/config'; import { auditedSupabase } from '@/hooks/useAuditedSupabase'; import { useRealtimeSubscription } from '@/hooks/useRealtimeSubscription'; import { RealtimeStatusIndicator } from '@/components/RealtimeStatusIndicator'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Card } from '@/components/ui/card'; import { Plus, Pencil, Trash2, Sparkles, Loader2, Users } from 'lucide-react'; import { Checkbox } from '@/components/ui/checkbox'; import { toast } from 'sonner'; import { AiGeneratorDialog } from '@/components/ai/AiGeneratorDialog'; import { AIPolishButton } from '@/components/ai/AIPolishButton'; import { ReviewQuestionsDialog } from '@/components/content/ReviewQuestionsDialog'; interface QuestionPack { id: string; name: string; description: string | null; icon: string | null; is_explicit: boolean; } interface Question { id: string; pack_id: string; text: string; partner_text: string | null; intensity: number; allowed_couple_genders: string[] | null; target_user_genders: string[] | null; created_at: string | null; } import { INTENSITY_LEVELS } from '@/lib/openai'; // Helper to get intensity info by level (1-indexed) const getIntensity = (level: number) => INTENSITY_LEVELS[level - 1] || INTENSITY_LEVELS[0]; export function QuestionsPage() { const { packId } = useParams<{ packId: string }>(); const [pack, setPack] = useState(null); const [questions, setQuestions] = useState([]); const [loading, setLoading] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [aiDialogOpen, setAiDialogOpen] = useState(false); const [reviewOpen, setReviewOpen] = useState(false); const [editingQuestion, setEditingQuestion] = useState(null); const [saving, setSaving] = useState(false); const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkDeleting, setBulkDeleting] = useState(false); // Form state const [formData, setFormData] = useState<{ text: string; partner_text: string; intensity: number; allowed_couple_genders: string[]; target_user_genders: string[]; }>({ text: '', partner_text: '', intensity: 1, allowed_couple_genders: [], target_user_genders: [], }); const fetchData = useCallback(async () => { if (!packId) return; setLoading(true); try { // Fetch pack info const { data: packData } = await supabase .from('question_packs') .select('id, name, description, icon, is_explicit') .eq('id', packId) .single(); setPack(packData); // Fetch questions const { data: questionData, error } = await supabase .from('questions') .select('*') .eq('pack_id', packId) .order('created_at', { ascending: true }); if (error) throw error; setQuestions(questionData || []); } catch (error) { toast.error('Failed to load questions'); console.error(error); } finally { setLoading(false); } }, [packId]); // Real-time subscription for questions in this pack const { status: realtimeStatus } = useRealtimeSubscription({ table: 'questions', filter: packId ? `pack_id=eq.${packId}` : undefined, enabled: !!packId, onInsert: useCallback((newQuestion: Question) => { setQuestions(prev => [...prev, newQuestion]); }, []), onUpdate: useCallback(({ new: updated }: { old: Question; new: Question }) => { setQuestions(prev => prev.map(q => q.id === updated.id ? updated : q)); }, []), onDelete: useCallback((deleted: Question) => { setQuestions(prev => prev.filter(q => q.id !== deleted.id)); setSelectedIds(prev => { const next = new Set(prev); next.delete(deleted.id); return next; }); }, []), }); useEffect(() => { fetchData(); }, [fetchData]); const openCreateDialog = () => { setEditingQuestion(null); setFormData({ text: '', partner_text: '', intensity: 1, allowed_couple_genders: [], target_user_genders: [] }); setDialogOpen(true); }; const openEditDialog = (question: Question) => { setEditingQuestion(question); setFormData({ text: question.text, partner_text: question.partner_text || '', intensity: question.intensity, allowed_couple_genders: question.allowed_couple_genders || [], target_user_genders: question.target_user_genders || [], }); setDialogOpen(true); }; const handleSave = async () => { if (!formData.text.trim()) { toast.error('Question text is required'); return; } setSaving(true); try { if (editingQuestion) { const { error } = await auditedSupabase.update('questions', editingQuestion.id, { text: formData.text, partner_text: formData.partner_text || null, intensity: formData.intensity, allowed_couple_genders: formData.allowed_couple_genders.length > 0 ? formData.allowed_couple_genders : null, target_user_genders: formData.target_user_genders.length > 0 ? formData.target_user_genders : null, }); if (error) throw error; toast.success('Question updated'); } else { const { error } = await auditedSupabase.insert('questions', { pack_id: packId, text: formData.text, partner_text: formData.partner_text || null, intensity: formData.intensity, allowed_couple_genders: formData.allowed_couple_genders.length > 0 ? formData.allowed_couple_genders : null, target_user_genders: formData.target_user_genders.length > 0 ? formData.target_user_genders : null, }); if (error) throw error; toast.success('Question created'); } setDialogOpen(false); fetchData(); } catch (error) { toast.error('Failed to save question'); console.error(error); } finally { setSaving(false); } }; const handleDelete = async (question: Question) => { if (!confirm('Delete this question? This cannot be undone.')) { return; } try { const { error } = await auditedSupabase.delete('questions', question.id); if (error) throw error; toast.success('Question deleted'); fetchData(); } catch (error) { toast.error('Failed to delete question'); console.error(error); } }; const handleAiGenerated = (generatedQuestions: Array<{ text: string; partner_text?: string; intensity: number }>) => { // Bulk insert AI-generated questions const insertQuestions = async () => { try { const { error } = await auditedSupabase.insert( 'questions', generatedQuestions.map(q => ({ pack_id: packId, text: q.text, partner_text: q.partner_text || null, intensity: q.intensity, })) ); if (error) throw error; toast.success(`${generatedQuestions.length} questions added`); fetchData(); } catch (error) { toast.error('Failed to add questions'); console.error(error); } }; insertQuestions(); setAiDialogOpen(false); }; // Selection handlers const toggleSelection = (id: string) => { const next = new Set(selectedIds); if (next.has(id)) { next.delete(id); } else { next.add(id); } setSelectedIds(next); }; const toggleSelectAll = () => { if (selectedIds.size === questions.length) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(questions.map(q => q.id))); } }; const handleBulkDelete = async () => { if (selectedIds.size === 0) return; const count = selectedIds.size; if (!confirm(`Delete ${count} question${count !== 1 ? 's' : ''}? This cannot be undone.`)) { return; } setBulkDeleting(true); try { await Promise.all( Array.from(selectedIds).map(id => auditedSupabase.delete('questions', id)) ); toast.success(`${count} question${count !== 1 ? 's' : ''} deleted`); setSelectedIds(new Set()); fetchData(); } catch (error) { toast.error('Failed to delete questions'); console.error(error); } finally { setBulkDeleting(false); } }; if (loading) { return (
); } return (
{/* Header */}

{pack && ( <> {pack.icon} {pack.name} )}

{questions.length} question{questions.length !== 1 ? 's' : ''} in this pack

{editingQuestion ? 'Edit Question' : 'Add Question'} {editingQuestion ? 'Update the question details below.' : 'Add a new question to this pack.'}
setFormData(d => ({ ...d, text: val }))} />