Files
net/frontend/src/pages/admin/collections/avatar-variants.astro
cawcenter 0f498e5386 feat: Add 10 collection management pages with Titanium Pro design
- Avatar Variants: Gender/tone variation management
- Campaign Masters: Marketing campaign overview
- Cartesian Patterns: Content template formulas
- Content Fragments: Reusable content blocks
- Generation Jobs: Queue monitoring with progress bars
- Geo Intelligence: Location targeting by state
- Headline Inventory: Spintax headline library
- Offer Blocks: CTA templates with pain points
- Spintax Dictionaries: Word variation sets
- Leads: Updated with stats and Titanium Pro styling

All pages include:
- Import/export functionality
- Usage statistics
- Titanium Pro design system
- Real-time Directus API integration
2025-12-13 13:00:44 -05:00

149 lines
5.1 KiB
Plaintext

---
/**
* Avatar Variants Management
* Full CRUD for avatar_variants collection
*/
import AdminLayout from '@/layouts/AdminLayout.astro';
import { getDirectusClient } from '@/lib/directus/client';
import { readItems } from '@directus/sdk';
const client = getDirectusClient();
let items = [];
let error = null;
let stats = {
total: 0,
male: 0,
female: 0,
neutral: 0,
};
try {
items = await client.request(readItems('avatar_variants', {
fields: ['*'],
sort: ['avatar_key', 'variant_type'],
}));
stats.total = items.length;
stats.male = items.filter((i: any) => i.variant_type === 'male').length;
stats.female = items.filter((i: any) => i.variant_type === 'female').length;
stats.neutral = items.filter((i: any) => i.variant_type === 'neutral').length;
} catch (e) {
console.error('Error fetching avatar variants:', e);
error = e instanceof Error ? e.message : 'Unknown error';
}
---
<AdminLayout title="Avatar Variants">
<div class="space-y-6">
<!-- Header -->
<div class="flex justify-between items-center">
<div>
<h1 class="spark-heading text-3xl">🎭 Avatar Variants</h1>
<p class="text-silver mt-1">Manage gender and tone variations</p>
</div>
<div class="flex gap-3">
<button class="spark-btn-secondary text-sm" onclick="window.dispatchEvent(new CustomEvent('import-modal'))">
📥 Import
</button>
<button class="spark-btn-secondary text-sm" onclick="window.dispatchEvent(new CustomEvent('export-data', {detail: {collection: 'avatar_variants'}}))">
📤 Export
</button>
<a href="/admin/collections/avatar-variants/new" class="spark-btn-primary text-sm">
✨ New Variant
</a>
</div>
</div>
{error && (
<div class="spark-card p-4 border-red-500 text-red-400">
<strong>Error:</strong> {error}
</div>
)}
<!-- Stats -->
<div class="grid grid-cols-4 gap-4">
<div class="spark-card p-6">
<div class="spark-label mb-2">Total Variants</div>
<div class="spark-data text-3xl">{stats.total}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Male</div>
<div class="spark-data text-3xl text-blue-400">{stats.male}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Female</div>
<div class="spark-data text-3xl text-pink-400">{stats.female}</div>
</div>
<div class="spark-card p-6">
<div class="spark-label mb-2">Neutral</div>
<div class="spark-data text-3xl text-purple-400">{stats.neutral}</div>
</div>
</div>
<!-- Variants Table -->
<div class="spark-card overflow-hidden">
<div class="p-6 border-b border-edge-subtle">
<h2 class="text-white font-semibold">All Variants</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-graphite">
<tr>
<th class="text-left px-6 py-3 spark-label">Avatar</th>
<th class="text-left px-6 py-3 spark-label">Type</th>
<th class="text-left px-6 py-3 spark-label">Pronouns</th>
<th class="text-left px-6 py-3 spark-label">Identity</th>
<th class="text-right px-6 py-3 spark-label">Actions</th>
</tr>
</thead>
<tbody>
{items.map((variant: any, index: number) => (
<tr class={index % 2 === 0 ? 'bg-black/20' : ''}>
<td class="px-6 py-4 text-white">{variant.avatar_key}</td>
<td class="px-6 py-4">
<span class={`px-2 py-1 rounded text-xs ${
variant.variant_type === 'male' ? 'bg-blue-500/20 text-blue-400' :
variant.variant_type === 'female' ? 'bg-pink-500/20 text-pink-400' :
'bg-purple-500/20 text-purple-400'
}`}>
{variant.variant_type}
</span>
</td>
<td class="px-6 py-4 text-silver">{variant.pronoun}</td>
<td class="px-6 py-4 text-silver">{variant.identity}</td>
<td class="px-6 py-4 text-right">
<a href={`/admin/collections/avatar-variants/${variant.id}`} class="spark-btn-ghost text-xs px-3 py-1">
Edit
</a>
</td>
</tr>
))}
</tbody>
</table>
{items.length === 0 && !error && (
<div class="p-12 text-center">
<p class="text-silver/50">No variants found. Create your first one!</p>
</div>
)}
</div>
</div>
</div>
</AdminLayout>
<script>
// Handle export
window.addEventListener('export-data', async (e: any) => {
const { collection } = e.detail;
const response = await fetch(`/api/collections/${collection}/export`);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${collection}-${new Date().toISOString().split('T')[0]}.json`;
a.click();
});
</script>