174 Commits

Author SHA1 Message Date
Vasyka 16fcdb41ed fix: phone uniqueness validation and DB constraint for users
- Replaced broken ->rules(fn (\$record)) with proper Filament ->unique()
  using ignoreRecord:true + modifyRuleUsing scoped to company_id,
  applied to both phone and email in MasterResource and UserResource
- Added migration 2026_09_26_000001 to add unique(company_id, phone)
  constraint on users table (nulls out duplicate phones before adding,
  MySQL allows multiple NULLs in unique index so blank phones are fine)
- Exception catch in Create/EditMaster and Create/EditUser now also
  handles UniqueConstraintViolationException on phone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:18:55 +00:00
Vasyka f1afcafa5d fix: duplicate email/phone shows notification instead of crashing
Two-layer protection on MasterResource and UserResource:
1. Form validation via ->rules() with Rule::unique() scoped to company_id
   (prevents DB hit in normal flow, shows inline field error)
2. handleRecordCreation/handleRecordUpdate catches
   UniqueConstraintViolationException as fallback — shows a Filament
   danger notification and halts without showing an error page

Also adds phone uniqueness validation (scoped per tenant).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:16:13 +00:00
Vasyka fcbb807462 fix: show friendly validation error for duplicate email on user/master create
Added ->unique(ignoreRecord: true) scoped to company_id on the email
field in both MasterResource and UserResource. Previously a DB-level
UniqueConstraintViolationException crashed the page; now Filament shows
"Acest email este deja folosit de un alt utilizator." inline on the field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:11:56 +00:00
Vasyka 68f58cbcdc feat: cookie policy & consent banner per Moldova law 195/2024
- Settings page: new "Politica Cookie" section — toggle banner on/off,
  editable RO/RU policy text (defaults to full 14-section template)
- Public route GET /cookie-policy → CookiePolicyController returns
  locale-aware full policy page with tenant branding
- Cookie banner partial (Alpine.js, localStorage) shown on tracking and
  landing pages; Accept/Reject stores consent, banner dismisses
- Translation keys added to lang/ro/portal.php and lang/ru/portal.php
- Alpine.js (CDN) added to tracking and landing pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 17:52:23 +00:00
Vasyka 753e81d292 fix: calendar board — 4 issues fixed
1. New form: Auto (vehicle) field first, client auto-filled via
   updatedNewApptVehicleId() when a vehicle is selected; getAllVehicleOptions()
   returns all vehicles as «plate — make model» for quick lookup

2. Scroll speed: JS intercepts wheel events on .cv-matrix-wrap and
   applies ×0.45 multiplier for horizontal scroll (shift+wheel or
   trackpad horizontal); scroll-behavior:smooth added to CSS

3. Event cards now show all 4 fields: 🔧 Lucrarea (title) +
   👤 Client + 🚗 Auto · plate + 🕐 Ora; each shown only if present

4. Delete bug fixed: replaced onclick=confirm() (races with wire:click)
   with wire:confirm= (Livewire native dialog — Cancel truly cancels)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 06:46:46 +00:00
Vasyka f885c04f90 fix: rework labor tree/list toggle — use Livewire wire:click not Alpine
Previous approach wrapped {{ $this->table }} in x-show which caused
Livewire/Alpine conflicts and broken toggling. New approach:
- viewMode is a Livewire #[Url] property (persisted in URL as ?view=)
- wire:click="setViewMode('list/tree')" triggers a clean Livewire render
- Only one view is rendered at a time (@if, not x-show)
- Tree: Alpine manages per-category open/close state client-side via
  indexed array; Open/Close all buttons use setAll() helper
- Visual overhaul: tighter cards, chevron arrows, proper hover states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 05:08:53 +00:00
Vasyka 0180f29023 feat: tree/list view toggle for labor catalog
ListLabors now has a toggle button (persistat în localStorage) care
comută între:
- Listă: tabelul Filament grupat pe categorii (existent)
- Arbore: accordion cu toate categoriile ca noduri colapsabile,
  fiecare cu tabelul manoperelor sale (Manoperă, Mod, Ore, Preț,
  Cost, link Editează); butoane Deschide tot / Închide tot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 05:03:03 +00:00
Vasyka 5a3275c2cf fix: store opened_at and closed_at as DATETIME (not DATE)
DATE columns lose time information, causing timeline to show 00:00 for
car reception and WO closure. Migrated both columns to DATETIME and
updated: model casts (date → datetime), form pickers (DatePicker →
DateTimePicker with seconds:false), default to now() instead of today(),
and cleaned up defensive Carbon::parse() in getTimeline().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 04:52:03 +00:00
Vasyka ce9f082612 feat: per-row and global discount with margin validation
Parts (wo_parts) already had discount_pct; now also added to wo_works.
Labor catalog gets a cost_price field as the hard minimum sell floor.

