Commit Graph

18 Commits

Author SHA1 Message Date
mivanchenko b84a5d4c6f Dashboard: full-width tables, no horizontal scrollbar
Remove the 1280px content cap and the table's overflow scroll; let cells wrap
(word-break) instead of forcing a horizontal scrollbar, so rows span the full
screen width and show full values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:41:21 +02:00
mivanchenko 10fea59e05 Fix dashboard 404: derive API base from page path
Opening the dashboard at /crm (no trailing slash) made the relative `api/leads`
fetch resolve to /api/leads -> 404. Derive the API base from
location.pathname so it works at both /crm and /crm/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:39:34 +02:00
mivanchenko d5a304ebe9 n8n ingest swap: lead-intake & onboarding write to the DB
Both flows now POST into the smb-crm DB API instead of appending to Sheets, so
Postgres stays the source of truth and the DB->Sheets mirror keeps the Sheet
current. Telegram notifications unchanged.

- lead-intake: Webhook -> Build lead row -> POST /api/leads -> Telegram
  (dropped the direct Sheets append + the redundant activity-log nodes; the DB
  service logs + mirrors activity itself).
- onboarding: Webhook -> Compute -> POST /api/clients (DB assigns C-id) ->
  Build project w/ returned id -> POST /api/projects -> Telegram. DB computes
  renewal_date; go-live = start + 14d.
- Token injected at deploy time; repo keeps the __CRM_TOKEN__ placeholder.

Verified end-to-end on the live webhooks: lead and client+project land in the
DB and mirror to Sheets; test data cleaned; DB/Sheets consistent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:33:53 +02:00
mivanchenko e59996a385 Back office: one-way DB -> Sheets mirror
- After each mutation, the affected tab + activity_log are pushed to Sheets by
  a single serialized background worker (Postgres stays source of truth; a
  mirror failure never fails the DB write). Concurrency race fixed by
  serializing through one worker; PYTHONUNBUFFERED for visible logs.
- _cell() formats dates/timestamps/numbers/bools and neutralises formula
  injection. POST /api/sync does a full DB->Sheets resync of every tab.

Verified: add/edit/delete propagate to the Sheet; full resync aligns all tabs;
DB and Sheet consistent after cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:26:42 +02:00
mivanchenko 9f42e46d6c Back office: add / edit / delete for Leads & Clients
- Token-protected mutation endpoints (POST/PATCH/DELETE), audit-logged to
  activity_log. Token injected into the basic-auth-gated dashboard.
