diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4466d3d..f4399f8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,12 +1,17 @@ { "permissions": { "allow": [ - "Bash(wc:*)" + "Bash(wc:*)", + "Bash(find:*)", + "Bash(for f in /Users/jjbrunton/Projects/Sauci/apps/supabase/migrations/*.sql)", + "Bash(do grep -q \"deleted_at\" \"$f\")", + "Bash(echo:*)", + "Bash(done)" ] }, + "enableAllProjectMcpServers": true, "enabledMcpjsonServers": [ "sauci-prod", "sauci-non-prod" - ], - "enableAllProjectMcpServers": true + ] } diff --git a/apps/admin/src/pages/content/QuestionAnalyticsPage.tsx b/apps/admin/src/pages/content/QuestionAnalyticsPage.tsx index 804f40a..46961aa 100644 --- a/apps/admin/src/pages/content/QuestionAnalyticsPage.tsx +++ b/apps/admin/src/pages/content/QuestionAnalyticsPage.tsx @@ -5,7 +5,7 @@ import { Skeleton } from '@/components/ui/skeleton'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis, Tooltip, Cell, Pie, PieChart as RechartsPieChart } from 'recharts'; -import { Crown, MessageCircle, PieChart, Sparkles, Tag } from 'lucide-react'; +import { Crown, MessageCircle, PieChart, Sparkles, Tag, Eye, EyeOff } from 'lucide-react'; interface QuestionRow { id: string; @@ -19,6 +19,7 @@ interface PackRow { name: string; is_premium: boolean; is_explicit: boolean; + is_public: boolean; category_id: string | null; category?: { name: string } | { name: string }[] | null; } @@ -75,8 +76,11 @@ const normalizeTopics = (topics?: PackTopicRow['topics']) => { return Array.isArray(topics) ? topics : [topics]; }; +type PublishFilter = 'all' | 'published' | 'unpublished'; + export function QuestionAnalyticsPage() { const [loading, setLoading] = useState(true); + const [publishFilter, setPublishFilter] = useState('published'); const [stats, setStats] = useState({ totalQuestions: 0, premiumQuestions: 0, @@ -141,10 +145,19 @@ export function QuestionAnalyticsPage() { const fetchAnalytics = async () => { setLoading(true); try { + // Build pack query with optional is_public filter + let packQuery = supabase + .from('question_packs') + .select('id, name, is_premium, is_explicit, is_public, category_id, category:categories(name)'); + + if (publishFilter === 'published') { + packQuery = packQuery.eq('is_public', true); + } else if (publishFilter === 'unpublished') { + packQuery = packQuery.eq('is_public', false); + } + const [packsResult, topicsResult, packTopicsResult, questions] = await Promise.all([ - supabase - .from('question_packs') - .select('id, name, is_premium, is_explicit, category_id, category:categories(name)'), + packQuery, supabase .from('topics') .select('id, name') @@ -351,7 +364,7 @@ export function QuestionAnalyticsPage() { }; fetchAnalytics(); - }, []); + }, [publishFilter]); const premiumPercent = formatPercent(stats.premiumQuestions, stats.totalQuestions); const freePercent = formatPercent(stats.freeQuestions, stats.totalQuestions); @@ -386,16 +399,44 @@ export function QuestionAnalyticsPage() { const categoryData = categoryBreakdown[categorySegment]; const packData = packBreakdown[packSegment]; + const publishFilterOptions: { key: PublishFilter; label: string; icon: typeof Eye }[] = [ + { key: 'all', label: 'All Packs', icon: Eye }, + { key: 'published', label: 'Published Only', icon: Eye }, + { key: 'unpublished', label: 'Unpublished Only', icon: EyeOff }, + ]; + return (
-
-

- - Question Analytics -

-

- Deep dive into premium coverage, tags, categories, and intensity distribution -

+
+
+

+ + Question Analytics +

+

+ Deep dive into premium coverage, tags, categories, and intensity distribution +

+
+
+

Scope

+
+ {publishFilterOptions.map((option) => { + const Icon = option.icon; + return ( + + ); + })} +
+