Commit Graph

152 Commits

Author SHA1 Message Date
simone b3f781b9b5 feat(05-03): extend getProjectFullDetail + add offer assignment server actions
- Add offer_micros, project_offers imports to admin-queries.ts
- Add ProjectOfferWithMicro type to admin-queries.ts
- Extend ProjectFullDetail type with projectOffers + availableMicros fields
- Extend getProjectFullDetail() to query project offers and available micros in parallel
- Add z import and project_offers import to project-actions.ts
- Add assignOfferToProject, removeProjectOffer, updateProjectOfferTotal server actions
- All three actions call requireAdmin() and revalidate /admin/forecast
2026-05-30 16:25:58 +02:00
simone 2faf2fedf1 docs(05-02): complete offer catalog admin CRUD plan — SUMMARY.md
- RSC page at /admin/offers with macro/micro/service CRUD fully functional
- ServiceCheckboxList for many-to-many service assignment
- NavBar updated with Forecast and Offerte links
2026-05-30 16:23:15 +02:00
simone ce8f95a163 feat(05-02): /admin/offers RSC page + ServiceCheckboxList + NavBar update
- Create src/app/admin/offers/page.tsx — RSC with macro/micro/service CRUD sections
- Create src/components/admin/offers/ServiceCheckboxList.tsx — client checkbox UI for service-to-micro assignment
- Update src/components/admin/NavBar.tsx — add Forecast and Offerte links (order: Statistiche | Forecast | Catalogo | Offerte | Impostazioni)
2026-05-30 16:22:19 +02:00
simone 56f084912a feat(05-02): add offer-queries.ts and server actions for offer catalog CRUD
- Create src/lib/offer-queries.ts with getCatalogWithMicros, getAllOfferServices, getMicroAssignedServiceIds
- Create src/app/admin/offers/actions.ts with createMacro, deleteMacro, createMicro, deleteMicro, createOfferService, toggleOfferServiceActive, updateMicroOfferServices
- All exported server actions guard with requireAdmin() + revalidatePath(/admin/offers)
2026-05-30 15:35:54 +02:00
simone d670d49048 docs(05-01): create SUMMARY.md for Phase 5 Plan 1 — offer schema migration
- Documents schema.ts changes (5 new tables, relations, TypeScript types)
- Documents Phase 4+5 DB migration applied via SSH due to drizzle-kit TTY limitation
- Confirms zero data loss; all 5 clients and related data preserved
2026-05-30 15:33:42 +02:00
simone 1b0b2eab73 chore(05-01): push Phase 4+5 schema migration to production DB via SSH
- Created projects (5 rows from clients), settings tables
- Added project_id to phases (9), payments (10), documents (1), quote_items (3) via client_id pivot
- Added slug to clients
- Created offer_macros, offer_micros, offer_services, offer_micro_services, project_offers
- Migration applied via direct SQL on clienthub-db container (drizzle-kit TTY limitation bypassed)
- Zero rows deleted; all existing data preserved
2026-05-30 15:32:58 +02:00
simone f0034410ff feat(05-01): append five offer tables + Drizzle relations to schema.ts
- Add primaryKey to drizzle-orm/pg-core import
- Define offer_macros, offer_micros, offer_services, offer_micro_services, project_offers
- Add Drizzle relations for all five new tables (+ projectOffers in projectsRelations)
- Export TypeScript infer types for all new tables
2026-05-30 15:26:02 +02:00
simone c83e5e5a7e fix(05): apply checker revisions — OFFER-04 client coverage, RESOLVED questions, wave fix, verify fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 15:14:00 +02:00
simone cd55cba56c docs(05): create Phase 5 Offer System plans — 4 plans in 3 waves
Wave 1: 05-01 schema migration (5 new tables, additive only)
Wave 2: 05-02 offer catalog CRUD (/admin/offers + NavBar)
Wave 3: 05-03 project offer assignment (OffersTab) + 05-04 client dashboard offers + /admin/forecast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 15:07:52 +02:00
simone 75097ccfa4 feat: init payments + split payment in project workspace
- initProjectPayments: crea Acconto 50%/Saldo 50% se il progetto non ha pagamenti
- splitPayment: divide un pagamento in due rate con importi custom
- SplitPaymentForm: form client interattivo con anteprima live della rata 2
- PaymentsTab: stato vuoto con CTA + pulsante Dividi per ogni riga
- projectId prop opzionale per attivare le feature solo nel contesto progetto
2026-05-23 10:18:06 +02:00
simone 0571040d2f docs: add Data Safety rule to CLAUDE.md — updates must not delete clients/projects/payments/phases 2026-05-23 09:15:38 +02:00
simone 5efb433c23 fix(04-07): remove debug code — public page restored
Root cause: INTERNAL_SECRET was on same line as HOST= in .env (no trailing
newline when appended), so proxy middleware got empty secret and rejected
all /api/internal/validate-* calls → rewrote every /client/* request to
/not-found without running the page component.
2026-05-23 09:03:06 +02:00
simone 25ae19c9d2 debug: page-level file logging 2026-05-22 20:34:19 +02:00
simone d67be3b6b4 debug: file-based logging to /tmp/clienthub-debug.log 2026-05-22 20:18:31 +02:00
simone bc2fcc8670 debug: add generateMetadata log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:54:58 +02:00
simone fca86fde73 debug: log token param in ClientPage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:44:56 +02:00
simone 78a65f6ced debug: log tokenOrSlug in getClientWithProjectsByToken
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:40:18 +02:00
simone 7f9150b75a debug: add error logging to getClientWithProjectsByToken
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:38:41 +02:00
simone a478462aa4 security: full hardening pass — auth guards, rate limiting, headers, internal secret
- Add requireAdmin() to all unprotected admin server actions
  (clients/new, clients/[id], timer-actions — 17 functions total)
- Protect /api/internal/* endpoints with X-Internal-Secret header
  (proxy.ts sends it; routes reject requests without it)
- Randomize auto-generated client slugs with 4-char suffix
  to prevent enumeration via predictable name-based slugs
- Add in-memory rate limiting to /api/client/approve (20/min)
  and /api/client/comment (10/min) per IP
- Add security headers: X-Frame-Options, X-Content-Type-Options,
  Referrer-Policy, Permissions-Policy, X-DNS-Prefetch-Control
- Reduce JWT session from 30 days to 7 days with daily rotation
- Remove hardcoded NEXTAUTH_URL from Dockerfile (pass via Coolify env)
- Genericize client API error messages to not leak data structure
- Update .env.example with all required variables including INTERNAL_SECRET

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:36:16 +02:00
simone eab88c9f63 fix(04-06): five post-deploy fixes
- Middleware (proxy.ts): switch internal API calls to localhost instead of
  request.url — avoids Docker hairpin NAT issues where the container can't
  reach its own external hostname via Traefik
- ProjectRow timer: pass projectId={project.id} to TimerCell so it calls
  startTimer(projectId) directly instead of startTimerForClient(project.id)
- Admin client list: add slug field to ClientWithPayments + show slug link
  when available (falls back to token) — so admins see the human-readable URL
- Analytics contracted: sum projects.accepted_total (authoritative) instead
  of clients.accepted_total (always 0 in multi-project architecture)
- createClient: auto-generate slug from client name at creation time
  (e.g. "Mario Rossi" → "mario-rossi"), with -2/-3 suffix on conflict

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:42:38 +02:00
simone e6a9774cfe fix(04-05): three post-deploy bug fixes
- Admin client list: remove Timer column, rename Acconto→Importo incassato
  and Saldo→Importo da saldare (calculated from payment amounts, not badges)
- Project workspace timer: pass projectId prop to TimerCell so it calls
  startTimer(projectId) directly instead of startTimerForClient(projectId)
  which was failing with "nessun progetto trovato"
- Public client page: pass client.token (real DB token) not the URL path
  segment (which may be a slug) to ClientDashboard → fixes approve/chat APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:13:31 +02:00
simone 4d8678b1a3 docs(04-04): capture phase summary — slug resolution + multi-project dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:37:37 +02:00
simone 5d785a1c1c feat(04-04): slug resolution + multi-project client dashboard + slug edit
- validate-slug API route: resolves clients.slug for Edge middleware
- proxy.ts: slug-first resolution (D-06) — tries slug then falls back to token
- client-view.ts: complete rewrite — getClientWithProjectsByToken + getProjectView
  - No quote_items, no payment amounts in client API (CLAUDE.md security invariants)
- client/[token]/page.tsx: multi-project dashboard — 1 project = direct view,
  2+ projects = shadcn Tabs with project names (D-09/D-10)
- edit/page.tsx: slug field with link preview + unique constraint error handling
- actions.ts: updateClient now persists slug, redirects on success/slug_taken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:31:32 +02:00
simone 1d9fa10961 feat(04-03): project workspace + timer analytics + settings page
- /admin/projects/[id]: full workspace with 7 tabs (Fasi, Pagamenti,
  Documenti, Note, Commenti, Preventivo, Timer)
- TimerTab: TimerCell + ProfitabilityCard with €/h real, ideal cost, delta
- ProfitabilityCard: hours worked, €/h real vs target, profit/loss delta
- /admin/impostazioni: target_hourly_rate setting (default 50€/h)
- actions.ts: resolveEntity() helper — actions now work transparently
  with both clientId and projectId (same tab components reused)
- quote-actions.ts: same resolveEntity pattern for addQuoteItem,
  removeQuoteItem, updateAcceptedTotal
- timer-actions.ts: revalidate /admin/projects and /admin/projects/[id]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:05:31 +02:00
simone ef05d647fe feat(04-02): admin projects list + client detail project cards
- NavBar: add Progetti (/admin/projects) and Impostazioni links
- ProjectRow: project table row with €/h calc (accepted_total / tracked hours)
- /admin/projects: table listing all projects with value, payments, timer, €/h
- /admin/projects/new: create form with client select + ?client_id pre-selection
- project-actions: createProject, archiveProject, unarchiveProject, updateProjectAcceptedTotal
- /admin/clients/[id]: rewritten to show project cards grid instead of tabbed workspace
- getAllClientsWithPayments: add projectBrands[] and ltv (sum of project accepted_totals)
- ClientRow: show project brand names under client name, LTV column replaces Totale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:45:10 +02:00
simone b92b1c447b fix(db): skip DATABASE_URL check during Next.js build phase
NEXT_PHASE=phase-production-build is set by Next.js during static analysis.
Route handlers are analyzed but not executed, so the DB client is never
actually used — the placeholder URL never connects to anything.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:11:36 +02:00
simone 63c9f750df feat(04-01): multi-project schema migration — projects, settings, FK pivot
- schema.ts: add projects table, settings kv table, slug on clients;
  migrate 6 FK from client_id to project_id (phases, payments, documents, notes,
  time_entries, quote_items); update all relations and TypeScript types
- admin-queries.ts: fix getAllClientsWithPayments + getClientFullDetail to aggregate
  through projects; add getAllProjectsWithPayments, getProjectFullDetail,
  getClientWithProjects, ClientWithProjects type
- settings.ts: new file — getSetting, updateSetting, getTargetHourlyRate, SETTINGS_KEYS
- Fix all downstream files: actions.ts, quote-actions.ts, new/actions.ts,
  timer-actions.ts, approve/route.ts, comment/route.ts, TimerCell.tsx,
  analytics-queries.ts, client-view.ts, seed.ts
- DB migration applied to Coolify Postgres (all test data cleared, schema rebuilt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:58:15 +02:00
simone 44d4fde0a5 fix(docker): npm ci --include=dev to install devDeps in build stage
Coolify injects ARG NODE_ENV=production causing npm to skip devDependencies.
@tailwindcss/postcss is a devDependency required at build time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 17:20:21 +02:00
simone 5bf5dfce71 infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy
- Rename src/app/c/[token] → src/app/client/[token]
- Update proxy.ts, ClientRow, admin client detail with /client/ path
- Add output: "standalone" to next.config.ts for Docker build
- Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore
- Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓)
- Update CLAUDE.md constraint 4 to reflect /client/ route
- Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:12:05 +02:00
simone 49ef45da83 fix(04): revision 1 — depends_on format + D-12 client list coverage
- 04-02/03/04: change depends_on from filename format to plan ID format
  ('04-01-PLAN.md' → '04-01') to match orchestrator expectations and
  Phase 3 precedent
- 04-02: add Task 3 implementing D-12 — /admin/clients list shows brand
  names below client name and LTV as sum of project accepted_totals;
  update getAllClientsWithPayments query and ClientRow component;
  add src/app/admin/page.tsx, src/lib/admin-queries.ts,
  src/components/admin/ClientRow.tsx to files_modified

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:10:25 +02:00
simone d210cf6202 docs(04): create phase plan — 4 plans in 2 waves for multi-project architecture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:02:56 +02:00
simone 3e3b34bbe2 docs(04): capture phase context — Progetti multi-project per cliente 2026-05-20 19:16:08 +02:00
Simone Cavalli 67e4483b48 docs(phase-03): complete phase execution — service catalog + quote builder verified 2026-05-19 23:12:59 +02:00
Simone Cavalli fe0a65ebeb docs(03-04): complete E2E verification — all tests approved, phase 3 verified 2026-05-19 23:06:26 +02:00
Simone Cavalli 2aade028b8 fix(css): exclude .planning/ from Tailwind v4 source scan — prevents [-:|] regex comment from generating invalid CSS 2026-05-19 23:04:34 +02:00
Simone Cavalli 511c7d1d43 fix: resolve merge conflict in admin-queries.ts — keep both quote_items and service_catalog imports 2026-05-17 11:50:38 +02:00
Simone Cavalli 932a82b9e4 chore: merge executor worktree (worktree-agent-a31348f014c34118e) — plan 03-02 2026-05-17 11:46:51 +02:00
Simone Cavalli 8641253e85 docs(03-03): complete quote builder plan summary
- SUMMARY.md for plan 03-03 (2/2 tasks complete)
- Covers quote-actions.ts, QuoteTab.tsx, admin-queries extension, page.tsx wiring
- Security verification: requireAdmin on all actions, 0 quote_items in client-view
2026-05-17 11:46:11 +02:00
Simone Cavalli f1ea4c3887 docs(03-02): complete service catalog plan — NavBar + page + actions + components
- SUMMARY.md for plan 03-02 with all task commits, decisions, deviations
2026-05-17 11:45:09 +02:00
Simone Cavalli 48f81e7110 feat(03-03): QuoteTab component + Preventivo tab in client detail page
- Create QuoteTab.tsx: catalog dropdown + freeform toggle + items table + accepted total editor
- Wire QuoteTab as 5th tab (Preventivo) in /admin/clients/[id]/page.tsx
- Destructure quoteItems + activeServices from getClientFullDetail result
- TypeScript clean, build passes
2026-05-17 11:44:57 +02:00
Simone Cavalli 4aae2e0d0f feat(03-02): catalog page + ServiceTable + ServiceForm + NavBar link
- Create src/app/admin/catalog/page.tsx: server component, fetches all services, renders ServiceForm + ServiceTable
- Create src/components/admin/catalog/ServiceForm.tsx: add-new-service form with open/collapse toggle
- Create src/components/admin/catalog/ServiceTable.tsx: per-row inline edit (DocumentRow pattern), Attivo/Disattivato badges, opacity-50 for inactive
- Modify src/components/admin/NavBar.tsx: add Catalogo link after Statistiche
- TypeScript clean, npm run build compiled successfully
2026-05-17 11:43:49 +02:00
Simone Cavalli db81829b85 feat(03-03): Server Actions quote CRUD + extend getClientFullDetail
- Create quote-actions.ts: addQuoteItem, removeQuoteItem, updateAcceptedTotal
- All three actions guarded by requireAdmin() + Zod validation
- Extend admin-queries.ts: QuoteItemWithLabel type, quoteItems + activeServices queries
- quote_items NEVER exposed via client-facing routes (security constraint enforced)
2026-05-17 11:42:44 +02:00
Simone Cavalli efbc235c6e feat(03-02): server actions + getAllServices query for service catalog
- Create src/app/admin/catalog/actions.ts with createService, updateService, toggleServiceActive
- Each action includes requireAdmin() guard via getServerSession
- Zod validation: name (min 1), unit_price (coerce.number min 0.01)
- Add getAllServices() to src/lib/admin-queries.ts ordered by name
- Import service_catalog and ServiceCatalog types in admin-queries.ts
2026-05-17 11:41:55 +02:00
Simone Cavalli 29bfd88255 chore: merge executor worktree (worktree-agent-a783b1d42e86fb23a) — plan 03-01 2026-05-17 11:39:29 +02:00
Simone Cavalli 8c83a76b30 docs(03-01): complete schema migration plan — nullable service_id + custom_label pushed to Neon
- SUMMARY.md created with full task record, self-check passed
- Task 1 commit: 9ddb699 (schema edit)
- Task 2: drizzle-kit push confirmed changes applied, second run "No changes detected"
2026-05-17 11:39:04 +02:00
Simone Cavalli 9ddb69929a feat(03-01): make quote_items.service_id nullable and add custom_label column
- Remove .notNull() from service_id to allow free-form items without catalog ref
- Add custom_label: text("custom_label") for free-form item label storage
- TypeScript compiles with zero errors (QuoteItem.service_id now string | null)
2026-05-17 11:36:43 +02:00
Simone Cavalli a4942d7684 docs(03): plan Phase 3 — Service Catalog & Quote Builder (4 plans, 2 waves)
Wave 1: schema push (service_id nullable + custom_label).
Wave 2 (parallel): catalog CRUD page + quote builder tab.
Wave 3: E2E human verification checkpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:23:15 +02:00
Simone Cavalli 31845b471b chore: rimuovi progetti annidati (ContactMe, SparklingOrbit) da ClientHub
Spostati come repository indipendenti a livello ~/ContactMe e ~/SparklingOrbit.
Rimossi i gitlink orfani (mode 160000 senza .gitmodules).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:47:07 +02:00
Simone Cavalli e4d4dfa5be fix: remove brief from public client dashboard (internal data only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:38:56 +02:00
Simone Cavalli 0f48570cd7 feat: client edit/delete/archive + time tracker + analytics time section
Schema:
- clients.archived boolean (default false)
- time_entries table (client_id, started_at, ended_at, duration_seconds)

Client management:
- /admin/clients/[id]/edit — form pre-compilato con nome, brand, brief
- ClientActions: Modifica / Archivia / Elimina con doppia conferma
- setClientArchived: toggle archiviazione senza perdere dati
- deleteClient: elimina con cascade, redirect a /admin
- Admin list: toggle "Mostra archiviati" via ?archived=1, righe archiviate opache

Time tracker:
- startTimer: crea sessione, ferma automaticamente quella precedente
- stopTimer: chiude sessione, calcola duration_seconds
- TimerCell: ▶/⏹ per ogni cliente, contatore live in secondi, totale cumulativo
- Una sola sessione attiva alla volta su tutta la lista

Analytics:
- Sezione "Fatturato" (invariata) + sezione "Tempo tracciato" separata
- Ore totali per anno + barre orizzontali per cliente
- getTotalTrackedHours, getTimeByClient queries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:28:01 +02:00