5 Commits

Author SHA1 Message Date
Vasyka 80c3834263 feat: calendar enhancements — view modes, post CRUD, PDF, list
Closes 5 user-requested features in /app/calendar-board:

1. View mode switcher: Zi / Săpt / Lună / Custom / Listă
2. Editable post names + assignable default master per bay
3. Quick-add bay (+ Pod nou) from calendar toolbar — supports yard
   spaces without a lift ("Curte 1", "Atelier electric")
4. PDF export of programări for printing
5. Inline list view alongside the matrix view

== View modes ==
$viewMode: day | week | month | custom | list

- Day view: 1 column, just today (or navigated day). Shift moves day by day.
- Week view: current 7-column matrix (unchanged default).
- Month view: 30/31 columns shown smaller (70px each). Shift moves by month.
- Custom: 2 date pickers for arbitrary start..end range (max 31 days).
- List view: flat sortable table with Data/Ora/Subiect/Client/Telefon/
  Auto/Pod/Maistru/Status columns. Click row → opens detail panel.

getDays() computes the right day count + start anchor for each mode.
setViewMode() snaps weekStart to the right anchor (startOfMonth, today,
startOfWeek). shiftWeek delta semantics adapt: day mode shifts 1 day,
month mode shifts 1 month, others shift 7 days.

== Editable posts + default master ==
New PostResource (/app/posts) in Admin group: full CRUD with name,
color, hours_per_day, default_master_id, description, is_active,
sort_order. Gated by ADMIN_SETTINGS_EDIT.

Migration: posts.default_master_id FK → users (nullOnDelete).

Inline rename from calendar: click any post's row label opens a modal
with name field + default master dropdown. Saved values propagate
immediately to next appointment creation.

Auto-fill in new appointment: when creating an appointment via the "+"
cell button on a post row, master_id is pre-filled from
post.default_master_id (if not already set by groupBy='master' row).

== Quick-add bay ==
"+ Pod nou" button in toolbar opens a small modal (no full page nav):
name, color picker, hours/day, description. createPost() saves and
refreshes the row list. Designed for "yard space" use-cases — names
like "Curte 1" or "Atelier electric" are first-class, not workarounds.

== PDF export ==
"🖨 PDF programări" button calls exportPdf() which uses the existing
dompdf integration (already installed). Renders pdf/appointments.blade.php
grouped by day with table per day showing time/title/client+vehicle/
post/master/status. Romanian date headers ("Marți, 10 Iunie 2026").
streamDownload with filename programari_YYYY-MM-DD_YYYY-MM-DD.pdf.

== List view ==
getListAppointments() returns flat array of all appointments in the
visible period (date-range respects current viewMode), with full
client/vehicle/post/master joined. Status filter respected. Row click
opens the existing event detail panel.

== Tests ==
CalendarEnhancementsTest (8):
- viewMode='day' returns 1 day
- viewMode='month' returns 30 days for June 2026
- viewMode='custom' uses customStart..customEnd range
- quick-add post via Livewire createPost persists with all fields
- rename post updates name + default_master_id
- new appointment auto-fills master_id from post's default_master_id
- list view returns flat array with phone + post name joined
- exportPdf returns StreamedResponse with .pdf filename

Suite: 285 passed (802 assertions). Was 277.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 07:34:27 +00:00
Vasyka 1d5ea6d261 feat: Calendar Vizual v2 (Pod×Days matrix) + hidden markup
Implements 2 of the biggest items from /tmp/service/new docs:

== Calendar Vizual v2 (from 02-prototip-calendar-vizual.html) ==

Replaces the FullCalendar week view (the one that visually collapsed after
Livewire re-renders) with a server-rendered matrix that the harness
already drives through Livewire — no third-party JS to clash with Filament.

Layout: 8-column CSS grid (1 row-label + 7 days). Rows are either Posts
(Pod 1, Pod 2…) or active masters depending on toolbar switch. Each
cell holds 0..N event cards.

Per-cell load badge (top-right):
  hours_planned / capacity  →  badge color (gray <50%, orange 50–90%, red ≥90%)

Drag-drop: HTML5 native, Alpine.js holds the dragEventId, moveEvent($id,
$toRowId, $toDate) in PHP updates either post_id or master_id (depending
on groupBy mode) plus date — works seamlessly when re-grouping.

