220 lines
7.7 KiB
TypeScript
220 lines
7.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { useBrands, useDeleteBrand } from '@/hooks/useBrands';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { Plus, Search, Palette, Pencil, Trash2 } from 'lucide-react';
|
|
|
|
export default function BrandsPage() {
|
|
const [search, setSearch] = useState('');
|
|
const [page, setPage] = useState(1);
|
|
|
|
const { data, isLoading } = useBrands({ search, page, limit: 12 });
|
|
const deleteBrand = useDeleteBrand();
|
|
|
|
const handleDelete = (id: string) => {
|
|
if (confirm('¿Está seguro de eliminar esta marca?')) {
|
|
deleteBrand.mutate(id);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Marcas</h1>
|
|
<p className="text-muted-foreground">
|
|
Gestiona las marcas de tus clientes
|
|
</p>
|
|
</div>
|
|
<Link to="/crm/brands/new">
|
|
<Button>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
Nueva Marca
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Search */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="relative flex-1 max-w-sm">
|
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Buscar marcas..."
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="pl-9"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Grid */}
|
|
{isLoading ? (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{[...Array(6)].map((_, i) => (
|
|
<Card key={i} className="animate-pulse">
|
|
<CardHeader>
|
|
<div className="h-6 bg-muted rounded w-2/3" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="h-4 bg-muted rounded w-full" />
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
) : data?.data?.length === 0 ? (
|
|
<Card>
|
|
<CardContent className="flex flex-col items-center justify-center py-12">
|
|
<Palette className="h-12 w-12 text-muted-foreground mb-4" />
|
|
<h3 className="text-lg font-semibold">No hay marcas</h3>
|
|
<p className="text-muted-foreground mb-4">
|
|
Comienza agregando tu primera marca
|
|
</p>
|
|
<Link to="/crm/brands/new">
|
|
<Button>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
Nueva Marca
|
|
</Button>
|
|
</Link>
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
{data?.data?.map((brand) => (
|
|
<Card key={brand.id} className="group hover:shadow-md transition-shadow">
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-3">
|
|
{brand.logo_url ? (
|
|
<img
|
|
src={brand.logo_url}
|
|
alt={brand.name}
|
|
className="h-10 w-10 rounded-lg object-cover"
|
|
/>
|
|
) : (
|
|
<div
|
|
className="h-10 w-10 rounded-lg flex items-center justify-center"
|
|
style={{
|
|
backgroundColor: brand.primary_color || '#3B82F6',
|
|
}}
|
|
>
|
|
<span className="text-white font-bold text-lg">
|
|
{brand.name.charAt(0).toUpperCase()}
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<CardTitle className="text-lg">
|
|
<Link
|
|
to={`/crm/brands/${brand.id}`}
|
|
className="hover:underline"
|
|
>
|
|
{brand.name}
|
|
</Link>
|
|
</CardTitle>
|
|
{brand.client && (
|
|
<CardDescription>
|
|
<Link
|
|
to={`/crm/clients/${brand.client.id}`}
|
|
className="hover:underline"
|
|
>
|
|
{brand.client.name}
|
|
</Link>
|
|
</CardDescription>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
|
|
<Link to={`/crm/brands/${brand.id}/edit`}>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
</Link>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 text-destructive"
|
|
onClick={() => handleDelete(brand.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
{brand.description && (
|
|
<p className="text-sm text-muted-foreground line-clamp-2">
|
|
{brand.description}
|
|
</p>
|
|
)}
|
|
{(brand.primary_color || brand.secondary_color) && (
|
|
<div className="flex items-center gap-2">
|
|
{brand.primary_color && (
|
|
<div
|
|
className="w-6 h-6 rounded-full border"
|
|
style={{ backgroundColor: brand.primary_color }}
|
|
title="Color primario"
|
|
/>
|
|
)}
|
|
{brand.secondary_color && (
|
|
<div
|
|
className="w-6 h-6 rounded-full border"
|
|
style={{ backgroundColor: brand.secondary_color }}
|
|
title="Color secundario"
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="pt-2">
|
|
<span
|
|
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
|
brand.is_active
|
|
? 'bg-green-100 text-green-700'
|
|
: 'bg-gray-100 text-gray-700'
|
|
}`}
|
|
>
|
|
{brand.is_active ? 'Activa' : 'Inactiva'}
|
|
</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Pagination */}
|
|
{data?.meta && data.meta.totalPages > 1 && (
|
|
<div className="flex items-center justify-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={!data.meta.hasPreviousPage}
|
|
onClick={() => setPage(page - 1)}
|
|
>
|
|
Anterior
|
|
</Button>
|
|
<span className="text-sm text-muted-foreground">
|
|
Página {data.meta.page} de {data.meta.totalPages}
|
|
</span>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={!data.meta.hasNextPage}
|
|
onClick={() => setPage(page + 1)}
|
|
>
|
|
Siguiente
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|