80c3834263
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>
66 lines
2.9 KiB
PHP
66 lines
2.9 KiB
PHP
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Programări {{ $periodLabel }}</title>
|
||
<style>
|
||
@page { margin: 18mm 14mm; }
|
||
body { font-family: DejaVu Sans, sans-serif; font-size: 11px; color: #1a202c; }
|
||
h1 { font-size: 18px; margin: 0 0 6px; color: #1a202c; }
|
||
.sub { color: #718096; font-size: 11px; margin-bottom: 14px; }
|
||
h2 { font-size: 14px; margin: 16px 0 8px; padding: 4px 8px; background: #ebf5ff; border-left: 3px solid #3b82f6; }
|
||
table { width: 100%; border-collapse: collapse; }
|
||
th { text-align: left; background: #f7fafc; font-size: 10px; padding: 6px 8px; border-bottom: 2px solid #e2e8f0; }
|
||
td { padding: 6px 8px; border-bottom: 1px solid #edf2f7; vertical-align: top; }
|
||
.time { font-weight: 700; color: #2563eb; white-space: nowrap; width: 60px; }
|
||
.client-name { font-weight: 600; }
|
||
.meta { color: #718096; font-size: 10px; }
|
||
.footer { margin-top: 24px; padding-top: 8px; border-top: 1px solid #e2e8f0; font-size: 10px; color: #94a3b8; }
|
||
.empty { padding: 20px; text-align: center; color: #94a3b8; font-style: italic; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Programări — {{ $periodLabel }}</h1>
|
||
<div class="sub">Generat la {{ $generatedAt }} · {{ $appointments->flatten()->count() }} programări total</div>
|
||
|
||
@forelse ($appointments as $date => $dayAppts)
|
||
@php $dateLabel = \Carbon\Carbon::parse($date)->locale('ro')->isoFormat('dddd, D MMMM YYYY'); @endphp
|
||
<h2>{{ ucfirst($dateLabel) }} · {{ $dayAppts->count() }} programări</h2>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th style="width:60px;">Ora</th>
|
||
<th>Subiect</th>
|
||
<th>Client / Auto</th>
|
||
<th>Pod</th>
|
||
<th>Maistru</th>
|
||
<th style="width:60px;">Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach ($dayAppts as $a)
|
||
<tr>
|
||
<td class="time">{{ substr($a->time_start ?? '', 0, 5) }}–{{ substr($a->time_end ?? '', 0, 5) }}</td>
|
||
<td>{{ $a->title }}@if ($a->notes)<div class="meta">{{ $a->notes }}</div>@endif</td>
|
||
<td>
|
||
<div class="client-name">{{ $a->client?->name ?? '—' }}</div>
|
||
<div class="meta">
|
||
@if ($a->vehicle?->make){{ $a->vehicle?->make }} {{ $a->vehicle?->model }}@endif
|
||
@if ($a->vehicle?->plate) · {{ $a->vehicle->plate }}@endif
|
||
</div>
|
||
</td>
|
||
<td>{{ $a->post?->name ?? '—' }}</td>
|
||
<td>{{ $a->master?->name ?? '—' }}</td>
|
||
<td>{{ ucfirst($a->status) }}</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
@empty
|
||
<div class="empty">Nicio programare în această perioadă.</div>
|
||
@endforelse
|
||
|
||
<div class="footer">AutoCRM PSauto · psauto.service.mir.md</div>
|
||
</body>
|
||
</html>
|