With collapsedGroupsByDefault() the 386 rows are fetched but not
rendered in DOM until expanded — no perf issue, all 17 group headers
now visible without needing to navigate pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration: replace 45 leaf categories (diag.scan, engine.mech…) with
17 root categories (diag, engine…); update labors.category by taking
the prefix before the first dot; delete the leaf LaborCategory rows
- ImportLaborCatalogCommand: rewritten to use root categories directly
so future imports go to the right level from the start
- LaborResource: add ->collapsible() to groups so user can collapse/
expand each category section to see only headers or only one group
- entrypoint.sh: remove one-time catalog import (was already done)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LaborResource: hide category column from data rows by default
(toggleable) so group headers span all columns cleanly; the group
header already shows the localized category name
- ImportLaborCatalogCommand: add --all flag to iterate every company,
refactor into importForCompany() helper, keep idempotent (skips
existing codes, updates only with --force)
- entrypoint.sh: run catalog:import-labors --all after each deploy so
all 386 operations + 45 categories are seeded automatically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LaborResource: add titlePrefixedWithLabel(false) so group headers show
just the localized category name (not "Categorie: Шины"); remove static
cache from closure to avoid Octane cross-request locale bleed
- WorkOrderDashboard: add changeStatus() Livewire method + Alpine.js
dropdown on the status badge so status can be changed directly from
the dashboard without going to the full edit form
- New artisan command catalog:import-labors {company} that reads
database/data/labor-catalog.json (386 operations, 45 leaf categories)
and upserts labor_categories + labors for the given tenant; supports
--force to update existing records by code
- Add "Status actualizat" translation to ru/en JSON
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Defines a proper Tables\Grouping\Group with getTitleFromRecordUsing so
group headers use the locale-aware label from LaborCategory::optionsForSelect()
instead of the raw DB value with Filament's hardcoded "Category:" prefix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
formatStateUsing looks up localized category name from LaborCategory
so 'Anvelope' shows as 'Шины' (ru) / 'Tires' (en). Filter label
uses __('Categorie') to avoid the auto-derived 'Category' (English).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Section was Filament\Schemas\Components\Section not Forms\Components\Section
- Added ru/en translations for page title, form labels, nav label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration: adds name_ro, name_ru, name_en columns; copies name→name_ro;
seeds RU and EN translations for all 11 default categories
- LaborCategory: label() returns localized name, optionsForSelect() keyed
by canonical name (stored in labors.category) with localized label
- LaborCategoryResource: 3-column form with RO/RU/EN inputs; table shows
all three columns side by side
- LaborResource: category select and filter use optionsForSelect()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Creates labor_categories table (company-scoped) seeded with 11 defaults
- LaborCategory model with BelongsToTenant
- Full Filament CRUD resource with drag-to-reorder (sort_order)
- LaborResource category Select now reads from DB instead of hardcoded const
- Navigation: 'Categorii manopere' in Service group, sort 33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getPluralModelLabel now uses nav.label.Norme-ore (already translated
to 'Catalog manopere'/'Каталог работ') so breadcrumb and page heading
match the sidebar navigation label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration: make clients.phone nullable (was NOT NULL, broke inline create)
- Vehicle createOptionUsing: use Get callback instead of $livewire->data
to reliably get client_id in the vehicle inline form
- Remove plate->required() on inline vehicle form (not required in DB)
- Rename 'Norme-ore' → 'Catalog manopere'/'Каталог работ' in navigation
so users can find the labor catalog easily
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Load subcontractJobs via explicit query (bypassing TenantScope but
scoped manually by work_order_id + company_id) to avoid relation
caching issues. Pass as separate blade variable \$subcontractJobs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loads subcontractJobs.subcontractor relation and renders a new table
in the PDF after parts: description, subcontractor, category, client price.
Adds ru/en translations for the new section labels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WorkOrderPdfService::filename() removed duplicate 'WO-' prefix
($wo->number already starts with 'WO-')
- PDF <title> now uses __('FIȘĂ DE LUCRU') so it shows in user locale
- entrypoint: view:clear before view:cache to avoid stale compiled views
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set app locale from user preference in PDF route before rendering
- Wrap hardcoded strings (TOTAL, Subtotal, Achitat etc.) with __()
- Translate status values via __() in blade
- Add ru/en translations: statuses, totals, payment lines
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strings used in work-order PDF (Nr., Nume:, Telefon:, Marca/Model:,
Data deschiderii:, Km la intrare:, subtotals) were falling back to
Romanian in ru/en locale. Added all missing keys.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.
Creates storage/fonts for DomPDF font cache.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Queries now run only when modal is opened — no risk of page-load
exception hiding the button. Button is always visible for admin/owner.
Adds restore action inside modal footer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Helps identify cross-tenant WO leakage — shows each company's slug,
active count, deleted count, and whether it's the current tenant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Soft-deleted WOs (WO-26-0005..0008) are missing from admin view.
A new restore action lets the admin recover them without CLI access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace isAdmin() (Spatie-dependent) with direct role check and add
TenantManager fallback using hostname subdomain so the button appears
even if TenantManager was not resolved for the current request.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows total WO count broken down by tenant/other-company/deleted across
all records without global scopes, helping debug missing WO visibility.
Also adds a recover action when orphaned WOs are found in wrong company.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of relying on terminal/artisan commands, admins see a warning
button on the WO list when orphaned WOs exist (wrong company_id due to
Octane singleton bug). One click moves them all to the current tenant.
Button only shows if there are orphaned WOs and user is admin/owner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous migration skipped companies with ≥1 user. Now moves WOs from
any company whose slug/name contains 'test' or has ≤2 users, which
covers the single test account scenario.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
One-time data migration that moves work orders created with wrong
company_id (Octane singleton race condition) back into psauto.
Only moves WOs from companies that have zero users (ghost/test tenants),
so legitimate separate companies are not affected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FrankenPHP/Octane reuses the same PHP process for concurrent requests.
TenantManager registered as singleton() was shared between requests —
a concurrent request from another tenant could overwrite the current
tenant context mid-flight, causing records to be saved with the wrong
company_id.
Fix 1: Change singleton() to scoped() so Octane flushes TenantManager
at the end of each request (fresh instance per request lifecycle).
Fix 2: Explicitly set company_id in mutateFormDataBeforeCreate() as an
extra guard — even if the creating event fires in the wrong context,
the value from the request's own TenantManager call takes precedence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
isMechanic() calls hasAnyRole(['mechanic']) via Spatie which checks the
model_has_roles table with team_id scoping. If roles are not correctly
seeded for a tenant, admins/owners could accidentally pass the mechanic
filter and see only WOs where master_id = their own id (i.e. nothing).
Now uses $user->role === 'mechanic' directly — no Spatie involved —
so admin/owner/manager always see all WOs in their tenant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous implementation relied on Spatie canDo() for all roles, which
caused admins/managers to see nothing if permissions weren't seeded.
Now: mechanics see only their assigned WOs (master_id = self); every
other role (admin, manager, receptionist, etc.) sees all WOs in the
tenant — TenantScope already prevents cross-tenant access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin/owner and users with work_orders.view_all see all work orders.
Users with only work_orders.view_own_assigned (mechanics) see only
work orders where master_id = their own user ID.
Roles without either permission see nothing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- client_id field: switched to relationship() with createOptionForm so new clients
can be created inline (name, phone, email); search also matches by phone
- vehicle_id field: added createOptionForm (make, model, plate, year, VIN); newly
created vehicle is automatically linked to the currently selected client
- master_id field: now shows only active users with role mechanic/master
- WorksRelationManager: labor catalog select marked optional; added createOptionForm
so new catalog entries can be added without leaving the WO form
- SubcontractJobsRelationManager: category table column now translates value via __()
and shows explicit label(__('Categorie'))
- PDF work-order: replaced 📞 / ✉ / ⚠ emojis with plain-text equivalents so
DomPDF/DejaVu Sans renders them correctly instead of showing "????"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FAQ sections were only 4 (service/crm/depozit/finante) even though
the sidebar has 8. Added the missing 4 to Hints::AREAS with proper
icons + accent colors:
📊 Analiză (pink) 📣 Marketing (orange)
🛒 Magazin (cyan) ⚙️ Admin (slate)
Retagged 15 hints that were shoved into wrong areas as a workaround
(admin.*/marketing.*/shop.*/analytics.* now use the matching bucket).
FAQ blade reads accent color from the AREAS registry instead of a
hardcoded local map.
Also added per-tenant navigation group ordering:
- Settings → new "Ordine meniu lateral" section with a Filament
Repeater that has reorderable buttons (drag/arrow buttons) but no
add/delete (fixed list of 8 groups)
- Persisted to settings.nav_group_order as an array of keys
- New ConfigureNavGroups middleware (runs after ResolveTenant on
the web stack) reads the current tenant's order and calls
$panel->navigationGroups([...]) so the sidebar renders in that
order per-request
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Filament pages wrap their content in an outer <form>. My inline
<form method="POST"> for the hint toggle + reset was nested inside
that outer form — invalid HTML, so browsers dropped my submit and
either did nothing or ran the outer Livewire form's handler.
Replaced both forms with plain <button type="button"> + onclick
fetch() POST + window.location.reload(). Controller now also returns
JSON when Accept: application/json (fetch sends that) instead of a
back() redirect.
The server-side flip was always correct — verified via a Feature
test that posts to /app/hints/toggle and asserts the user column
flipped from true → false.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous pill button showed "● ACTIVE" or "○ INACTIVE" but users read
it as a status label, not a clickable action — nobody clicked to
disable.
New UI: 46×24 iOS-style switch that slides between grey (OFF) and
green (ON) with a sliding white knob, plus a text label next to it.
The whole switch is a form button — clicking flips hints_enabled.
Title attribute + label make both directions obvious: "Click pentru
a DEZACTIVA" when on, "Click pentru a ACTIVA" when off.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New tenant setting: settings.faq_video_url. TextInput on Settings
page → Liste configurabile (accepts YouTube watch URL, youtu.be
short URL, YouTube shorts/embed URL, or Vimeo URL).
- Faq page has getVideoEmbedUrl() that converts recognized URLs to
their /embed/ variant. Unknown URLs fall back to a "?" card with
a direct link. Admins see a "configure in Settings" nudge when no
URL is set.
- Video renders as a 16:9 iframe capped at 480px height, above the
hero.
- .faq-shell is now width:100% (was max-width:1200px) so FAQ uses
all available horizontal space.
- .faq-grid switched from strict 2 columns to
repeat(auto-fit, minmax(420px, 1fr)) so wide screens get 3 columns
and narrow screens stack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced the flat vertical list with:
- Hero strip (gradient blue) with title, subtitle, and 2 stat pills
showing total topics + section count
- Controls bar in a card: search input with inline icon, pill-shaped
toggle button (green when hints ON), reset button with icon
- 2-column responsive grid of area cards. Each card has a colored
header (service=blue, crm=purple, depozit=amber, finante=green)
with icon + name + count badge
- Details/summary items use +/− indicators, tinted background when
open, hover state; body has pill-styled links and green next-step
- Full dark-mode support
- Empty state with search icon
Same content, much easier to scan and looks like a proper help center.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switched render hook from PAGE_HEADER_ACTIONS_BEFORE to
PAGE_HEADER_HEADING_AFTER:
- Previous hook lived inside the action buttons container which is
skipped on pages without header actions (most Admin pages: Settings,
Integrations, API Tokens, Backup, Activity log).
- Previous hook also pushed the icon to the right side on pages that
did have actions (Clienți, Mașini, Calendar list), which looked
disconnected from the title.
New hook renders "?" immediately after the page heading text on every
page, always visible. Also fixed one URL prefix mismatch:
app/pipeline → app/pipeline-board (actual Filament page slug).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registry now covers ~50 hints across Service, CRM, Depozit, Finanțe,
Admin, Marketing, Analiză, Shop. Every main tenant page has an entry.
New Hints::forPath(\$path) does str_starts_with matching against a
curated URL prefix map. First match wins so specific paths (e.g.
/work-orders/create) beat broader ones (/work-orders).
A single render hook on PAGE_HEADER_ACTIONS_BEFORE looks up the
current request path and, if it matches, renders a "?" icon next to
the page header actions. Blades that already have <x-hint> tags
embedded (WO dashboard, Calendar, Pipeline, Excel wizard) keep those
on top of the auto-injected header hint.
Net effect: users see "?" icons everywhere in the panel now, and
adding coverage for a new page is a 2-line change (registry entry +
one URL prefix in forPath).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registry grows from 14 → 25 hints. New entries:
- wo.dashboard.topbar_actions / meta / tabs / history / finance /
timeline / bottom
- wo.create.overview
- crm.clients.overview / vehicles.overview
- finante.dashboard.stats / expenses.overview
"?" icons injected into:
- WO dashboard: top-bar actions, meta header, tabs bar, history card,
finance card, timeline card, bottom action bar (in addition to the
existing dashboard title, PDF preview, chat)
- CalendarBoard: title
- PipelineBoard: title
- Excel import wizard: step 1
- Main tenant dashboard (via PanelsRenderHook::PAGE_START scoped to
Dashboard::class): small banner with a "?" and a shortcut to the
FAQ page for users who don't see the icons
Users now see hint icons on the primary daily-use screens; more can
be added by dropping <x-hint key="..." /> anywhere.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Infrastructure for a portal-wide in-app help system:
* Users have hints_enabled (default true) and dismissed_hints (JSON
array) columns. User::shouldSeeHint(key) checks both.
* app/Support/Hints.php — single-source-of-truth registry with 14
pilot hints across Service (5), CRM (3), Depozit (3), Finanțe (3).
Each entry has RO/RU/EN title + body + optional next-step + links.
* <x-hint key="wo.dashboard.overview" /> Blade component renders a
small "?" icon with Alpine.js popover; the popover shows title,
body, next-step, related links and an "X" button that POSTs to
/app/hints/{key}/dismiss.
* HintController handles dismiss (per key), toggle (global on/off)
and reset (clear dismissed + re-enable). Routes are auth:web.
* /app/faq page (Filament Page under Admin group) renders the whole
registry grouped by area with a live search box and buttons to
toggle global hints or reset dismissed ones.
* Wired 3 pilot hints into the WO dashboard: title (overview), Docs
tab PDF preview, and the Chat client card.
Follow-ups: extend registry to cover more pages and add <x-hint>
tags where useful. Filament resource fields can also reuse the same
copy via ->hint()/->helperText().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same Alpine.js overlay pattern as the WO dashboard Docs tab: click
"🖨 PDF programări" opens a centered modal with the PDF iframed at
90vh, plus "Descarcă" and "Deschide în tab nou" fallbacks in the
header and Esc/click-outside to close. iframe src only binds when
the modal is open, so PDF generation doesn't happen on page load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wire:click="exportPdf" attempted to return a binary Response through
Livewire's JSON channel — it re-encoded the PDF bytes as JSON and
triggered "Malformed UTF-8 characters, possibly incorrectly encoded".
Added GET /app/appointments/pdf?from=...&to=... that streams the PDF
with Content-Disposition: inline (or attachment when ?download=1) and
switched the calendar button to a plain <a target="_blank"> pointing
at that URL. The button now includes the currently visible period via
query params.
The old exportPdf() method is retained (still returns a plain PDF
Response) because CalendarEnhancementsTest asserts against it — it's
just no longer wired to the UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously nav had 12 groups, several with just 1 item. Now 8 clean
groups, no orphans:
- Anvelope (1 item) → merged into Service (Seturi anvelope)
- Stoc & Finanțe (1 item) → merged into Depozit (Import factură Excel)
- Tinichigerie (1 item) → merged into Service (Tinichigerie / Detailing)
- Subcontractare (2 items) → merged into Service (Lucrări terți,
Subcontractori)
- Integrări moved from Marketing → Admin (it's a config concern, not
a marketing feature)
- ClientResource + VehicleResource were ungrouped → added to CRM
Final groups:
Admin(8) · Analiză(4) · CRM(5) · Depozit(10) · Finanțe(6) ·
Magazin(2) · Marketing(3) · Service(11)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>