Commit Graph

92 Commits

Author SHA1 Message Date
Vasyka c66b895948 feat: photo lightbox with prev/next navigation on dashboard
Replace link-to-new-tab with Alpine.js lightbox overlay. Clicking a
thumbnail opens a full-screen dark overlay with the full-size image,
prev/next arrows (keyboard ←/→ supported), counter, and close button
(ESC or ✕ or click outside). No external libraries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 12:21:37 +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 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 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 6848e58f14 Translate PDF footer 'Document generat automat' for ru/en locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:31:55 +00:00
Vasyka 221e311c08 Fix subcontract jobs not appearing in PDF
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>
2026-09-06 12:29:55 +00:00
Vasyka 2caac4ae8e Add subcontract jobs section to work order PDF
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>
2026-09-06 12:24:59 +00:00
Vasyka 4c7cacc765 Fix PDF filename double WO prefix, title translation, view cache clear
- 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>
2026-09-06 12:19:59 +00:00
Vasyka 9df834ffa1 Fix PDF locale and missing translations for ru/en
- 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>
2026-09-06 12:14:54 +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 346fcf968f fix(faq): toggle worked server-side but click did nothing — nested forms
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>
2026-09-01 11:04:24 +00:00
Vasyka 6dba81502b fix(faq): replace confusing status pill with iOS-style toggle switch
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>
2026-09-01 10:59:33 +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 cae2904c76 feat(faq): visual redesign — hero, chip toggles, 2-col colored cards
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>
2026-09-01 10:43:33 +00:00
Vasyka 3c92ea5475 feat(hints): scale coverage — 11 new registry entries + inject across pages
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>
2026-09-01 09:57:54 +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 293373f4ac feat(calendar): preview PDF programări in modal iframe
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>
2026-08-12 05:35:20 +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 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 c9d400fd6e feat(pdf): preview in Filament modal (iframe) instead of new tab
All "Vizualizare PDF" actions now open a wide (7xl) Filament modal
containing an iframe pointing at the inline PDF endpoint, with a
top-right "Descarcă" and "Deschide în tab nou" fallback.

- WorkOrder edit action + WO table row action
- InjectorProtocol edit action + protocols table row action
- Dashboard Docs tab preview button (Alpine.js overlay since it's
  a plain Blade view, not a Filament action)

The underlying /pdf routes stay unchanged (Content-Disposition: inline,
?download=1 for attachment) — the iframe just loads the same URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:47:07 +00:00
Vasyka 5a5f43f2de feat(pdf): inline preview by default with ?download=1 override + vehicle photo fallback
- WorkOrder and InjectorProtocol PDF routes now respond with
  Content-Disposition: inline (opens in browser tab so the user can
  read/print/save from the built-in PDF viewer). ?download=1 forces
  the classic attachment download.
- CalendarBoard exportPdf() switched to the same inline default.
- WO edit action + WO table row action + injector protocol actions
  now use ->url(...)->openUrlInNewTab() instead of streaming the PDF
  inline into the current tab.
- Dashboard Docs tab has both a preview link and a "Descarcă" link.
- Vehicle card photo falls back to WO's first uploaded photo when
  Vehicle model has no MediaLibrary integration of its own.

Test CalendarEnhancementsTest updated to assert the new inline
response headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:56:24 +00:00
Vasyka e0b61fb3cb feat(work-order-dashboard): PDF download in Docs tab + gate chat by config
- Documente tab: link to a new /app/work-orders/{id}/pdf route that
  streams the invoice PDF via WorkOrderPdfService; also lists any
  files uploaded to the 'signed_documents' media collection.
- Chat client card: only show the input form when telegram_bot or
  whatsapp_business is enabled AND credentialed in the tenant's
  Integrations page. Otherwise render an empty state with a shortcut
  to /app/integrations.

RU + EN translations added for the new strings (RO source).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:47:43 +00:00
Vasyka 34f53383bf fix(work-order-dashboard): guard getFirstMediaUrl call on Vehicle
Vehicle model doesn't use Spatie MediaLibrary — the direct call broke
the dashboard for any WO whose vehicle has no MediaLibrary integration.
Guard with method_exists and remove the diagnostic logging + URL echo
now that the routing issue is resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:34:30 +00:00
Vasyka 5fff3d1a70 fix(dashboard): replace route() with url() — Filament auto-name has literal {record}
Filament auto-generates route names from the URL slug, so my custom
Page with slug 'work-orders/{record}/dashboard' got the ugly name
'filament.tenant.pages.work-orders.{record}.dashboard' — where
'{record}' is treated as a literal segment in the name, breaking
route() lookups.