- Add auto-generates IDs (next C-#### / L-<epoch>), sets created/received/status
  defaults, and computes client renewal_date from start + billing cycle
  (parity with the onboarding workflow).
- Dashboard: per-row edit (✎) and delete (🗑), "+ Neu" modal form per entity.

Verified end-to-end: add lead/client, edit, delete, renewal compute, token
gating (403), 404s. DB-only for now; DB->Sheets mirror is the next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:18:28 +02:00
mivanchenko 18e346d67b Back office: Postgres source-of-truth + read dashboard
Stand up the DB-first CRM backbone (architecture pivot: Postgres is the
source of truth, Google Sheets becomes a one-way downstream mirror).

- backoffice/ stack: smb-db (Postgres 16) + smb-crm (Flask/waitress service).
- Schema mirrors the six Sheet tabs (clients, leads, projects, activity_log,
  bookings, invoices) with typed columns + updated_at triggers.
- Service-account Sheets client (PyJWT) for the one-time import + future mirror.
- import_from_sheets.py: idempotent seed of Postgres from the live Sheets.
- Read dashboard (Leads & Clients tables) at onboard.mivanchenko.de/crm,
  behind the existing Caddy basic-auth; JSON API reads straight from Postgres.

Deployed + verified: import seeded DB, dashboard/API live, no-auth blocked,
onboarding form unaffected. Add/edit/delete + DB->Sheets sync + n8n ingest
swap are the next steps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:12:39 +02:00
mivanchenko a04ad82e73 Neutralise demo pages: stack-agnostic copy, non-stale dates
- Demo bars now read "Beispielseite · So könnte Ihr Online-Auftritt aussehen"
  (was "Tier A (Google-Stack)" / "Tier B (Self-Hosted)" / "für Gäste").
- Booking widgets relabelled generic: "Online-Terminbuchung" / "Sichere Buchung"
  (dropped Google Kalender + branded dots, Cal.com, self-hosted).
- Removed Tier/Google/Cal.com refs from rendered copy AND HTML comments.
- Demo booking dates now render on the current week via JS (no stale dates).
- TODO.md: tick off completed items; add onboard back-office (Leads/Clients CRUD).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:47:22 +02:00
mivanchenko cde0cb0fa8 Add pizzeria demo w/ online ordering; fix gcal dots; reword chooser
- New demo-pizzeria (Bella Napoli): interactive menu -> cart (qty +/-,
  live total) -> order form (pickup/delivery, address, time, notes) that
  POSTs the order summary to the lead-intake webhook (client_id
  DEMO-PIZZA, source order-pizzeria). Verified end-to-end (~5s).
- Fix barbershop "Google Kalender" header: the 3 dots were faked via
  box-shadow with no layout width, so text overlapped them. Reserve space.
- Chooser: client-friendly wording (no "self-hosted"/Cal.com jargon, no
  "~0 EUR"), reframed as example businesses, 3-up responsive layout.
- TODO.md: decouple tier from business type; demos as stack-agnostic
  starters; hosting (Google vs self-host) decided per client. Plus notes
  on n8n auth hardening and a real Orders flow/tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:32:01 +02:00
mivanchenko e151bbd734 Add Rückruf (callback) lead popup to chooser + both demos
Floating button + centered modal on every page, opened from the demo
bar and a fixed FAB. Minimal required fields (name + phone), optional
business / what-they-want / message. POSTs to the lead-intake webhook
(client_id DEMO/DEMO-A/DEMO-B, source callback-*). Also refreshes the
stale "noch nicht aktiv" notices now that the forms are live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:11:09 +02:00
mivanchenko f1577bcefa Phase 5: add internal operator onboarding page
Self-contained admin form that POSTs to the /webhook/onboard endpoint
(creates Clients + Projects + Activity Log rows). Hosted behind HTTP
basic auth at onboard.mivanchenko.de (Caddy basic_auth -> smb-demos
container). Not linked from the public demos; creates billable records
so it stays operator-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:00:36 +02:00
mivanchenko a59a22b38b Phase 3: add onboarding workflow
Webhook /webhook/onboard → read Clients → compute next C-#### →
append Clients row (status onboarding, renewal_date auto from billing
cycle) → seed Projects row (checklist from services, +14d go-live) →
log Activity → Telegram confirmation. Sheets writes retry on fail.
Verified end-to-end (C-0002 created across all three tabs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:13:12 +02:00
mivanchenko a6eb8a415c Phase 4/5: host demos via Apache container behind Caddy
deploy/smb-demos: compose + runbook for the static demo host
(httpd on the proxy network, routed by Caddy at demos.mivanchenko.de).
Pending a DNS A record before Caddy can issue TLS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:03:25 +02:00
mivanchenko 2bd6499ae5 Phase 4: harden Sheets writes with retry-on-fail
Add retryOnFail (4 tries, 3s apart) to all Google Sheets nodes in
lead-intake and renewal-reminder so a transient API throttle/race
re-tries instead of silently dropping a row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:53:17 +02:00
mivanchenko 4b7e201b91 Phase 4: wire demo lead forms to live n8n webhook
- demo-tier-a / demo-tier-b: forms POST JSON to /webhook/lead-intake
  (hidden client_id + source, named fields), async submit with
  success/error toast.
- lead-intake webhook: enable CORS (allowedOrigins=*) so the static
  demos (different origin) can submit; re-export n8n/lead-intake.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:48:56 +02:00
mivanchenko 1e6c46e9f0 Phase 3: add renewal-reminder workflow
Daily 08:00 schedule → read Clients → flag active clients whose
renewal_date is due (14/7/3/1/0 days out or overdue) → Telegram
summary → log to Activity Log. Quiet on days with nothing due.
Verified end-to-end via CLI run against a seeded due client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:42:29 +02:00
mivanchenko 339fb1a1a7 Phase 3: add Telegram lead notification to lead-intake
- Notify Telegram node (bot @smb_leads_bot) between Leads append and
  Activity Log; notified flag set TRUE. Verified end-to-end (lead →
  Telegram + Leads row + Activity Log).
- Re-export n8n/lead-intake.json with the notify node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:36:46 +02:00
mivanchenko 0009a138a9 Phase 3: export working lead-intake workflow; gitignore .secrets
- n8n/lead-intake.json: Webhook → Build lead row (sanitized) →
  Leads append → Activity Log append. Verified end-to-end.
- .gitignore: ensure .secrets/ excluded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:15:07 +02:00
mivanchenko 5668bfad3f Phase 1: tier demo landing pages, CRM schema, playbooks
- templates/landing: two self-contained demos (Tier A barbershop / Google stack,
  Tier B physio / self-hosted) + chooser page
- sheets/SCHEMA.md: Google Sheets CRM workbook spec
- playbooks: per-client setup checklists for both tiers
- README + .gitignore
- Demos avoid unverifiable claims (no "DSGVO-konform", no "Ende-zu-Ende")

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:14:15 +02:00