diff --git a/design-reference/DESIGN-SYSTEM.md b/design-reference/DESIGN-SYSTEM.md index 10ebe20..a41750d 100644 --- a/design-reference/DESIGN-SYSTEM.md +++ b/design-reference/DESIGN-SYSTEM.md @@ -220,6 +220,25 @@ righe `py-4 px-6` / `divide-y divide-border` / `hover:bg-muted/40`. (singolare/plurale gestiti). - Le righe archiviate restano `opacity-60` con pill "Archiviato". +### Offerte (lista) notes + +`/admin/offers` migrata a "Quiet Luxury" (replica `design-reference/pagina-offerte`). +Griglia responsive di card `bg-card rounded-xl border-border p-6 shadow-card` +(`hover:shadow-card-hover`), `md:grid-cols-2 lg:grid-cols-3`. + +- **Header**: `PageHeader` "Offerte" + sottotitolo, azione "+ Nuova Offerta" + (`Button` primario) che espande un form inline (`Input` nome + "Crea"). +- **Filtri**: pill categoria rettangolari (`rounded-lg px-4 py-2`) — attiva + `bg-primary text-primary-foreground`, inattiva `border-border bg-card + text-muted-foreground hover:bg-muted`. "Tutti" + `categoryOptions` dinamiche. + A destra checkbox "Mostra offerte archiviate". +- **Card**: nome interno + descrizione muted + badge categoria colorato per tipo + (Retainer→blue, Entry→purple, Signature→amber, tutti con variante dark) via + helper `categoryBadgeClass`. Le archiviate sono `opacity-60` con pill "Archiviata". +- **Tabella tier**: `border-border rounded-lg`, header uppercase muted + (Tier · Servizi · Pubblico), righe `font-mono` `divide-y divide-border`; solo i + tier con `servicesTotal > 0` o `public_price` valorizzato; "Pubblico" assente → `—`. + ### Client Portal restyle notes The `/client/[token]` portal was migrated to the same "Quiet Luxury" tokens diff --git a/src/components/admin/offers/OfferListClient.tsx b/src/components/admin/offers/OfferListClient.tsx index 636c0f0..cdac086 100644 --- a/src/components/admin/offers/OfferListClient.tsx +++ b/src/components/admin/offers/OfferListClient.tsx @@ -4,16 +4,28 @@ import { useState, useMemo, useTransition } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { createOfferMacro } from "@/app/admin/offers/actions"; -import type { OfferListCard, OfferListTier } from "@/lib/offer-queries"; +import type { OfferListCard } from "@/lib/offer-queries"; import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { PageHeader } from "@/components/admin/PageHeader"; +import { cn } from "@/lib/utils"; function formatEuro(value: number): string { return `€${value.toLocaleString("it-IT", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } +/** Category → pill colors, light/dark. Falls back to a neutral badge. */ +function categoryBadgeClass(category: string): string { + const key = category.toLowerCase(); + if (key.includes("retainer")) + return "bg-blue-50 text-blue-600 border-blue-100 dark:bg-blue-950/40 dark:text-blue-300 dark:border-blue-900"; + if (key.includes("entry")) + return "bg-purple-50 text-purple-600 border-purple-100 dark:bg-purple-950/40 dark:text-purple-300 dark:border-purple-900"; + if (key.includes("signature")) + return "bg-amber-50 text-amber-700 border-amber-100 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900"; + return "bg-muted text-muted-foreground border-border"; +} + export function OfferListClient({ cards, categoryOptions, @@ -46,18 +58,14 @@ export function OfferListClient({ } const createCta = ( -
- {showCreateForm && (
setNewOfferName(e.target.value)} className="h-9 w-48" /> -
@@ -81,20 +84,25 @@ export function OfferListClient({ ); return ( -
- +
+ {/* Filter row */} -
-
+
+
@@ -103,76 +111,110 @@ export function OfferListClient({ key={category} type="button" onClick={() => setActiveCategory(category)} - className={`rounded-full border px-3 py-1 text-sm transition-colors ${ + className={cn( + "rounded-lg px-4 py-2 text-xs font-medium transition-all duration-200", activeCategory === category - ? "border-[#1A463C] text-[#1A463C]" - : "border-[#e5e7eb] text-[#71717a]" - }`} + ? "bg-primary text-primary-foreground" + : "border border-border bg-card text-muted-foreground hover:bg-muted hover:text-foreground" + )} > {category} ))}
-
{/* Card grid / empty state */} {filteredCards.length === 0 ? ( -
-

Nessuna offerta

-

Inizia creando la tua prima offerta

+
+

Nessuna offerta

+

Inizia creando la tua prima offerta

{createCta}
) : ( -
- {filteredCards.map((card) => ( - -

{card.internal_name}

- {card.description && ( -

{card.description}

- )} -
- {card.category && {card.category}} - {card.is_archived && ( - Archiviata +
+ {filteredCards.map((card) => { + const visibleTiers = card.tiers.filter( + (t) => Number(t.servicesTotal) > 0 || t.public_price !== null + ); + return ( + - {card.tiers.filter( - (t) => Number(t.servicesTotal) > 0 || t.public_price !== null - ).length > 0 && ( -
- {card.tiers - .filter((t) => Number(t.servicesTotal) > 0 || t.public_price !== null) - .map((t) => ( -
- - {t.public_name || `Tier ${t.tier_letter}`} - - - Servizi: {formatEuro(Number(t.servicesTotal))} - - - Pubblico: {t.public_price !== null ? formatEuro(Number(t.public_price)) : "—"} - -
- ))} + > + {/* Header */} +
+
+

{card.internal_name}

+ {card.description && ( +

+ {card.description} +

+ )} + {card.is_archived && ( + + Archiviata + + )} +
+ {card.category && ( + + {card.category} + + )}
- )} - - ))} + + {/* Tier table */} + {visibleTiers.length > 0 && ( +
+ + + + + + + + + + {visibleTiers.map((t) => ( + + + + + + ))} + +
TierServiziPubblico
+ {t.tier_letter || t.public_name} + + {formatEuro(Number(t.servicesTotal))} + + {t.public_price !== null ? formatEuro(Number(t.public_price)) : "—"} +
+
+ )} + + ); + })}
)}