KPI bar (4 cards above toolbar):
- Ore programate X / Y · % capacity
- Fișe deschise (orange)
- Confirmate X/Y (green) + confirmation rate
- No-show alert (red) — scheduled events <24h away that are still unconfirmed

Toolbar:
- ◀ Week ▶ + Astăzi (reset)
- Date label "01 — 07 iunie 2026"
- Grupare switch: Pod ↔ Mecanic
- Filtru: master dropdown + status dropdown (Confirmate/Neconfirmate/În lucru)

Today column highlighted blue; Sunday column hatched as closed
(non-interactive, no drop target); Saturday muted as weekend.

Event card color = master.color (deterministic, matches profile setting),
shown as left border + background tint. Title = client name; meta =
"VW Passat · CIU 001"; time = "08:00–12:00 · V.".

Click empty cell → quick-create panel (right slide-in) with date+pod
pre-filled. Click event → detail panel with Client/Phone/Auto/Plate/
Master/Pod + delete + edit.

Legend section at bottom (mecanici dots, load colors, day states).

== Hidden Markup (from gap-analysis.md #3) ==

Adds `hidden_markup_pct` decimal to parts. Customer documents continue
to show the standard sell_price; the hidden markup is an internal margin
indicator used for B2B contracts and corporate analytics.

Part::internalCostWithHiddenMarkup() returns buy_price * (1 + pct/100).
Falls back to buy_price when pct is null. Decimal:2 cast so persistence
round-trips cleanly.

== Schema migration ==

Idempotent (hasColumn guards):
- posts.hours_per_day decimal(5,1) default 10
- posts.description varchar(255) nullable
- parts.hidden_markup_pct decimal(5,2) nullable

== Tests ==

+11 new in CalendarBoardV2Test (8) + HiddenMarkupTest (3):
- get_days returns 7 days with today flagged + Sunday closed + Saturday weekend
- get_rows returns posts when grouped by post + with capacity
- get_rows returns masters when grouped by master + Fără maistru fallback row
- matrix places events in correct cells + sums hours
- move_event reassigns post_id and date
- create_appt inserts appointment via panel form
- stats compute utilization from events (8h / 60h capacity = 13%)
- status filter narrows to confirmed only
- hidden_markup applies pct correctly + falls back to buy_price + persists

Suite: 196 passed (551 assertions). Was 185.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 21:50:22 +00:00
Vasyka c84ef5d9bd fix: calendar collapses to plain text after first render
FullCalendar mounts into a Livewire-managed subtree. The first
$wire.getEvents() response triggered a Livewire DOM morph that reverted
#autocrm-calendar to its empty server HTML, destroying the rendered grid
(~1s after load it became unstyled text).

Wrap the calendar container in wire:ignore so Livewire's morphdom skips it.
The quick-create modal stays outside wire:ignore to keep its form reactive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 22:58:55 +00:00
Vasyka 59409e1b11 fix: calendar — register createForm via getForms() + remove broken FullCalendar CSS link
- Filament v5 multi-form pages need protected getForms() returning form names
- FullCalendar v6 bundles CSS in JS — separate <link> 404'd silently
- Added Russian locale alongside Romanian for i18n switcher
2026-05-08 11:27:04 +00:00
Vasyka bfe58ed286 Faza 1 (din lista de continuare): Calendar vizual cu FullCalendar 6
- Custom Filament Page CalendarBoard la /app/calendar-board (group CRM)
- FullCalendar 6.1.15 din CDN + locale RO
- View-uri: zi (timeGridDay), săptămână (timeGridWeek default), lună
- Programări colorate per maistru (din User.color)
- Live event loading: Livewire $wire.getEvents(start, end)
- Drag-drop reschedule: eventDrop → $wire.moveEvent(id, start, end)
- Resize event (extinde durată): eventResize
- Click slot gol → quick-create modal cu form Filament populat cu data/timpul
  - Câmpuri: title, time_start/end, client (live), vehicle (filtrat după client),
    master, post, notes
- Click event → confirm + delete
- Toolbar: prev/next/today + month/week/day switch
- 07:00–21:00 grid cu sloturi 30 min, today indicator live
- Modal stilizat (CSS scoped) cu close button + ESC

Total tenant routes: 93.
2026-05-07 13:10:27 +00:00