Compare commits
2 Commits
dd4ae42542
...
dd2d148457
| Author | SHA1 | Date | |
|---|---|---|---|
| dd2d148457 | |||
| d444bd6064 |
@@ -282,6 +282,52 @@ uppercase muted (`bg-muted/50`, `text-[11px] font-semibold uppercase tracking-wi
|
||||
- **Empty state**: `border-dashed border-border rounded-xl`, `FileText` muted + CTA
|
||||
"Genera il primo preventivo →". Footer "Mostrando N di N preventivi generati".
|
||||
|
||||
### Progetti (lista) notes
|
||||
|
||||
`/admin/projects` migrata a "Quiet Luxury" (replica `design-reference/pagina-progetti`).
|
||||
Stesso contenitore tabella di Preventivi (`bg-card rounded-xl border-border shadow-card`,
|
||||
header `bg-muted/50` uppercase muted, righe `hover:bg-muted/40` + `divide-y divide-border`).
|
||||
|
||||
- **Header**: `PageHeader` "Progetti" + sottotitolo (redditività oraria), azione `Button`
|
||||
"Nuovo Progetto" (`+`, link a `/admin/projects/new`).
|
||||
- **Colonne**: Progetto (link → dettaglio + cliente muted, badge "Archiviato" se archiviato),
|
||||
Valore Totale (`text-right font-mono`), Acconto (badge centrato), Saldo (badge centrato),
|
||||
Timer (`TimerCell` centrato), Redditività €/H (`text-right`).
|
||||
- **Badge pagamento** (`ProjectRow`): pill rounded-full `text-[10px] uppercase border` dual
|
||||
light/dark — saldato→emerald, da_saldare→rose, inviata→amber; prefissati "Acconto:"/"Saldo:".
|
||||
Pagamento assente → `—` muted.
|
||||
- **TimerCell**: ora una pill `rounded-full border font-mono` — idle neutro (`bg-muted`,
|
||||
play triangle su `bg-foreground/10`), running emerald (`bg-emerald-50/60` dark
|
||||
`emerald-950/30`, pausa su `bg-emerald-500`), contatore live. Stessa pill riusata nel
|
||||
`TimerTab` del dettaglio progetto.
|
||||
- **Redditività**: `null` (0h tracciate) → `—` muted; `>0` → emerald bold con dot
|
||||
`bg-emerald-500`; `=0` → mono muted. Footer "Mostrando N di N progetti attivi".
|
||||
|
||||
### Impostazioni notes
|
||||
|
||||
`/admin/impostazioni` migrata a "Quiet Luxury" (replica `design-reference/pagina-impostazioni`).
|
||||
Pagina a sezioni impilate (`space-y-8`), ogni sezione è una card
|
||||
`rounded-xl border-border-light bg-card shadow-card p-6`.
|
||||
|
||||
- **Header**: `PageHeader` "Impostazioni" + sottotitolo ("Configura le tariffe target e gestisci
|
||||
le tassonomie globali del sistema").
|
||||
- **Titoli sezione**: `text-xs font-bold uppercase tracking-wider text-foreground`, sottotitolo
|
||||
`text-xs text-tertiary`. Le sezioni tassonomia hanno un'icona (`Tag`/`BookOpen`) in un box
|
||||
`h-9 w-9 rounded-lg border-border-light bg-muted text-muted-foreground`.
|
||||
- **Analytics Profittabilità**: form server-action con input `type=number` in un gruppo bordato
|
||||
`focus-within:border-primary focus-within:ring-1 ring-primary`, prefisso `€`/suffisso `/h`
|
||||
muted assoluti, valore `font-mono`; bottone "Salva" `bg-primary text-primary-foreground`.
|
||||
- **Griglie tassonomia** (`TaxonomyManager`): `border-t border-border-light pt-4` + grid
|
||||
`gap-6` — Offerte `md:grid-cols-2`, Catalogo `md:grid-cols-2 lg:grid-cols-3`.
|
||||
- **PoolManager** (box campo): `rounded-xl border-border-light bg-muted/40 p-5 flex flex-col
|
||||
justify-between`. Testata = label uppercase muted + badge conteggio mono
|
||||
(`bg-card border-border-light text-tertiary`, sempre visibile anche a 0). Valori = chip
|
||||
`bg-card border-border/60 text-foreground shadow-sm` con `X` (hover destructive → cascade
|
||||
delete confermata via `window.confirm`). Vuoto → "Nessun valore inserito" italic muted.
|
||||
Aggiunta = input full-width `bg-card border-border` con Enter (no bottone `+`); duplicati
|
||||
bloccati con errore inline `text-destructive`.
|
||||
- **Footer**: nota `text-xs text-tertiary` sulla sincronizzazione bidirezionale dei valori.
|
||||
|
||||
### Client Portal restyle notes
|
||||
|
||||
The `/client/[token]` portal was migrated to the same "Quiet Luxury" tokens
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Impostazioni — Luxury Admin CRM</title>
|
||||
<!-- Google Fonts: Plus Jakarta Sans -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Plus Jakarta Sans', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
dark: '#1A463C', /* Verde scuro lusso */
|
||||
darkHover: '#13342D', /* Variante scura per hover */
|
||||
active: 'rgba(255, 255, 255, 0.08)',
|
||||
bg: '#F8F9FA',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.sidebar-transition {
|
||||
transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1), transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-brand-bg font-sans text-slate-800 antialiased min-h-screen flex overflow-x-hidden">
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<aside id="sidebar" class="w-64 bg-brand-dark text-white flex flex-col justify-between p-6 border-r border-emerald-950/20 shrink-0 sidebar-transition relative z-10">
|
||||
<div class="overflow-hidden">
|
||||
<!-- Logo e Intestazione -->
|
||||
<div class="mb-10 px-2 flex items-center justify-between">
|
||||
<span id="sidebar-logo-text" class="text-lg font-bold tracking-wider text-emerald-50 whitespace-nowrap transition-opacity duration-300">iamcavalli</span>
|
||||
</div>
|
||||
|
||||
<!-- Navigazione Principale -->
|
||||
<nav class="space-y-1">
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M4 6a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2H6a2 2 0 01-2-2v-4zM14 16a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 01-2-2v-4z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Dashboard</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a.598.598 0 01-.73-.578 5.672 5.672 0 011.02-3.155A8.72 8.72 0 013 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Conversazioni</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Lead</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0 text-emerald-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Clienti</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Progetti</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Preventivi</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M7 7h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Offerte</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Catalogo</span>
|
||||
</a>
|
||||
<!-- Stato Attivo su Impostazioni -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-white bg-brand-active font-medium transition-all duration-200 shadow-sm">
|
||||
<svg class="w-4 h-4 shrink-0 text-emerald-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066-1.543.94-3.31-.826-2.37-2.37 1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Impostazioni</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Area Inferiore Sidebar -->
|
||||
<div class="border-t border-white/10 pt-4 space-y-3 overflow-hidden">
|
||||
<div class="flex items-center justify-between px-3 py-1 text-xs text-emerald-200/50 whitespace-nowrap">
|
||||
<span class="sidebar-text transition-opacity duration-300">Tema</span>
|
||||
<button class="p-1 rounded-full bg-white/5 hover:bg-white/10 text-emerald-200 hover:text-white transition-all duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707m12.728 0l-.707-.707M6.343 6.343l-.707-.707M14 12a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-red-300/80 hover:text-red-200 hover:bg-red-950/20 transition-all duration-200 whitespace-nowrap">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
<span class="sidebar-text transition-opacity duration-300">Esci</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- SEZIONE PRINCIPALE -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
|
||||
<!-- BARRA DI INTESTAZIONE SUPERIORE -->
|
||||
<header class="h-16 border-b border-slate-100 bg-white px-8 flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Pulsante Apri/Chiudi Sidebar -->
|
||||
<button id="sidebar-toggle" class="p-2 -ml-2 rounded-lg text-slate-500 hover:bg-slate-50 hover:text-slate-800 transition-colors focus:outline-none" title="Espandi/Comprimi Sidebar">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 6h16M4 12h12M4 18h16"/></svg>
|
||||
</button>
|
||||
<span class="text-xs text-slate-400 font-medium">Configurazione Sistema</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-xs font-semibold text-slate-700 bg-slate-100 px-2.5 py-1 rounded-full">Admin Panel</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- AREA DEL CONTENUTO -->
|
||||
<main class="flex-1 p-8 lg:p-10 max-w-[1400px] w-full mx-auto flex flex-col gap-8 overflow-y-auto">
|
||||
|
||||
<!-- INTESTAZIONE SEZIONE -->
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900 tracking-tight">Impostazioni</h1>
|
||||
<p class="text-xs text-slate-400 mt-1">Configura le tariffe target e gestisci le tassonomie globali del sistema</p>
|
||||
</div>
|
||||
|
||||
<!-- SEZIONE 1: ANALYTICS PROFITTABILITÀ -->
|
||||
<section class="bg-white rounded-xl border border-slate-100 shadow-[0_4px_20px_rgba(0,0,0,0.01)] p-6">
|
||||
<div class="max-w-2xl">
|
||||
<h2 class="text-xs font-bold text-slate-900 uppercase tracking-wider mb-1">Analytics Profittabilità</h2>
|
||||
<p class="text-xs text-slate-400 mb-6">Tariffa oraria target (€/h). Usata per calcolare il costo ideale e il delta profitto/perdita per ogni progetto.</p>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative rounded-lg border border-slate-200 focus-within:border-brand-dark focus-within:ring-1 focus-within:ring-brand-dark transition-all duration-150">
|
||||
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-xs font-medium text-slate-400 pointer-events-none">
|
||||
€
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value="100,00"
|
||||
class="bg-transparent text-xs font-semibold font-mono text-slate-800 pl-7 pr-12 py-2.5 w-36 focus:outline-none"
|
||||
/>
|
||||
<span class="absolute inset-y-0 right-0 pr-3 flex items-center text-[11px] font-medium text-slate-400 pointer-events-none">
|
||||
/h
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Bottone Salva -->
|
||||
<button class="bg-brand-dark hover:bg-brand-darkHover text-white text-xs font-medium px-5 py-2.5 rounded-lg transition-colors shadow-sm">
|
||||
Salva
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SEZIONE 2: TASSONOMIE OFFERTE -->
|
||||
<section class="bg-white rounded-xl border border-slate-100 shadow-[0_4px_20px_rgba(0,0,0,0.01)] p-6">
|
||||
<div class="flex items-start gap-3 mb-6">
|
||||
<div class="p-2 bg-slate-50 border border-slate-100 rounded-lg text-slate-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581a2.25 2.25 0 003.182 0l5.174-5.173a2.25 2.25 0 000-3.181L12.016 3.659A2.25 2.25 0 0010.425 3h-.857zM10.5 7.5h.008v.008H10.5V7.5z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xs font-bold text-slate-900 uppercase tracking-wider">Tassonomie Offerte</h2>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Valori dei campi Categoria, Ticket, Tipo e Obiettivo dell'editor offerte.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 pt-4 border-t border-slate-50">
|
||||
|
||||
<!-- Categoria -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Categoria</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">3</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Entry Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Retainer Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Signature Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
<!-- Ticket -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Ticket</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">3</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
High Ticket <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Low Ticket <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Mid Ticket <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
<!-- Tipo -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Tipo</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">3</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Audit <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Consulenza <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Done For You <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
<!-- Obiettivo -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Obiettivo</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">7</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Aumento LTV <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Caso Studio <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Fatturato <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Lead Generation <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- SEZIONE 3: TASSONOMIE CATALOGO -->
|
||||
<section class="bg-white rounded-xl border border-slate-100 shadow-[0_4px_20px_rgba(0,0,0,0.01)] p-6">
|
||||
<div class="flex items-start gap-3 mb-6">
|
||||
<div class="p-2 bg-slate-50 border border-slate-100 rounded-lg text-slate-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xs font-bold text-slate-900 uppercase tracking-wider">Tassonomie Catalogo</h2>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Valori dei campi Fase, Offerta e Pacchetto del catalogo servizi.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 pt-4 border-t border-slate-50">
|
||||
|
||||
<!-- Box: Fase -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Fase</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">5</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Fase 1 ➔ Onboarding <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Fase 2 ➔ Analisi <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Fase 3 ➔ Esecuzione <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
<!-- Box: Offerta -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Offerta</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">3</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1.5 mb-4">
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Entry Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Retainer Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1 pl-2.5 pr-1.5 py-1 rounded-md text-[11px] font-semibold bg-white border border-slate-200/60 text-slate-700 shadow-sm">
|
||||
Signature Offer <button class="text-slate-400 hover:text-slate-800">×</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
<!-- Box: Pacchetto -->
|
||||
<div class="bg-slate-50/50 border border-slate-100 rounded-xl p-5 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-xs font-bold text-slate-500 uppercase tracking-wider">Pacchetto</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 font-mono bg-white border border-slate-100 px-1.5 py-0.5 rounded">0</span>
|
||||
</div>
|
||||
<div class="text-center py-6 text-slate-400 text-xs italic">
|
||||
Nessun valore inserito
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" placeholder="Aggiungi valore..." class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs placeholder-slate-400 focus:outline-none focus:border-brand-dark transition-colors" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- NOTA IN BASSO -->
|
||||
<footer class="text-xs text-slate-400 max-w-4xl px-2 leading-relaxed">
|
||||
I valori si sincronizzano ovunque: creandone uno dall'editor offerte o dal catalogo comparirà qui automaticamente. Eliminando un valore da qui viene rimosso ovunque sia usato.
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- SCRIPT DI LOGICA (Gestione Sidebar) -->
|
||||
<script>
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggleButton = document.getElementById('sidebar-toggle');
|
||||
const logoText = document.getElementById('sidebar-logo-text');
|
||||
const sidebarTexts = document.querySelectorAll('.sidebar-text');
|
||||
|
||||
toggleButton.addEventListener('click', () => {
|
||||
const isCollapsed = sidebar.classList.contains('w-20');
|
||||
|
||||
if (isCollapsed) {
|
||||
sidebar.classList.remove('w-20');
|
||||
sidebar.classList.add('w-64');
|
||||
setTimeout(() => {
|
||||
logoText.classList.remove('opacity-0');
|
||||
sidebarTexts.forEach(text => text.classList.remove('opacity-0'));
|
||||
}, 150);
|
||||
} else {
|
||||
logoText.classList.add('opacity-0');
|
||||
sidebarTexts.forEach(text => text.classList.add('opacity-0'));
|
||||
sidebar.classList.remove('w-64');
|
||||
sidebar.classList.add('w-20');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,332 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Progetti — Luxury Admin CRM</title>
|
||||
<!-- Google Fonts: Plus Jakarta Sans -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Plus Jakarta Sans', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
dark: '#1A463C', /* Verde scuro lusso */
|
||||
darkHover: '#13342D', /* Variante scura per hover */
|
||||
active: 'rgba(255, 255, 255, 0.08)',
|
||||
bg: '#F8F9FA',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.sidebar-transition {
|
||||
transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1), transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-brand-bg font-sans text-slate-800 antialiased min-h-screen flex overflow-x-hidden">
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
<aside id="sidebar" class="w-64 bg-brand-dark text-white flex flex-col justify-between p-6 border-r border-emerald-950/20 shrink-0 sidebar-transition relative z-10">
|
||||
<div class="overflow-hidden">
|
||||
<!-- Logo e Intestazione -->
|
||||
<div class="mb-10 px-2 flex items-center justify-between">
|
||||
<span id="sidebar-logo-text" class="text-lg font-bold tracking-wider text-emerald-50 whitespace-nowrap transition-opacity duration-300">iamcavalli</span>
|
||||
</div>
|
||||
|
||||
<!-- Navigazione Principale -->
|
||||
<nav class="space-y-1">
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M4 6a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2H6a2 2 0 01-2-2v-4zM14 16a2 2 0 012-2h2a2 2 0 012 2v4a2 2 0 01-2 2h-2a2 2 0 01-2-2v-4z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Dashboard</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a.598.598 0 01-.73-.578 5.672 5.672 0 011.02-3.155A8.72 8.72 0 013 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Conversazioni</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Lead</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Clienti</span>
|
||||
</a>
|
||||
<!-- Stato Attivo su Progetti -->
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-white bg-brand-active font-medium transition-all duration-200 shadow-sm">
|
||||
<svg class="w-4 h-4 shrink-0 text-emerald-400" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Progetti</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Preventivi</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M7 7h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Offerte</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Catalogo</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-emerald-200/70 hover:text-white hover:bg-white/5 transition-all duration-200">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
||||
<span class="sidebar-text whitespace-nowrap transition-opacity duration-300">Impostazioni</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Area Inferiore Sidebar -->
|
||||
<div class="border-t border-white/10 pt-4 space-y-3 overflow-hidden">
|
||||
<div class="flex items-center justify-between px-3 py-1 text-xs text-emerald-200/50 whitespace-nowrap">
|
||||
<span class="sidebar-text transition-opacity duration-300">Tema</span>
|
||||
<button class="p-1 rounded-full bg-white/5 hover:bg-white/10 text-emerald-200 hover:text-white transition-all duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707m12.728 0l-.707-.707M6.343 6.343l-.707-.707M14 12a2 2 0 11-4 0 2 2 0 014 0z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<a href="#" class="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-red-300/80 hover:text-red-200 hover:bg-red-950/20 transition-all duration-200 whitespace-nowrap">
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/></svg>
|
||||
<span class="sidebar-text transition-opacity duration-300">Esci</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- SEZIONE PRINCIPALE -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
|
||||
<!-- BARRA DI INTESTAZIONE SUPERIORE -->
|
||||
<header class="h-16 border-b border-slate-100 bg-white px-8 flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Pulsante Apri/Chiudi Sidebar -->
|
||||
<button id="sidebar-toggle" class="p-2 -ml-2 rounded-lg text-slate-500 hover:bg-slate-50 hover:text-slate-800 transition-colors focus:outline-none" title="Espandi/Comprimi Sidebar">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M4 6h16M4 12h12M4 18h16"/></svg>
|
||||
</button>
|
||||
<span class="text-xs text-slate-400 font-medium">Gestione Progetti</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-xs font-semibold text-slate-700 bg-slate-100 px-2.5 py-1 rounded-full">Admin Panel</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- AREA DEL CONTENUTO -->
|
||||
<main class="flex-1 p-8 lg:p-10 max-w-[1400px] w-full mx-auto flex flex-col gap-8 overflow-y-auto">
|
||||
|
||||
<!-- INTESTAZIONE SEZIONE -->
|
||||
<div class="flex flex-col sm:flex-row justify-between sm:items-center gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900 tracking-tight">Progetti</h1>
|
||||
<p class="text-xs text-slate-400 mt-1">Traccia le attività, monitora gli incassi e verifica la redditività oraria effettiva</p>
|
||||
</div>
|
||||
|
||||
<!-- Pulsante Nuovo Progetto -->
|
||||
<button class="bg-brand-dark hover:bg-brand-darkHover text-white text-xs font-medium tracking-wide px-5 py-3 rounded-lg flex items-center gap-2 transition-all duration-200 shadow-sm">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||
Nuovo Progetto
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- TABELLA PROGETTI -->
|
||||
<div class="bg-white rounded-xl border border-slate-100 shadow-[0_4px_20px_rgba(0,0,0,0.01)] overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="border-b border-slate-100 bg-slate-50/50 text-[11px] font-bold uppercase tracking-wider text-slate-400">
|
||||
<th class="py-4 px-6 w-1/4">Progetto</th>
|
||||
<th class="py-4 px-6 text-right">Valore Totale</th>
|
||||
<th class="py-4 px-6 text-center">Acconto</th>
|
||||
<th class="py-4 px-6 text-center">Saldo</th>
|
||||
<th class="py-4 px-6 text-center">Timer</th>
|
||||
<th class="py-4 px-6 text-right">Redditività (€/H)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100/60 text-sm">
|
||||
|
||||
<!-- Progetto 1 -->
|
||||
<tr class="hover:bg-slate-50/30 transition-colors">
|
||||
<td class="py-4 px-6">
|
||||
<div class="font-semibold text-slate-950">Protocollo Estetico</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">George Vlad</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-medium text-slate-900 font-mono">€3.500,00</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-emerald-50 text-emerald-700 border border-emerald-100 uppercase tracking-wide">
|
||||
Acconto: Saldato
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Saldo: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<div class="inline-flex items-center gap-2 justify-center bg-slate-50 border border-slate-100 rounded-full pl-2 pr-3 py-1 text-xs font-mono text-slate-600">
|
||||
<button class="w-5 h-5 rounded-full bg-slate-200 hover:bg-slate-300 flex items-center justify-center transition-colors">
|
||||
<svg class="w-2.5 h-2.5 text-slate-600" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
0:00
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right text-slate-400 font-medium">—</td>
|
||||
</tr>
|
||||
|
||||
<!-- Progetto 2 -->
|
||||
<tr class="hover:bg-slate-50/30 transition-colors">
|
||||
<td class="py-4 px-6">
|
||||
<div class="font-semibold text-slate-950">Caruso Speaker</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">Gian Luca Caruso</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-medium text-slate-900 font-mono">€5.000,00</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-emerald-50 text-emerald-700 border border-emerald-100 uppercase tracking-wide">
|
||||
Acconto: Saldato
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Saldo: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<div class="inline-flex items-center gap-2 justify-center bg-slate-50 border border-slate-100 rounded-full pl-2 pr-3 py-1 text-xs font-mono text-slate-600">
|
||||
<button class="w-5 h-5 rounded-full bg-slate-200 hover:bg-slate-300 flex items-center justify-center transition-colors">
|
||||
<svg class="w-2.5 h-2.5 text-slate-600" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
0:00
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right text-slate-400 font-medium">—</td>
|
||||
</tr>
|
||||
|
||||
<!-- Progetto 3 -->
|
||||
<tr class="hover:bg-slate-50/30 transition-colors">
|
||||
<td class="py-4 px-6">
|
||||
<div class="font-semibold text-slate-950">Incarichi Online</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">George Vlad</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-medium text-slate-900 font-mono">€3.500,00</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Acconto: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Saldo: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<div class="inline-flex items-center gap-2 justify-center bg-slate-50 border border-slate-100 rounded-full pl-2 pr-3 py-1 text-xs font-mono text-slate-600">
|
||||
<button class="w-5 h-5 rounded-full bg-slate-200 hover:bg-slate-300 flex items-center justify-center transition-colors">
|
||||
<svg class="w-2.5 h-2.5 text-slate-600" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
0:00
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right text-slate-400 font-medium">—</td>
|
||||
</tr>
|
||||
|
||||
<!-- Progetto 4 -->
|
||||
<tr class="hover:bg-slate-50/30 transition-colors">
|
||||
<td class="py-4 px-6">
|
||||
<div class="font-semibold text-slate-950">Teckell</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">Gianfranco Barban</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-medium text-slate-900 font-mono">€0,00</td>
|
||||
<td class="py-4 px-6 text-center text-slate-300">—</td>
|
||||
<td class="py-4 px-6 text-center text-slate-300">—</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<div class="inline-flex items-center gap-2 justify-center bg-slate-50 border border-slate-100 rounded-full pl-2 pr-3 py-1 text-xs font-mono text-slate-600">
|
||||
<button class="w-5 h-5 rounded-full bg-slate-200 hover:bg-slate-300 flex items-center justify-center transition-colors">
|
||||
<svg class="w-2.5 h-2.5 text-slate-600" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
1:30:00
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-mono font-semibold text-slate-500">€0,00/h</td>
|
||||
</tr>
|
||||
|
||||
<!-- Progetto 5 (Redditività eccellente evidenziata da micro-indicatore) -->
|
||||
<tr class="hover:bg-slate-50/30 transition-colors">
|
||||
<td class="py-4 px-6">
|
||||
<div class="font-semibold text-slate-950">Rossi Inc</div>
|
||||
<div class="text-[11px] text-slate-400 mt-0.5">Mario Rossi</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right font-medium text-slate-900 font-mono">€7.000,00</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Acconto: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold bg-rose-50 text-rose-700 border border-rose-100 uppercase tracking-wide">
|
||||
Saldo: Da saldare
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-center">
|
||||
<div class="inline-flex items-center gap-2 justify-center bg-emerald-50/40 border border-emerald-100 rounded-full pl-2 pr-3 py-1 text-xs font-mono text-emerald-800">
|
||||
<!-- Timer Attivo in verde -->
|
||||
<button class="w-5 h-5 rounded-full bg-emerald-500 text-white flex items-center justify-center transition-colors">
|
||||
<svg class="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>
|
||||
</button>
|
||||
30:00:08
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-6 text-right">
|
||||
<div class="inline-flex items-center justify-end gap-1.5 font-mono font-bold text-emerald-800">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
€233,32/h
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Footer Tabella -->
|
||||
<div class="border-t border-slate-100 px-6 py-4 flex items-center justify-between text-xs text-slate-400">
|
||||
<span>Mostrando 5 di 5 progetti attivi</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- SCRIPT DI LOGICA (Gestione Sidebar) -->
|
||||
<script>
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const toggleButton = document.getElementById('sidebar-toggle');
|
||||
const logoText = document.getElementById('sidebar-logo-text');
|
||||
const sidebarTexts = document.querySelectorAll('.sidebar-text');
|
||||
|
||||
toggleButton.addEventListener('click', () => {
|
||||
const isCollapsed = sidebar.classList.contains('w-20');
|
||||
|
||||
if (isCollapsed) {
|
||||
sidebar.classList.remove('w-20');
|
||||
sidebar.classList.add('w-64');
|
||||
setTimeout(() => {
|
||||
logoText.classList.remove('opacity-0');
|
||||
sidebarTexts.forEach(text => text.classList.remove('opacity-0'));
|
||||
}, 150);
|
||||
} else {
|
||||
logoText.classList.add('opacity-0');
|
||||
sidebarTexts.forEach(text => text.classList.add('opacity-0'));
|
||||
sidebar.classList.remove('w-64');
|
||||
sidebar.classList.add('w-20');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -17,50 +17,53 @@ export default async function ImpostazioniPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader title="Impostazioni" />
|
||||
<div className="space-y-8">
|
||||
<PageHeader
|
||||
title="Impostazioni"
|
||||
subtitle="Configura le tariffe target e gestisci le tassonomie globali del sistema"
|
||||
/>
|
||||
|
||||
<div className="space-y-6 max-w-4xl">
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6 max-w-md">
|
||||
<h2 className="text-base font-semibold text-[#1a1a1a] mb-4">Analytics Profittabilità</h2>
|
||||
<section className="rounded-xl border border-border-light bg-card p-6 shadow-card">
|
||||
<div className="max-w-2xl">
|
||||
<h2 className="mb-1 text-xs font-bold uppercase tracking-wider text-foreground">
|
||||
Analytics Profittabilità
|
||||
</h2>
|
||||
<p className="mb-6 text-xs text-tertiary">
|
||||
Tariffa oraria target (€/h). Usata per calcolare il costo ideale e il delta
|
||||
profitto/perdita per ogni progetto.
|
||||
</p>
|
||||
|
||||
<form action={handleSave} className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="target_hourly_rate"
|
||||
className="block text-sm font-medium text-[#1a1a1a] mb-1"
|
||||
>
|
||||
Tariffa oraria target (€/h)
|
||||
</label>
|
||||
<p className="text-xs text-[#71717a] mb-2">
|
||||
Usata per calcolare il costo ideale e il delta profitto/perdita per ogni progetto.
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-[#71717a]">€</span>
|
||||
<input
|
||||
id="target_hourly_rate"
|
||||
name="target_hourly_rate"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
defaultValue={targetRate.toFixed(2)}
|
||||
className="border border-[#e5e7eb] rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#1A463C]/20 w-32"
|
||||
/>
|
||||
<span className="text-sm text-[#71717a]">/h</span>
|
||||
</div>
|
||||
<form action={handleSave} className="flex items-center gap-3">
|
||||
<div className="relative rounded-lg border border-border transition-all duration-150 focus-within:border-primary focus-within:ring-1 focus-within:ring-primary">
|
||||
<span className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-xs font-medium text-tertiary">
|
||||
€
|
||||
</span>
|
||||
<input
|
||||
id="target_hourly_rate"
|
||||
name="target_hourly_rate"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
defaultValue={targetRate.toFixed(2)}
|
||||
aria-label="Tariffa oraria target"
|
||||
className="w-36 bg-transparent py-2.5 pl-7 pr-12 font-mono text-xs font-semibold text-foreground focus:outline-none"
|
||||
/>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 text-[11px] font-medium text-tertiary">
|
||||
/h
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#1A463C] text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-[#1A463C]/90 transition-colors"
|
||||
className="rounded-lg bg-primary px-5 py-2.5 text-xs font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90"
|
||||
>
|
||||
Salva
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<TaxonomyManager pools={pools} />
|
||||
</div>
|
||||
<TaxonomyManager pools={pools} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getAllProjectsWithPayments } from "@/lib/admin-queries";
|
||||
import { ProjectRow } from "@/components/admin/ProjectRow";
|
||||
import Link from "next/link";
|
||||
import { FolderOpen, Plus } from "lucide-react";
|
||||
import { PageHeader } from "@/components/admin/PageHeader";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export const revalidate = 0;
|
||||
|
||||
@@ -9,43 +11,57 @@ export default async function ProjectsPage() {
|
||||
const projects = await getAllProjectsWithPayments();
|
||||
|
||||
const projectiAction = (
|
||||
<Link
|
||||
href="/admin/projects/new"
|
||||
className="text-sm bg-[#1A463C] text-white px-4 py-2 rounded-lg hover:bg-[#1A463C]/90 transition-colors"
|
||||
>
|
||||
+ Nuovo Progetto
|
||||
</Link>
|
||||
<Button asChild className="self-start sm:self-auto">
|
||||
<Link href="/admin/projects/new">
|
||||
<Plus size={16} />
|
||||
Nuovo Progetto
|
||||
</Link>
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader title="Progetti" action={projectiAction} />
|
||||
<PageHeader
|
||||
title="Progetti"
|
||||
subtitle="Traccia le attività, monitora gli incassi e verifica la redditività oraria effettiva"
|
||||
action={projectiAction}
|
||||
/>
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-12 text-center">
|
||||
<p className="text-[#71717a]">Nessun progetto ancora. Creane uno dal dettaglio di un cliente.</p>
|
||||
<div className="text-center py-20 border border-dashed border-border rounded-xl">
|
||||
<FolderOpen size={40} className="mx-auto text-muted-foreground mb-4" />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Nessun progetto ancora. Creane uno dal dettaglio di un cliente.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead className="bg-[#f9f9f9] border-b border-[#e5e7eb]">
|
||||
<tr>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">Progetto</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">Valore</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">Acconto</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">Saldo</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">Timer</th>
|
||||
<th className="text-left py-3 px-4 text-xs font-semibold text-[#71717a] uppercase tracking-wider">€/h</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{projects.map((project) => (
|
||||
<ProjectRow key={project.id} project={project} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="bg-card rounded-xl border border-border shadow-card overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-border bg-muted/50 text-muted-foreground text-[11px] font-semibold uppercase tracking-wider">
|
||||
<th className="py-4 px-6 w-1/4">Progetto</th>
|
||||
<th className="py-4 px-6 text-right">Valore Totale</th>
|
||||
<th className="py-4 px-6 text-center">Acconto</th>
|
||||
<th className="py-4 px-6 text-center">Saldo</th>
|
||||
<th className="py-4 px-6 text-center">Timer</th>
|
||||
<th className="py-4 px-6 text-right">Redditività (€/H)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{projects.map((project) => (
|
||||
<ProjectRow key={project.id} project={project} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border px-6 py-4 text-xs text-muted-foreground">
|
||||
Mostrando {projects.length} di {projects.length}{" "}
|
||||
{projects.length === 1 ? "progetto attivo" : "progetti attivi"}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,111 @@
|
||||
import Link from "next/link";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { TimerCell } from "@/components/admin/TimerCell";
|
||||
import type { ProjectWithPayments } from "@/lib/admin-queries";
|
||||
|
||||
const statusConfig: Record<string, { label: string; className: string }> = {
|
||||
da_saldare: { label: "Da saldare", className: "bg-red-100 text-red-700 border-transparent" },
|
||||
inviata: { label: "Inviata", className: "bg-[#DEF168]/30 text-[#1A463C] border-transparent" },
|
||||
saldato: { label: "Saldato", className: "bg-[#1A463C]/10 text-[#1A463C] border-transparent font-medium" },
|
||||
da_saldare: {
|
||||
label: "Da saldare",
|
||||
className:
|
||||
"bg-rose-50 text-rose-700 border-rose-100 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-900",
|
||||
},
|
||||
inviata: {
|
||||
label: "Inviata",
|
||||
className:
|
||||
"bg-amber-50 text-amber-600 border-amber-100 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900",
|
||||
},
|
||||
saldato: {
|
||||
label: "Saldato",
|
||||
className:
|
||||
"bg-emerald-50 text-emerald-700 border-emerald-100 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900",
|
||||
},
|
||||
};
|
||||
|
||||
function PaymentBadge({ prefix, status }: { prefix: string; status: string }) {
|
||||
const info = statusConfig[status] ?? {
|
||||
label: status,
|
||||
className: "bg-muted text-muted-foreground border-border",
|
||||
};
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-semibold tracking-wide uppercase border whitespace-nowrap ${info.className}`}
|
||||
>
|
||||
{prefix}: {info.label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProjectRow({ project }: { project: ProjectWithPayments }) {
|
||||
const acconto = project.payments.find((p) => p.label.toLowerCase().includes("acconto"));
|
||||
const saldo = project.payments.find((p) => p.label.toLowerCase().includes("saldo"));
|
||||
const saldo = project.payments.find((p) => p.label.toLowerCase().includes("saldo"));
|
||||
|
||||
const hours = project.totalTrackedSeconds / 3600;
|
||||
const eurPerHour = hours > 0 ? parseFloat(project.accepted_total) / hours : null;
|
||||
|
||||
return (
|
||||
<tr className={`border-b border-[#f4f4f5] hover:bg-[#f9f9f9] transition-colors ${project.archived ? "opacity-60" : ""}`}>
|
||||
<td className="py-3 px-4">
|
||||
<tr
|
||||
className={`hover:bg-muted/40 transition-colors ${project.archived ? "opacity-60" : ""}`}
|
||||
>
|
||||
<td className="py-4 px-6">
|
||||
<Link
|
||||
href={`/admin/projects/${project.id}`}
|
||||
className="font-medium text-[#1a1a1a] hover:text-[#1A463C] hover:underline"
|
||||
className="font-semibold text-foreground hover:text-primary transition-colors"
|
||||
>
|
||||
{project.name}
|
||||
</Link>
|
||||
<p className="text-xs text-[#71717a]">{project.client.name}</p>
|
||||
<p className="text-[11px] text-muted-foreground mt-0.5">{project.client.name}</p>
|
||||
{project.archived && (
|
||||
<span className="text-[10px] text-[#71717a] bg-[#f4f4f5] px-1.5 py-0.5 rounded-full">
|
||||
<span className="mt-1 inline-block text-[10px] text-muted-foreground bg-muted px-1.5 py-0.5 rounded-full">
|
||||
Archiviato
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-[#1a1a1a]">
|
||||
|
||||
<td className="py-4 px-6 text-right font-medium text-foreground font-mono whitespace-nowrap">
|
||||
€{parseFloat(project.accepted_total).toLocaleString("it-IT", { minimumFractionDigits: 2 })}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{acconto && (
|
||||
<Badge className={statusConfig[acconto.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}>
|
||||
Acconto: {statusConfig[acconto.status]?.label ?? acconto.status}
|
||||
</Badge>
|
||||
|
||||
<td className="py-4 px-6 text-center">
|
||||
{acconto ? (
|
||||
<PaymentBadge prefix="Acconto" status={acconto.status} />
|
||||
) : (
|
||||
<span className="text-muted-foreground/40">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
{saldo && (
|
||||
<Badge className={statusConfig[saldo.status]?.className ?? "border-transparent bg-[#f4f4f5] text-[#71717a]"}>
|
||||
Saldo: {statusConfig[saldo.status]?.label ?? saldo.status}
|
||||
</Badge>
|
||||
|
||||
<td className="py-4 px-6 text-center">
|
||||
{saldo ? (
|
||||
<PaymentBadge prefix="Saldo" status={saldo.status} />
|
||||
) : (
|
||||
<span className="text-muted-foreground/40">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-3 px-4">
|
||||
<TimerCell
|
||||
clientId={project.id}
|
||||
projectId={project.id}
|
||||
activeEntryId={project.activeTimerEntryId}
|
||||
activeStartedAt={project.activeTimerStartedAt}
|
||||
totalTrackedSeconds={project.totalTrackedSeconds}
|
||||
/>
|
||||
|
||||
<td className="py-4 px-6">
|
||||
<div className="flex justify-center">
|
||||
<TimerCell
|
||||
clientId={project.id}
|
||||
projectId={project.id}
|
||||
activeEntryId={project.activeTimerEntryId}
|
||||
activeStartedAt={project.activeTimerStartedAt}
|
||||
totalTrackedSeconds={project.totalTrackedSeconds}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 px-4 text-sm text-[#71717a] tabular-nums">
|
||||
{eurPerHour !== null ? `€${eurPerHour.toFixed(2)}/h` : "—"}
|
||||
|
||||
<td className="py-4 px-6 text-right whitespace-nowrap">
|
||||
{eurPerHour === null ? (
|
||||
<span className="text-muted-foreground/40 font-medium">—</span>
|
||||
) : eurPerHour > 0 ? (
|
||||
<span className="inline-flex items-center justify-end gap-1.5 font-mono font-bold text-emerald-700 dark:text-emerald-400">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
|
||||
€{eurPerHour.toFixed(2)}/h
|
||||
</span>
|
||||
) : (
|
||||
<span className="font-mono font-semibold text-muted-foreground">
|
||||
€{eurPerHour.toFixed(2)}/h
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,36 +60,37 @@ export function TimerCell({
|
||||
const displayTotal = formatDuration(totalTrackedSeconds + (isRunning ? elapsed : 0));
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`inline-flex items-center gap-2 rounded-full border pl-2 pr-3 py-1 text-xs font-mono tabular-nums transition-colors ${
|
||||
isRunning
|
||||
? "bg-emerald-50/60 border-emerald-100 text-emerald-800 dark:bg-emerald-950/30 dark:border-emerald-900 dark:text-emerald-300 font-semibold"
|
||||
: "bg-muted border-border text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
onClick={handleToggle}
|
||||
title={isRunning ? "Ferma timer" : "Avvia timer"}
|
||||
className={`w-7 h-7 rounded-full flex items-center justify-center transition-colors shrink-0 ${
|
||||
className={`w-5 h-5 rounded-full flex items-center justify-center transition-colors shrink-0 ${
|
||||
isRunning
|
||||
? "bg-red-100 text-red-600 hover:bg-red-200"
|
||||
: "bg-[#1A463C]/10 text-[#1A463C] hover:bg-[#1A463C]/20"
|
||||
? "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||
: "bg-foreground/10 text-foreground hover:bg-foreground/20"
|
||||
}`}
|
||||
>
|
||||
{isRunning ? (
|
||||
// Stop square
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
|
||||
<rect x="3" y="3" width="10" height="10" rx="1.5" />
|
||||
// Pause bars
|
||||
<svg className="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 16 16">
|
||||
<rect x="4" y="3" width="3" height="10" rx="1" />
|
||||
<rect x="9" y="3" width="3" height="10" rx="1" />
|
||||
</svg>
|
||||
) : (
|
||||
// Play triangle
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
|
||||
<svg className="w-2.5 h-2.5" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M4 3.5v9l9-4.5-9-4.5z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<span
|
||||
className={`text-xs tabular-nums font-mono ${
|
||||
isRunning ? "text-red-600 font-semibold" : "text-[#71717a]"
|
||||
}`}
|
||||
>
|
||||
{isRunning ? formatDuration(elapsed) : displayTotal}
|
||||
</span>
|
||||
{isRunning ? formatDuration(elapsed) : displayTotal}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -109,8 +109,8 @@ function ConversationListItem({
|
||||
href={`/admin/conversazioni?c=${conv.clientId}`}
|
||||
className={cn(
|
||||
"block p-4 transition-colors hover:bg-muted/40",
|
||||
active && "bg-muted/60 border-l-2 border-primary",
|
||||
!active && "border-l-2 border-transparent"
|
||||
active && "bg-muted/60 border-l-[3px] border-l-primary",
|
||||
!active && "border-l-[3px] border-l-transparent"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useTransition } from "react";
|
||||
import { X, Plus } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
export function PoolManager({
|
||||
label,
|
||||
@@ -18,8 +18,6 @@ export function PoolManager({
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const canAdd = input.trim().length > 0;
|
||||
|
||||
function handleAdd() {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) return;
|
||||
@@ -51,67 +49,62 @@ export function PoolManager({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`rounded-lg border border-[#ececee] bg-[#fafafa] p-3.5 transition-opacity ${
|
||||
className={`flex flex-col justify-between rounded-xl border border-border-light bg-muted/40 p-5 transition-opacity ${
|
||||
isPending ? "opacity-60" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2.5">
|
||||
<p className="text-sm font-medium text-[#1a1a1a]">{label}</p>
|
||||
{pool.length > 0 && (
|
||||
<span className="text-[11px] text-[#a1a1aa] tabular-nums">{pool.length}</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
||||
{label}
|
||||
</span>
|
||||
<span className="rounded border border-border-light bg-card px-1.5 py-0.5 font-mono text-[10px] font-bold tabular-nums text-tertiary">
|
||||
{pool.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1.5 mb-3 min-h-[26px]">
|
||||
{pool.length === 0 ? (
|
||||
<span className="text-xs text-[#a1a1aa] italic">Nessun valore</span>
|
||||
) : (
|
||||
pool.map((v) => (
|
||||
<span
|
||||
key={v}
|
||||
className="group inline-flex items-center gap-1 rounded-md border border-[#e5e7eb] bg-white pl-2.5 pr-1 py-1 text-xs text-[#27272a] shadow-[0_1px_1px_rgba(0,0,0,0.02)]"
|
||||
>
|
||||
{v}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemove(v)}
|
||||
disabled={isPending}
|
||||
className="rounded p-0.5 text-[#c4c4c8] transition-colors hover:bg-red-50 hover:text-red-500 disabled:opacity-50"
|
||||
aria-label={`Elimina ${v}`}
|
||||
title={`Elimina "${v}" ovunque`}
|
||||
<div className="mb-4 flex flex-wrap gap-1.5">
|
||||
{pool.length === 0 ? (
|
||||
<div className="w-full py-6 text-center text-xs italic text-tertiary">
|
||||
Nessun valore inserito
|
||||
</div>
|
||||
) : (
|
||||
pool.map((v) => (
|
||||
<span
|
||||
key={v}
|
||||
className="inline-flex items-center gap-1 rounded-md border border-border/60 bg-card py-1 pl-2.5 pr-1.5 text-[11px] font-semibold text-foreground shadow-sm"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
{v}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemove(v)}
|
||||
disabled={isPending}
|
||||
className="rounded p-0.5 text-tertiary transition-colors hover:bg-destructive/10 hover:text-destructive disabled:opacity-50"
|
||||
aria-label={`Elimina ${v}`}
|
||||
title={`Elimina "${v}" ovunque`}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
setError(null);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Aggiungi valore"
|
||||
disabled={isPending}
|
||||
aria-label={`Aggiungi valore a ${label}`}
|
||||
className="h-8 w-full rounded-md border border-[#e5e7eb] bg-white pl-3 pr-9 text-sm text-[#1a1a1a] placeholder:text-[#a1a1aa] transition-shadow focus:border-[#1A463C]/30 focus:outline-none focus:ring-2 focus:ring-[#1A463C]/15 disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAdd}
|
||||
disabled={isPending || !canAdd}
|
||||
aria-label={`Conferma aggiunta a ${label}`}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 grid h-6 w-6 place-items-center rounded text-[#1A463C] transition-colors hover:bg-[#1A463C]/10 disabled:text-[#c4c4c8] disabled:hover:bg-transparent"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{error && <p className="mt-1.5 text-xs text-red-500">{error}</p>}
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
setInput(e.target.value);
|
||||
setError(null);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Aggiungi valore..."
|
||||
disabled={isPending}
|
||||
aria-label={`Aggiungi valore a ${label}`}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-xs text-foreground transition-colors placeholder:text-tertiary focus:border-primary focus:outline-none disabled:opacity-50"
|
||||
/>
|
||||
{error && <p className="mt-1.5 text-xs text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,26 +25,28 @@ function Section({
|
||||
subtitle,
|
||||
fields,
|
||||
pools,
|
||||
gridClass,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
fields: FieldDef[];
|
||||
pools: Record<string, string[]>;
|
||||
gridClass: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-[#e5e7eb] p-6">
|
||||
<div className="flex items-start gap-3 mb-5">
|
||||
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-lg bg-[#1A463C]/8 text-[#1A463C]">
|
||||
<section className="rounded-xl border border-border-light bg-card p-6 shadow-card">
|
||||
<div className="mb-6 flex items-start gap-3">
|
||||
<div className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-border-light bg-muted text-muted-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-[#1a1a1a] leading-tight">{title}</h2>
|
||||
<p className="text-xs text-[#71717a] mt-0.5">{subtitle}</p>
|
||||
<h2 className="text-xs font-bold uppercase tracking-wider text-foreground">{title}</h2>
|
||||
<p className="mt-0.5 text-xs text-tertiary">{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div className={`grid gap-6 border-t border-border-light pt-4 ${gridClass}`}>
|
||||
{fields.map((f) => (
|
||||
<PoolManager
|
||||
key={f.id}
|
||||
@@ -55,28 +57,30 @@ function Section({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function TaxonomyManager({ pools }: { pools: Record<string, string[]> }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
<Section
|
||||
icon={<Tag className="h-[18px] w-[18px]" />}
|
||||
icon={<Tag className="h-5 w-5" />}
|
||||
title="Tassonomie Offerte"
|
||||
subtitle="Valori dei campi Categoria, Ticket, Tipo e Obiettivo dell'editor offerte."
|
||||
fields={OFFER_FIELDS}
|
||||
pools={pools}
|
||||
gridClass="grid-cols-1 md:grid-cols-2"
|
||||
/>
|
||||
<Section
|
||||
icon={<BookOpen className="h-[18px] w-[18px]" />}
|
||||
icon={<BookOpen className="h-5 w-5" />}
|
||||
title="Tassonomie Catalogo"
|
||||
subtitle="Valori dei campi Fase, Offerta e Pacchetto del catalogo servizi."
|
||||
fields={CATALOG_FIELDS}
|
||||
pools={pools}
|
||||
gridClass="grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
||||
/>
|
||||
<p className="text-xs text-[#a1a1aa] leading-relaxed">
|
||||
<p className="max-w-4xl px-2 text-xs leading-relaxed text-tertiary">
|
||||
I valori si sincronizzano ovunque: creandone uno dall'editor offerte o dal catalogo
|
||||
comparirà qui automaticamente. Eliminando un valore da qui viene rimosso ovunque sia usato.
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user