Switch all links + the header-action URL to url('/app/work-orders/'
. \$id . '/dashboard') so they resolve correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:05:42 +00:00
Vasyka 9830dd9a6f feat(work-order-dashboard): Phase 2 — Timeline + Chat + Bottom action bar
Right column now shows:
- Timeline card: synthesised events from WO lifecycle fields (created,
  opened, approved, closed) + payments + activity_log entries (spatie/
  activitylog). Sorted newest first, scrollable.
- Chat client card: outbound notifications history (ClientNotificationLog)
  + inline send form. Prefers Telegram if client has telegram_chat_id,
  else falls back to SMS/WhatsApp. Logged either way for UI history.

New bottom action bar (fixed, above footer):
- Previous / Next WO links (adjacent by id) with number preview
- Repeat order: pre-fills create form with same client/vehicle
- Close order (danger button): sets status='done' + closed_at=now, with
  wire:confirm guard. Hidden when already closed.

+14 translations. Fixed ready_at reference (not in schema, removed).
All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:00:28 +00:00
Vasyka fa8704f8b6 feat(work-orders): new Mitchell1-style dashboard view (Phase 1 — layout + tabs)
Custom Filament Page at /app/work-orders/{id}/dashboard renders a
Mitchell1-inspired 3-column layout:
  - Top bar: WO#, status badge, actions (Edit / Tracking link)
  - Meta header row: creation / opened / ETA / responsible / urgency /
    paid amount (6 cells)
  - Left sidebar (300px): Client card (avatar, phone, email, status
    tag, 3-stat grid: visits/total/debt) + Vehicle card (photo, plate,
    VIN, mileage, engine, gearbox) + Repair history (latest 5 for this
    vehicle, links to dashboards)
  - Middle: tab bar (Lucrări/Piese/Diagnostic/Foto/Documente/Note)
    with Alpine-driven switching. Works & Parts show the tables read-
    only; add/edit still goes through existing EditWorkOrder Filament
    resource. Photos tab shows gallery from spatie/media-library.
  - Right (300px): Finance summary card (works cost, parts cost,
    discount, total, paid, balance) + placeholder for Timeline+Chat
    (Phase 2)
  - Responsive: right column collapses <1280px, left <900px.

'Vizualizare dashboard' button added on top of the existing
EditWorkOrder page so users can switch between edit form and info-
dense dashboard.

Fixed getSlug() signature (must match parent with ?Panel $panel).

+14 translations. All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 18:48:52 +00:00
Vasyka 44726adcb3 i18n(injector-protocol PDF): switch keys from Cyrillic to RO source + 24 dict entries
Prior version used Cyrillic strings as __() keys — RO users saw
Cyrillic text because fallback = key. Now all PDF labels use RO as
the source-language key (e.g. 'Automobil', 'Nr. înmatriculare',
'Nr. protocol:', 'INJECTOARE'). ru.json + en.json provide translations.

+24 keys covering title, all field labels, table headers, measurement
column headers, before/after subheadings, footer note, conclusion
section, signature block, unit suffixes (km, ml).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:43:16 +00:00
Vasyka 47eb4f62c1 feat(injector-protocols): full module — resource + PDF export via Browsershot
Implements the injector diagnostic protocol module per spec:
- Migration: injector_protocols + injector_protocol_rows with company_id
  (multi-tenant), vehicle_id (not car_id — this CRM uses Vehicle),
  master_id FK to users, snapshot fields (car_model/plate/vin/mileage/year/
  injector_brand) captured at protocol time.
- Models with BelongsToTenant; auto-generate protocol_number as
  PS-{tenantId}-{year}-{seq6}; auto-seed 8 empty rows on create.
- Permissions: injector_protocols.view + injector_protocols.conclude.
  Default roles: owner/admin (both), manager (both), receptionist (view
  only), mechanic (both). Test permission count updated 52→54.
