diff --git a/app/Filament/Tenant/Pages/VinSearch.php b/app/Filament/Tenant/Pages/VinSearch.php
new file mode 100644
index 0000000..b333243
--- /dev/null
+++ b/app/Filament/Tenant/Pages/VinSearch.php
@@ -0,0 +1,56 @@
+vin) return;
+ $this->decoded = app(VinDecoder::class)->decode($this->vin);
+ }
+
+ public function vehicleMatches(): \Illuminate\Support\Collection
+ {
+ if (! $this->vin) return collect();
+ return Vehicle::with('client')
+ ->where('vin', 'like', '%' . $this->vin . '%')
+ ->limit(10)->get();
+ }
+
+ public function partsResults(): \Illuminate\Support\Collection
+ {
+ $q = trim($this->partsQuery);
+ if ($q === '') return collect();
+ return Part::where('is_active', true)
+ ->where(function ($query) use ($q) {
+ $query->where('name', 'like', "%{$q}%")
+ ->orWhere('article', 'like', "%{$q}%")
+ ->orWhere('brand', 'like', "%{$q}%");
+ })
+ ->limit(20)->get();
+ }
+}
diff --git a/app/Filament/Tenant/Pages/Workload.php b/app/Filament/Tenant/Pages/Workload.php
new file mode 100644
index 0000000..f0ee84c
--- /dev/null
+++ b/app/Filament/Tenant/Pages/Workload.php
@@ -0,0 +1,94 @@
+weekStart = Carbon::now()->startOfWeek()->toDateString();
+ }
+
+ public function setWeek(int $delta): void
+ {
+ $this->weekStart = Carbon::parse($this->weekStart)->addWeeks($delta)->toDateString();
+ }
+
+ public function data(): array
+ {
+ $start = Carbon::parse($this->weekStart);
+ $end = (clone $start)->addDays(6);
+
+ $posts = Post::where('is_active', true)->orderBy('sort_order')->get();
+ $days = collect(range(0, 6))->map(fn ($i) => $start->copy()->addDays($i));
+
+ // Appointments grouped by post + day.
+ $appts = Appointment::whereBetween('date', [$start, $end])
+ ->whereNotIn('status', ['cancelled', 'no_show'])
+ ->get()
+ ->groupBy(fn ($a) => $a->post_id . '|' . $a->date->format('Y-m-d'));
+
+ // Compute total hours per cell.
+ $matrix = [];
+ foreach ($posts as $post) {
+ $matrix[$post->id] = ['post' => $post, 'days' => []];
+ foreach ($days as $d) {
+ $key = $post->id . '|' . $d->format('Y-m-d');
+ $items = $appts->get($key, collect());
+ $hours = $items->sum(function ($a) {
+ $start = strtotime("1970-01-01 {$a->time_start}");
+ $end = strtotime("1970-01-01 {$a->time_end}");
+ return max(0, ($end - $start) / 3600);
+ });
+ $matrix[$post->id]['days'][$d->format('Y-m-d')] = [
+ 'date' => $d,
+ 'hours' => $hours,
+ 'count' => $items->count(),
+ ];
+ }
+ }
+
+ // Same for appointments without post (drop them on a virtual "—" row).
+ $unposted = $appts->keys()->filter(fn ($k) => str_starts_with($k, '|'));
+ if ($unposted->isNotEmpty()) {
+ $matrix[0] = ['post' => (object) ['id' => 0, 'name' => '— fără pod —', 'color' => '#9ca3af'], 'days' => []];
+ foreach ($days as $d) {
+ $key = '|' . $d->format('Y-m-d');
+ $items = $appts->get($key, collect());
+ $hours = $items->sum(function ($a) {
+ $start = strtotime("1970-01-01 {$a->time_start}");
+ $end = strtotime("1970-01-01 {$a->time_end}");
+ return max(0, ($end - $start) / 3600);
+ });
+ $matrix[0]['days'][$d->format('Y-m-d')] = ['date' => $d, 'hours' => $hours, 'count' => $items->count()];
+ }
+ }
+
+ return [
+ 'matrix' => $matrix,
+ 'days' => $days,
+ 'weekLabel' => $start->format('d.m') . ' – ' . $end->format('d.m.Y'),
+ // Working day = 10h. Color scale: 0..10h → light to red.
+ 'capacity' => 10,
+ ];
+ }
+}
diff --git a/app/Services/VinDecoder.php b/app/Services/VinDecoder.php
new file mode 100644
index 0000000..41ef404
--- /dev/null
+++ b/app/Services/VinDecoder.php
@@ -0,0 +1,59 @@
+ ['BMW', 'Germany'], 'WBS' => ['BMW M', 'Germany'],
+ 'WAU' => ['Audi', 'Germany'], 'WA1' => ['Audi SUV', 'Germany'],
+ 'WVW' => ['Volkswagen', 'Germany'], 'WV1' => ['VW Comm', 'Germany'], 'WV2' => ['VW Bus', 'Germany'],
+ 'WP0' => ['Porsche', 'Germany'], 'WP1' => ['Porsche SUV', 'Germany'],
+ 'WDB' => ['Mercedes-Benz', 'Germany'], 'WDC' => ['Mercedes SUV', 'Germany'], 'WDD' => ['Mercedes AMG', 'Germany'],
+ 'TMB' => ['Skoda', 'Czechia'],
+ 'JF1' => ['Subaru', 'Japan'], 'JS3' => ['Suzuki', 'Japan'],
+ 'JT1' => ['Toyota', 'Japan'], 'JTD' => ['Toyota Hybrid', 'Japan'],
+ 'JHM' => ['Honda', 'Japan'], '1HG' => ['Honda US', 'USA'],
+ 'KMH' => ['Hyundai', 'Korea'], 'KNA' => ['Kia', 'Korea'],
+ 'VF1' => ['Renault', 'France'], 'VF3' => ['Peugeot', 'France'], 'VF7' => ['Citroen', 'France'],
+ 'ZFA' => ['Fiat', 'Italy'], 'ZAR' => ['Alfa Romeo', 'Italy'],
+ 'WF0' => ['Ford EU', 'Germany'], '1FA' => ['Ford US', 'USA'],
+ 'YV1' => ['Volvo', 'Sweden'],
+ 'W0L' => ['Opel', 'Germany'],
+ ];
+
+ /** 10th VIN char (model year). */
+ public const YEAR_CODES = [
+ 'A' => 2010, 'B' => 2011, 'C' => 2012, 'D' => 2013, 'E' => 2014, 'F' => 2015,
+ 'G' => 2016, 'H' => 2017, 'J' => 2018, 'K' => 2019, 'L' => 2020, 'M' => 2021,
+ 'N' => 2022, 'P' => 2023, 'R' => 2024, 'S' => 2025, 'T' => 2026,
+ '1' => 2001, '2' => 2002, '3' => 2003, '4' => 2004, '5' => 2005,
+ '6' => 2006, '7' => 2007, '8' => 2008, '9' => 2009,
+ ];
+
+ public function decode(string $vin): array
+ {
+ $vin = strtoupper(trim($vin));
+ if (strlen($vin) < 11) {
+ return ['valid' => false, 'error' => 'VIN trebuie să aibă cel puțin 11 caractere.'];
+ }
+
+ $wmi = substr($vin, 0, 3);
+ $manufacturer = self::WMI[$wmi] ?? null;
+ $yearChar = $vin[9] ?? null;
+ $year = self::YEAR_CODES[$yearChar] ?? null;
+
+ // Heuristic: 70s/80s/90s vs 2000s+ — model year alone is ambiguous,
+ // we just return what we can.
+ return [
+ 'valid' => $manufacturer !== null,
+ 'vin' => $vin,
+ 'wmi' => $wmi,
+ 'manufacturer' => $manufacturer[0] ?? '?',
+ 'country' => $manufacturer[1] ?? '?',
+ 'serial' => substr($vin, 11),
+ 'year' => $year,
+ ];
+ }
+}
diff --git a/resources/views/filament/tenant/pages/vin-search.blade.php b/resources/views/filament/tenant/pages/vin-search.blade.php
new file mode 100644
index 0000000..2581372
--- /dev/null
+++ b/resources/views/filament/tenant/pages/vin-search.blade.php
@@ -0,0 +1,102 @@
+🔎 Decode VIN
+
+ ✅ VIN decodificat
+ 🚗 Mașini cu acest VIN în CRM
+
+
+
+
+ @foreach ($matches as $v)
+ VIN Marca/Model Plate Client Km
+
+ @endforeach
+
+ {{ $v->vin }}
+ {{ $v->make }} {{ $v->model }} {{ $v->year }}
+ {{ $v->plate ?? '—' }}
+ {{ $v->client?->name ?? '—' }}
+ {{ number_format($v->mileage, 0, '.', ' ') }}
+ 📦 Caută piesă în catalog
+
+
+ @php $parts = $this->partsResults(); @endphp
+ @if ($partsQuery)
+
+
+ @endif
+
+
+ @foreach ($parts as $p)
+ Denumire Cod Brand Stoc Preț
+
+ @endforeach
+
+
+ {{ $p->name }}
+
+ {{ $p->article ?? '—' }}
+ {{ $p->brand ?? '—' }}
+
+ {{ $p->qty }} {{ $p->unit }}
+
+ {{ number_format($p->sell_price, 2, '.', ' ') }} MDL
+
+
+
+
+
+
+
+ @foreach ($data['matrix'] as $row)
+
+ @foreach ($data['days'] as $d)
+ {{ $d->translatedFormat('D') }}
+ @endforeach
+
{{ $d->format('d.m') }}
+
+ @endforeach
+
+
+
+ @foreach ($row['days'] as $cell)
+ @php
+ $h = (float) $cell['hours'];
+ $ratio = min(1, $h / max(0.001, $data['capacity']));
+ // Heatmap color: green → yellow → red as ratio grows.
+ if ($h <= 0) {
+ $bg = '#f3f4f6'; $fg = '#9ca3af';
+ } else {
+ $r = (int) (255 * $ratio);
+ $g = (int) (200 * (1 - $ratio));
+ $bg = "rgba({$r},{$g},80,0.18)";
+ $fg = "rgba({$r},{$g},80,1)";
+ }
+ @endphp
+
+
+ @endforeach
+
Autoservice profesional{{ $city ? ' — ' . $city : '' }}. Diagnostic, reparații, piese, ITP.
+Oraș: {{ $city }}
@endif + @if ($phone)Telefon: {{ $phone }}
@endif + @if ($email)Email: {{ $email }}
@endif +Luni – Vineri: 08:00 – 18:00
+Sâmbătă: 09:00 – 14:00
+Duminică: închis
+{{ implode(', ', array_slice($cars, 0, 12)) }}@if (count($cars) > 12), …@endif
+