- WorkOrderWork: discount_pct + cost_price fields; total calculation
  now includes discount: hours × price_per_hour × (1 − disc/100)
- Labor model: cost_price field (used to pre-fill work rows and validate)
- WorksRelationManager: discount_pct field with live helper text showing
  effective price + margin %; validation blocks save if below buy cost
  or below company min_discount_margin_pct; "Reducere globală" header
  action applies % to all work rows (skips rows that would breach limits)
- PartsRelationManager: same validation on existing discount_pct field;
  helper text (🟢/🟠/🔴) shows effective price + margin in real time;
  "Reducere globală" header action for all parts
- Settings page: new "Reduceri & limite preț" section with
  min_discount_margin_pct — 0 = no limit, >0 = hard block below that %
- LaborResource form: cost_price field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 04:48:15 +00:00
Vasyka 8eb9d43525 fix: remove redundant Achitat field when pay_status != partial
The read-only Achitat (MDL) display is unnecessary — the Total field
already shows the amount. Only show the editable Sumă achitată field
when partial is selected; rest de achitat remains always visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 12:16:44 +00:00
Vasyka 1e7ca0fdfe feat: partial payment field + photo upload from dashboard
- WorkOrderResource: pay_status is now live(); added _partial_amount
  TextInput (visible only when pay_status=partial) that shows current
  paid amount and accepts a new total — dehydrated so it does not save
  directly; _achitat hidden when partial is selected to avoid confusion
- EditWorkOrder.afterSave: creates a Payment record for the difference
  when pay_status=partial and entered amount > existing paidAmount
- WorkOrderDashboard: WithFileUploads + uploadPhotos / deletePhoto
  methods using Spatie MediaLibrary
- Dashboard blade: Foto tab replaced with upload form + gallery with
  per-photo delete buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 08:16:21 +00:00
Vasyka aa231f4b08 Add Achitat + Rest de achitat display fields in payment section
Shows the financial breakdown inline in the Plată & total section:
- Total (MDL) — bold, computed
- Achitat (MDL) — green, from paidAmount()
- Rest de achitat (MDL) — red if unpaid, green if fully paid

All three fields are readOnly/dehydrated (display only). To register
payments, use the Plăți relation manager tab.
Translations added for RO/RU/EN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:56:36 +00:00
Vasyka 1215bc8be2 Fix total readOnly, label pay_status/discount, add RU/EN translations
- pay_status: add label Statut plată (was showing English auto-label)
- discount_pct: rename label to Reducere % (standard RO terminology)
- total: disabled() → readOnly() so field looks active (not grayed out)
  while still preventing manual editing; $set() from discount live works
- Add ru/en translations: Statut plată, Reducere %

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:43:51 +00:00
Vasyka 116495ce0b Fix payment amount editable + discount live recalculation
- PaymentsRelationManager: remove broken default() closure on amount;
  use fillForm() on CreateAction instead (correct Filament API) so the
  modal pre-fills with remaining balance (total - paid) but field stays
  fully editable; add minValue/step for numeric UX
- WorkOrderResource: discount_pct is now live(onBlur) with afterStateUpdated
  that recomputes works+parts+subcontract minus discount and sets the
  total field immediately — no Save required to see the effect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:41:02 +00:00
Vasyka 7c0c728347 PDF: remove part codes; payments: pre-fill amount with balance due
- PDF parts table: removed Cod/article column, widened Denumire column,
  fixed colspan in tfoot from 4 to 3
- PaymentsRelationManager: amount field now defaults to (total - paidAmount)
  so it pre-fills with the remaining balance; always editable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:23:06 +00:00
