ppt-tool/frontend/app/admin/clients/page.tsx
Vadym Samoilenko 5def8f9e84 Phase 7: Apply design system to all admin pages + fix test stubs
Frontend — consistent HSL token usage across remaining pages:
- Users: shared Card, Badge with success/error tokens, h2 typography, animate-fadeIn
- Audit: shared Card, muted-foreground text, animate-fadeIn
- Clients: shared Card, Badge active/inactive, hsl(--primary) icon color
- Storage: shared Card, StatusBadge for status pills, hsl warning/primary
  bars replacing hardcoded amber/blue, all gray text → muted-foreground
- Login: hsl(--surface) bg, hsl(--primary) submit button, brand mark icon,
  animate-scaleIn card entry, hsl(--warning) dev notice

Backend tests — convert print-only stubs to real assertions:
- test_pptx_creator: mkdir, deterministic save path, assert file exists + slide count
- test_gemini_schema_support: direct google.genai client, skipif guard on
  GOOGLE_API_KEY, JSON parse + Pydantic model validation assertions
- test_openai_schema_support: clean skip (OpenAI removed in Phase 6)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 19:01:52 +00:00

95 lines
3.6 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { AppDispatch, RootState } from '@/store/store';
import { fetchClients } from '@/store/slices/adminSlice';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/shared/Card';
import { Plus, Building2 } from 'lucide-react';
import Link from 'next/link';
import CreateClientDialog from '../components/CreateClientDialog';
export default function ClientsPage() {
const dispatch = useDispatch<AppDispatch>();
const { clients, isLoading } = useSelector((state: RootState) => state.admin);
const user = useSelector((state: RootState) => state.auth.user);
const [createOpen, setCreateOpen] = useState(false);
useEffect(() => {
dispatch(fetchClients());
}, [dispatch]);
if (isLoading) {
return (
<div className="space-y-4 animate-fadeIn">
<h1 className="h2">Clients</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="h-36 bg-[hsl(var(--surface-hover))] rounded-lg animate-pulse" />
))}
</div>
</div>
);
}
return (
<div className="space-y-4 animate-fadeIn">
<div className="flex items-center justify-between">
<h1 className="h2">Clients</h1>
{user?.role === 'super_admin' && (
<Button onClick={() => setCreateOpen(true)}>
<Plus className="w-4 h-4 mr-1" />
New Client
</Button>
)}
</div>
{clients.length === 0 ? (
<div className="text-center py-12 text-muted-foreground">
<Building2 className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p>No clients yet.</p>
{user?.role === 'super_admin' && (
<p className="text-sm mt-1">Create your first client to get started.</p>
)}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{clients.map((client) => (
<Link key={client.id} href={`/admin/clients/${client.id}`}>
<Card hover className="p-5">
<CardHeader className="mb-2 pb-0">
<CardTitle className="text-lg flex items-center gap-2">
<Building2 className="w-5 h-5 text-[hsl(var(--primary))]" />
{client.name}
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>/{client.slug}</span>
<Badge
className={
client.is_active
? 'bg-[hsl(var(--success)/0.1)] text-[hsl(var(--success))] border-[hsl(var(--success)/0.2)]'
: 'bg-[hsl(var(--error)/0.1)] text-[hsl(var(--error))] border-[hsl(var(--error)/0.2)]'
}
variant="outline"
>
{client.is_active ? 'Active' : 'Inactive'}
</Badge>
</div>
<p className="text-xs text-muted-foreground mt-2">
Policy: {client.review_policy}
</p>
</CardContent>
</Card>
</Link>
))}
</div>
)}
<CreateClientDialog open={createOpen} onOpenChange={setCreateOpen} />
</div>
);
}