- InjectorProtocolResource under Service nav group with 4 sections:
  data auto+client (with client_id/vehicle_id lookups auto-filling
  snapshot fields), reason checkboxes, 8-row repeater for measurements
  (Repeater with position hidden; fixed 8, non-addable/deletable),
  conclusion + comment + master signature.
- Table with columns, badge filters, PDF action.
- Blade PDF template: pixel-close to reference (graphite/gold/blue
  brand tokens, striped rows, rotated 'ФОРСУНКИ' header).
- Route /app/injector-protocols/{id}/pdf with permission gate,
  streams PDF via new InjectorProtocolPdfService (Browsershot).
- Dockerfile: install nodejs 22 + chromium + noto fonts + puppeteer-core
  in /opt/browsershot; env vars for Browsershot binary paths.
- +71 RU/EN translations covering resource + PDF template.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:27:53 +00:00
Vasyka 1f471cc1ea i18n(dates): use app locale for month names on MechanicKpi + PDF appointments
MechanicKpi getPeriodLabel() was hardcoded to ->locale('ro') which
forced Romanian month names ('iulie 2026') regardless of the app
locale. Same on PDF appointments day-label. Both now switch to
app()->getLocale() so RU shows «июль 2026», EN 'July 2026'.

SetLocale middleware already calls Carbon::setLocale on every
request, so translatedFormat() elsewhere already respects the app
locale — no other files needed patching.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:43:54 +00:00
Vasyka 8b77d8ae7e i18n(calendar-board): translate mechanic specialization tokens in matrix + legend
Both the row-meta on the master matrix rows and the legend list at
the bottom split specialization by '/' and translate each token via
__(), matching the pattern already used on Reports.php.

'Suspensie / Frâne' → «Подвеска / Тормоза», 'Motor / Cutie viteze' →
«Двигатель / КПП» etc. (all tokens already in the dict).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:37:58 +00:00
Vasyka 7df4d1306f i18n(workload): +10 translations + wrap capacity/hours/legend suffixes
Wrapped 'capacitate ref: Nh/zi' header, cell counters 'Nh / N progr.',
and 3 previously-raw legend labels (liber / mediu / plin (peste
capacitate)). +10 keys total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:41:48 +00:00
Vasyka 990a2898a3 i18n(recommendations): +12 translations + wrap raw 'niciodată' fallback
Covers page title, 4 section headings + subtitles, 4 empty-state
messages, 'ultima fișă' column suffix, and last-contact 'niciodată'
fallback (which was a raw string).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:38:19 +00:00
Vasyka 3b6dc982c1 i18n(ai-assistant): wrap wire:confirm delete conversation prompt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:37:10 +00:00
Vasyka 622619262d fix(ai-assistant): actually wrap tokens/cereri counter suffix (Edit was skipped)
Previous commit added translations but the source Edit failed silently
via file-not-read guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:34:06 +00:00
Vasyka 78e48c6dda i18n(reports): wrap Anul curent + auto-translate specialization tokens
- Reports.php periods() had 'this_year' hardcoded; now wrapped in __()
- masters tab specialization cell: split by '/' and __()-translate each
  token so 'Motor / Cutie viteze' auto-renders as «Двигатель / КПП»
  on RU (and 'Engine / Gearbox' on EN). Users can enter any free-text
  specialisation; the common keywords now have RU/EN translations.
- +12 specialisation vocabulary entries (Motor, Frâne, Suspensie,
  Anvelope, Cutie viteze, Electrică, Diagnosticare, Vopsitorie,
  Tinichigerie, Aer condiționat, Roți, Ambreiaj).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:32:06 +00:00
Vasyka 5e6472948f fix(reports): repair blade @foreach corrupted by aggressive i18n wrap
The mega-i18n script mis-wrapped a PHP fragment inside the array
literal of a Blade @foreach on reports.blade.php (line 91). It
matched the '>' from '>= 0' as an HTML tag boundary and turned the
rest of the array into a broken __() string, which caused
'unexpected endforeach' compile error.

Rewrote the array with proper __() calls on each label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:22:36 +00:00
Vasyka 7abbfe8509 i18n(excel-import-wizard): translate stepper (1-4) + Step 3 title + row status badges
- 4 stepper labels (Upload / Mapare coloane / Previzualizare / Confirmare)
  now split so the number stays and only the text runs through __()