Vasyka b0433b66c7 Recalculate WO total after saving discount_pct
afterSave() calls recalcTotal() so discount_pct changes are applied
to the total immediately on save, then fillForm() refreshes the
displayed total field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:12:37 +00:00
Vasyka b7653f600e Disable pagination on labor catalog so all categories are visible
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>
2026-09-08 05:59:03 +00:00
Vasyka 2341dd846b Collapse all labor groups by default — use correct Table method
collapsedGroupsByDefault() is on Table, not Group::collapsedByDefault()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:47:26 +00:00
Vasyka f987071a6d Collapse all labor groups by default on page load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:23:42 +00:00
Vasyka 7ed60b1dbf Restructure catalog to root categories + collapsible groups
- 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>
2026-09-08 05:17:14 +00:00
Vasyka cbf8deabf3 Fix labor table grouping colspan + auto-import catalog on deploy
- 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>
2026-09-08 05:07:32 +00:00
Vasyka 5e923459f7 Three features: labor group headers, status picker, catalog import
- 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>
2026-09-08 04:38:35 +00:00
Vasyka 06fac16026 Fix labor catalog group headers to show localized category names
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>
2026-09-07 13:07:34 +00:00
Vasyka 116a53d81d Localize category column and filter label in LaborResource table
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>
2026-09-07 12:56:23 +00:00
Vasyka 9ee17fa918 Fix LaborCategoryResource: use Schemas\Components\Section + add translations
- 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>
2026-09-07 12:46:03 +00:00
Vasyka 1b34a3c217 Add multilingual support to labor categories (RO/RU/EN)
- 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>
2026-09-07 12:37:39 +00:00
Vasyka 818ae29b20 Fix LaborCategoryResource: remove unavailable BulkActionGroup class
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:29:53 +00:00
Vasyka a2ddea2529 Add LaborCategory resource — manage labor categories from UI
- 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>
2026-09-07 12:25:19 +00:00
Vasyka 1cf1dc2f54 Fix LaborResource breadcrumb/heading to show 'Catalog manopere'
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>
2026-09-07 12:15:49 +00:00
Vasyka aba1fcc372 Fix client/vehicle inline creation and labor catalog visibility
- 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>
2026-09-07 12:08:14 +00:00
Vasyka 63ef482514 Fix Romanian diacritics (ș ț) and Cyrillic in DomPDF-generated PDFs
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>
2026-09-06 12:03:24 +00:00
Vasyka 9be4aab7e5 Refactor WO diagnostic: move all DB queries inside modal, add try/catch
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>
2026-09-06 11:44:12 +00:00
Vasyka 24bdc36913 Expand diagnostic modal to show all companies with WO counts
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>
2026-09-06 11:39:11 +00:00
Vasyka 5fa55f4bff Add restore button for soft-deleted WOs in admin diagnostic
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>
2026-09-06 11:32:14 +00:00
Vasyka 8e32444a13 Fix diagnostic button visibility for admin users
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>
2026-09-06 11:22:53 +00:00
Vasyka 0efe2e0bff Add diagnostic button to WO list for admins to inspect DB state
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>
2026-09-06 11:20:23 +00:00
Vasyka 416b379d27 feat: add one-click orphaned WO recovery button for admins
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>
2026-09-06 11:11:45 +00:00
Vasyka c2f87a468f fix: TenantManager scoped() + explicit company_id in WO create
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>
2026-09-06 10:49:18 +00:00
Vasyka ebc4fe0f23 fix: use role column directly in WO query — drop isMechanic() Spatie dep
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>
2026-09-06 10:40:19 +00:00
Vasyka 4a896118b9 fix: simplify WO visibility — only mechanics are restricted to own WOs
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>
2026-09-06 10:25:55 +00:00
Vasyka a618f0af32 feat: implement per-role WO visibility via getEloquentQuery
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>
2026-09-06 10:16:55 +00:00
Vasyka 0af8f2a755 fix: revert client_id to options() — relationship() broke initial list loading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 20:09:44 +00:00
Vasyka 3c7e5fffbf improve WO form: inline client/vehicle create, mechanic filter, category translation, PDF emoji fix
- 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>
2026-09-04 19:56:40 +00:00
Vasyka 2ca6ab58a1 feat(nav): FAQ shows all 8 sections + drag-drop reorder in Settings
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>
2026-09-01 11:09:24 +00:00
Vasyka 0716b70bfa feat(faq): video tutorial + full-width layout + wider grid
- 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>
2026-09-01 10:50:40 +00:00
Vasyka 06081159b6 feat(hints): contextual help system + FAQ page (MVP)
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>
2026-09-01 09:46:07 +00:00
Vasyka 74f5693fec fix(calendar): PDF programări via HTTP route, not Livewire wire:click
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>
2026-08-12 05:25:42 +00:00
Vasyka 29e5481fc3 refactor(nav): collapse single-item groups + fix ungrouped CRM resources
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>
2026-08-10 19:29:03 +00:00
Vasyka 1b8ccb116f feat(work-orders): dashboard becomes single hub — list/create/edit funnel through it
- WO list row click now opens /work-orders/{id}/dashboard instead of
  /edit (via ->recordUrl on the table). Edit remains reachable from
  the per-row Filament EditAction.
- CreateWorkOrder redirects to the dashboard after save instead of
  the edit page.
- Dashboard top bar exposes: "Listă" (back to WO list), "+ Nou"
  (create), and "Editare completă" (full edit form) so users don't
  have to hop through the sidebar to move between related WO screens.

The old /work-orders, /create, and /{id}/edit routes stay intact —
just the default navigation flow now converges on the dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 19:20:36 +00:00
Vasyka 517d32c4d4 feat(nav): single Calendar menu — hide Appointments resource from sidebar
AppointmentResource::\$shouldRegisterNavigation = false so only
CalendarBoard shows in the sidebar as "Calendar". The board already
has a "Listă" view-mode button alongside Zi/Săpt/Lună/Custom, so both
use cases are covered from a single entry. CRUD routes
(/app/appointments/create, /{id}/edit) stay accessible from the
board's click handlers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 19:04:54 +00:00