From 246ef8647fda8add39653ff68e14bc88c55f0ca4 Mon Sep 17 00:00:00 2001 From: cawcenter Date: Sat, 13 Dec 2025 19:48:53 -0500 Subject: [PATCH] Your commit message here --- .../AvatarIntelligenceManager.tsx | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/frontend/src/components/admin/intelligence/AvatarIntelligenceManager.tsx b/frontend/src/components/admin/intelligence/AvatarIntelligenceManager.tsx index e69de29..4aa9963 100644 --- a/frontend/src/components/admin/intelligence/AvatarIntelligenceManager.tsx +++ b/frontend/src/components/admin/intelligence/AvatarIntelligenceManager.tsx @@ -0,0 +1,296 @@ +import React, { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { getDirectusClient, readItems, createItem, updateItem, deleteItem } from '@/lib/directus/client'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Users, Plus, Search, Edit2, Trash2, Zap, TrendingUp } from 'lucide-react'; +import { toast } from 'sonner'; +import { motion } from 'framer-motion'; + +interface Avatar { + id: string; + slug: string; + base_name: string; + wealth_cluster: string; + tech_stack: string[]; + identity_male: string; + identity_female?: string; + identity_neutral?: string; + pain_points?: string[]; + goals?: string[]; +} + +export default function AvatarIntelligenceManager() { + const queryClient = useQueryClient(); + const [search, setSearch] = useState(''); + const [isEditing, setIsEditing] = useState(null); + const client = getDirectusClient(); + + // 1. Fetch Avatars + const { data: avatars = [], isLoading } = useQuery({ + queryKey: ['avatar_intelligence'], + queryFn: async () => { + // @ts-ignore + return await client.request(readItems('avatar_intelligence', { + sort: ['base_name'], + limit: 100 + })); + } + }); + + // 2. Fetch Avatar Variants (for stats) + const { data: variants = [] } = useQuery({ + queryKey: ['avatar_variants'], + queryFn: async () => { + // @ts-ignore + return await client.request(readItems('avatar_variants', { + limit: -1 + })); + } + }); + + // 3. Calculate Stats + const getVariantCount = (avatarSlug: string) => { + return variants.filter((v: any) => v.avatar_key === avatarSlug).length; + }; + + // 4. Filter Logic + const filteredAvatars = avatars.filter((a: Avatar) => + a.base_name?.toLowerCase().includes(search.toLowerCase()) || + a.slug?.toLowerCase().includes(search.toLowerCase()) || + a.wealth_cluster?.toLowerCase().includes(search.toLowerCase()) + ); + + // 5. Delete Mutation + const deleteMutation = useMutation({ + mutationFn: async (id: string) => { + // @ts-ignore + await client.request(deleteItem('avatar_intelligence', id)); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['avatar_intelligence'] }); + toast.success('Avatar deleted successfully'); + }, + onError: (error: any) => { + toast.error(`Failed to delete: ${error.message}`); + } + }); + + const handleDelete = (id: string, name: string) => { + if (confirm(`Delete avatar "${name}"? This will also delete all variants.`)) { + deleteMutation.mutate(id); + } + }; + + if (isLoading) { + return ( +
+
+
+ ); + } + + return ( +
+ {/* Header Stats */} +
+ + + +
+

Total Avatars

+

{avatars.length}

+
+ +
+
+
+ + + + +
+

Total Variants

+

{variants.length}

+
+ +
+
+
+ + + + +
+

Avg Variants/Avatar

+

+ {avatars.length > 0 ? Math.round(variants.length / avatars.length) : 0} +

+
+ +
+
+
+ + + + +
+

Wealth Clusters

+

+ {new Set(avatars.map((a: Avatar) => a.wealth_cluster)).size} +

+
+ +
+
+
+
+ + {/* Toolbar */} +
+
+ + setSearch(e.target.value)} + className="pl-9 bg-zinc-950 border-zinc-800 text-white" + /> +
+ +
+ + {/* Grid Layout */} +
+ {filteredAvatars.map((avatar: Avatar, index: number) => ( + + + +
+ {avatar.base_name} + + {avatar.slug} + +
+ + {avatar.wealth_cluster} + +
+
+
+ + +
+
+ +
+ {/* Tech Stack */} + {avatar.tech_stack && avatar.tech_stack.length > 0 && ( +
+

Tech Stack

+
+ {avatar.tech_stack.slice(0, 3).map((tech: string) => ( + + {tech} + + ))} + {avatar.tech_stack.length > 3 && ( + + +{avatar.tech_stack.length - 3} + + )} +
+
+ )} + + {/* Identity */} +
+

Primary Identity

+

{avatar.identity_male || 'Not set'}

+
+ + {/* Variants Count */} +
+
+ {getVariantCount(avatar.slug)} variants +
+ +
+ + {/* Send to Engine */} + +
+
+
+
+ ))} +
+ + {/* Empty State */} + {filteredAvatars.length === 0 && ( +
+ +

No avatars found

+

+ {search ? 'Try a different search term' : 'Create your first avatar to get started'} +

+ {!search && ( + + )} +
+ )} +
+ ); +}