- Step 3 heading rewritten with __(':n poziții') placeholder
- 'Selectat: filename.xlsx' hint wrapped
- Row status badges (Găsit/Nou/Nu găsit) split so emoji stays and label
  translates
- 'Articole noi (se vor crea)' summary line wrapped

+8 translations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:18:39 +00:00
Vasyka 466d182192 i18n(excel-import-wizard): +19 translations (full wizard + fix Step 4 count)
Full page translation across all 4 steps (upload / map columns / preview /
done), including form labels, buttons, helper text, empty state, and
final result summary. Notification 'N poziții importate' now built via
__(':n')-placeholder so it translates on RU/EN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:16:20 +00:00
Vasyka 2b56bbc35b i18n(finance): full page — tabs/periods/pnl/balance + Expense.CATEGORIES
- All 4 tabs (Overview/Cashflow/P&L/Balanță) built with __() around
  the text portion, emoji stays in code
- 4 period presets wrapped with __()
- P&L table Marjă/Marjă piese footer wrapped
- Expense::CATEGORIES values look up through __() in the expensesByCat
  loop so categories show translated (Salariu → Зарплата etc.)
- +25 translations covering all UI copy on /app/finance

306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-17 06:28:53 +00:00
Vasyka 661101fa4a i18n(vin-search): full page translation +15 keys, wrap remaining inline text
Wrapped 3 remaining raw RO strings ('An model (estimat)', WMI error
fallback, 'Niciun rezultat pentru') and added human RU/EN for all
15 keys used on /app/vin-search.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 11:24:10 +00:00
Vasyka 2831dbd1c1 i18n(scanner): translate full page (13 keys) + wrap inline blade + JS errors
- Wrap inline blade text ('Mod recepție'), placeholder, and JS error
  messages (via @json(__(...)) so they land as JS strings).
- +13 translations covering all UI text on /app/scanner.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 11:15:12 +00:00
Vasyka a966e0b2c7 i18n(calendar): translate 'h/zi', matrix howto, load legend, day-count word
- Post/mechanic meta 'h/zi' → __('h/zi') so RU shows 'ч/день', EN 'h/day'
- Wrap the 3 load-legend labels (0–5h/10, 5–8.5h/10, ≥9h/10) so 'h' → 'ч' on RU
- Split the long howto sentence so 'Pod'/'Mecanic' + 'Zile' translate
- Default fallback 'Pod 1 (default)' now uses __('Pod') + __('implicit')

Adds 5 new translations. Post NAMES themselves are DB rows (user data)
and are shown as-is — they're not translated by the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 06:59:45 +00:00
Vasyka 95aeecb932 i18n: fix widget heading, translate Select options from model consts, +40 keys
- LowStockTable: rename getHeading → getTableHeading (correct Filament API)
- Global I18n::opts() helper wraps Model::CONST values in __() so
  Select dropdowns show translated status/type/season/etc. labels
- Sed-transform ->options(X::CONST), ->options(array_combine(X::A,X::A)),
  and formatStateUsing(fn($s)=>X::CONST[$s]??$s) → wrap with __()
  (58 options + 10 combines + 7 formatStateUsing across 28 files)
- CalendarBoard: all 7 day names now via __() (was missing 4)
- calendar-board.blade: fix untranslated 'săptămâna curentă', 'capacitate',
  'rata confirmare', 'mediu', 'plin', 'Pod / Zi' / 'Mecanic / Zi'
- +40 RU/EN translations (Client & Auto, Maistru / Mecanic, Programat,
  Sosit, Finalizat, Anulat, Neprezentat, days of week, calendar KPIs)

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 06:42:16 +00:00
Vasyka 7077fff3b3 feat(i18n): convert 38 hardcoded static labels + safe blade wrap + 226 keys
- Convert all remaining static \$modelLabel/\$pluralModelLabel to getter
  methods with __() (AppointmentResource, WorkOrderResource, MasterResource,
  ServiceTemplateResource, LaborResource, and 33 others)
- Safe blade wrap v2 (37 files, 201 wraps): fixed the regex to not match
  `->`, `=>`, `!<` — previous aggressive pass broke @if directives
- Bulk-translate lowercase model-label keys (programări, fișe lucru,
  tehnicieni, șabloane servicii, norme-ore, etc.) — these were the RO
  strings appearing in page titles/breadcrumbs
- Add human RU/EN for mechanic-kpi (Mecanic/Lucrări/Norma ore/etc.)
  and calendar-board words

Tests 306 green. Blade compiles cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-15 06:17:10 +00:00
Vasyka 3911012c65 feat(i18n): mega-wrap 494 raw RO strings + 269 lang entries + 161 human RU/163 EN
Broad sweep across the whole codebase:
- Blade views (39 files, 315 wraps): tag-text and title/placeholder/alt
  attributes wrapped with {{ __() }}. Excludes scripts, styles, @php,
  @verbatim, {{ }}, {!! !!}, comments to avoid touching interpolations.
- PHP (54 files, 179 wraps): array 'key' => 'RO value' patterns and
  list items with diacritics wrapped with __(). Reverted __() inside
  const arrays (PHP disallows non-constant expressions).
- Added 269 new keys to lang/{ru,en}.json (identity fallback for
  unknowns → 161 human RU + 163 EN translations added for the most
  common enums, stages, roles, statuses, payment methods, vehicle
  categories, warehouse, portal, form actions.

Missing translations fall back to RO so the UI never breaks. All 306
tests pass; view cache compiles cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-15 05:53:22 +00:00
Vasyka 2d447e6294 feat(i18n): fix widgets, pipeline & mechanic boards + missing translations
Follow-up to previous i18n pass — user reported many visible RO strings on
EN/RU locales because auto-wrap missed:
  - Stat::make() first-arg labels in StatsOverview, FinanceOverview, PlatformStats
  - $heading static in LowStockTable (converted to getHeading() method)
  - Pipeline board Blade view: 40+ literal strings wrapped with {{ __() }}
  - Mechanic board Blade view: statuses, buttons, block modal
  - PipelineBoard.php hardcoded tag labels, stage labels, notify strings
  - MechanicBoard.php confirmBlock notification

Added human RU + EN translations for the visible strings shown in the
screenshots (dashboard widgets, Client/Vehicle list breadcrumbs & columns,
pipeline board topbar/stats/filters/cards/detail panel/quick actions,
mechanic board stats/statuses/block modal). All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-15 05:31:26 +00:00
Vasyka b9d096fa59 fix: language switcher works — GET route, simple links, forced RO fallback
The previous POST-based switcher failed silently: CSRF token check
returned 419 when clicking, and the session never persisted.

Root causes:
- POST requires @csrf token, but the test path fetched pages that had
  no matching form so the token in the DOM didn't match the session
- Some tenant subdomains had SESSION_DOMAIN scoped differently, so
  the cookie set by POST didn't come back on the follow-up GET
- Prod .env had APP_LOCALE=en which took precedence over the config
  edit; when session had no locale yet, defaulted to English

Fixes:

1. Route accepts BOTH GET and POST via Route::match(['get', 'post']).
   Setting your own language is not a security concern — GET is fine.

2. Route explicitly calls $request->session()->save() before redirect,
   forcing the session store to write before the redirect fires.
   Also honors ?redirect=<url> query so the user lands back on their
   original page rather than referer-guessing.

3. lang-switcher partial rewrites to plain <a href> tags (no @csrf,
   no forms). Each link points at /locale/{code}?redirect={current-url}
   so the switch happens in a single hop with predictable target.

4. SetLocale middleware hard-codes 'ro' as the ultimate fallback,
   ignoring config/env. The Romanian portal is the default
   client-facing surface; if a client has no session locale set and
   no user account, they see Romanian (safer than English which has
   no portal translations).

Suite: 306 passed (853 assertions). Unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-13 20:17:12 +00:00
Vasyka 57bdc1b594 feat: language switcher on portal + apply_margin re-added to Plată & total
Two fixes:

== 1. Language switcher (RO/RU/EN) in portal headers ==

New partial resources/views/partials/lang-switcher.blade.php with two
visual styles:
- style='chip' (default) — white-tinted glass for colored headers
  (shop nav bar, tracking page hero)
- style='light' — outlined buttons for pale backgrounds (invitation
  accept card)

Each button is a POST form to the existing /locale/{lang} route which:
- puts locale in session
- persists to user.locale if authenticated
- redirects back to the same page

Included in:
- resources/views/shop/layout.blade.php (right side of top nav,
  after login/register)
- resources/views/tracking/show.blade.php (top-right of hero header,
  absolutely positioned)
- resources/views/invitations/accept.blade.php (top-right of card,
  above welcome heading, light style)

Current locale button is highlighted (opaque white on colored bg /
blue on pale bg). Others are muted until hovered.

Two new translation keys:
- portal.common.language (RO: Limbă / RU: Язык)

== 2. apply_margin toggle back in "Plată & total" ==

Previous session's edit didn't persist to the file. Now confirmed in
place: WorkOrderResource form's "Plată & total" section shows a
"Aplică marjă internă" toggle between discount_pct and
override_margin_pct. Gated by FINANCE_VIEW_INTERNAL_MARGIN so only
owner / admin / manager / accountant see it. Default = true.

When toggled off on a Fișă, WorkOrderWork::saving hook writes
salary_base = total for every line on that WO (no reduction).
Backend logic already in place — this commit fixes the missing UI
control.

Suite: 303 passed (840 assertions). Unchanged — refactor + view only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-13 19:42:00 +00:00
Vasyka 113610ea8f feat: WO apply_margin at fișă level + full RO/RU i18n on client portal
Two changes in one commit:

== 1. Moved apply_margin toggle from line-level to Fișă-level ==

The per-manoperă apply_margin toggle is gone from the Manopere tab.
In its place: a single "Aplică marjă internă" toggle in Fișa's
"Plată & total" section (next to override_margin_pct). One decision
per Fișă instead of per line — cleaner mental model, matches how the
shop actually thinks about at-cost vs. billable work.

Migration: work_orders.apply_margin boolean default true (idempotent).
WorkOrderWork::saving now reads WO.apply_margin from DB directly (not
via belongsTo cache) to determine salary_base:
  - WO.apply_margin=false → every line gets salary_base=total, applied_margin_pct=0
  - WO.apply_margin=true  → resolver chain (WO.override → user margin → tenant default)

Old wo_works.apply_margin column stays untouched (backward-compat with
existing rows), but no longer exposed in UI. Tests updated to new
semantic. All existing tests green.

== 2. Full i18n audit on client-facing portal — RO/RU separated ==

Problem: user selecting Russian saw Romanian mixed into headings,
buttons, labels. Every client-facing Blade file was 100% hardcoded
Romanian — zero __() calls.

Fix: created lang/ro/portal.php + lang/ru/portal.php with 131 keys
across 3 namespaces:
  - portal.common (email, phone, save, total, powered_by, ...)
  - portal.invitation (welcome_name, activate_account, expired_body, ...)
  - portal.tracking (title_fisa, approve, approval_needed_title,
                    ready_estimated, hours, unit_pcs, ...)
  - portal.shop (catalog, cart, checkout_title, order_number, vin_title,
                signin_title, add_to_cart, in_stock, ...)

Converted 15 Blade files to __() calls:
  - resources/views/invitations/{accept,expired,invalid}.blade.php
  - resources/views/tracking/show.blade.php
  - resources/views/shop/{layout,catalog,cart,checkout,order,account,part,vin}.blade.php
  - resources/views/shop/auth/{login,register,forgot,reset}.blade.php

Each view's <html lang="{{ app()->getLocale() }}"> now reflects the
resolved locale (was hardcoded lang="ro").

SetLocale middleware resolves locale in this order:
  1. session locale (user picked via language switcher)
  2. authenticated user.locale
  3. tenant.settings.language
  4. app.locale default (now 'ro')

Config change: config/app.php default locale + fallback both = 'ro'
(was 'en'). English falls back to Romanian for portal.* keys since
we don't ship English portal translations — a Romanian shop that
switches to English shows Romanian text, which is safer than showing
"portal.invitation.activate_account" literals.

phpunit.xml sets APP_LOCALE=ro so test assertSee() calls that look
for Romanian text pass.

Verified via portal.* grep: 131 __() calls across 15 files. Zero
hardcoded Romanian nouns/verbs left in any client-facing view.

Suite: 303 passed (840 assertions). Unchanged count — refactor,
not new tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-13 04:56:04 +00:00