Commit Graph

237 Commits

Author SHA1 Message Date
Vasyka 1029181526 feat(nav): nest Calendar vizual under Calendar (Programări) in sidebar
CalendarBoard::getNavigationParentItem() now returns the Appointments
resource nav label so the sidebar collapses them into a single
expandable menu item. Routes and logic unchanged — both
/app/appointments and /app/calendar-board still work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 18:56:59 +00:00
Vasyka 6e7c665433 feat(dashboard): clickable stat cards + 2 new WO counters + parts row link
Every card on the tenant main dashboard now links to a relevant page:

Operations widget (was 5 cards, now 7):
- Clienți → /app/clients
- Mașini → /app/vehicles
- Cereri noi → /app/leads pre-filtered by status=new
- Deal-uri active → /app/deals
- Programări azi → /app/calendar-board
- NEW: Fișe în lucru (in_work + awaiting_parts) → /app/work-orders filter
- NEW: Fișe gata predare (ready) → /app/work-orders filter

Finance widget (all 4 cards clickable now):
- Încasări → /app/payments
- Cheltuieli → /app/expenses
- Profit → /app/reports (with "Vezi raportul financiar" hint)
- Datorii → /app/work-orders pre-filtered by pay_status=unpaid

LowStockTable rows now navigate to /app/parts/{id}/edit on click and
have an explicit edit action for touch users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:55:39 +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 e289fbc068 feat(vehicle): dedicated photo upload with MediaLibrary + form field
Vehicle model now implements HasMedia + InteractsWithMedia and
registers a 'photos' collection. VehicleResource form gets a
SpatieMediaLibraryFileUpload section (collapsible, max 6 photos,
imageEditor enabled).

Dashboard already checks vehicle->getFirstMediaUrl('photos') first
and falls back to the WO's photos, so uploading here immediately
populates the vehicle card on the dashboard without any view changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:41:09 +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 f82a3ebc57 fix(work-order-dashboard): rename route param to {wo} to bypass model binding
Root cause of the "Fișa #0" bug: the page had
    public ?WorkOrder \$record = null;
and the route slug was work-orders/{record}/dashboard. Livewire/Laravel
saw the {record} param and the typed \$record property with the same
name and attempted route model binding via BelongsToTenant scope. When
that resolution didn't return a Model instance in a Livewire hydration
context, mount() ended up being called with 0, so the button worked
but the destination page redirected saying "Fișa #0".

Fix: rename the route parameter to {wo} so it no longer collides with
the property name, receive it as int|string in mount(), and do the
find() ourselves. The property stays as \$record for the Blade view
but is now always populated by our own code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:27:38 +00:00
Vasyka bd8e42d3d1 diag(work-order-dashboard): log every mount + include URL in error
Adds Log::warning() on every mount() so we can trace exactly what
record id and URL the button navigates to. Also includes the full URL
in the "not found" alert so the user can copy it back to us.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:23:28 +00:00
Vasyka fbb0bcb7e1 fix(edit-wo): pass panel='tenant' to Page::getUrl()
Without an explicit panel, WorkOrderDashboard::getUrl() resolves the
route name against Filament's current-or-default panel, which in some
Livewire lifecycles or from ambiguous contexts is 'central' — where
the page isn't registered. Symptom: the produced URL ended up empty
or malformed, and clicking the button hit our friendly redirect
claiming WO #0 doesn't exist.

Also removes the temporary /__wo-diag/{id} route added for diagnosis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:19:19 +00:00
Vasyka 07a4fb8948 temp(diag): add /__wo-diag/{id} route to inspect prod state
Returns tenant context, WO visibility via scope, and the current
dashboard button URL that Filament's Page::getUrl() produces. Will be
removed once the bug is diagnosed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:17:45 +00:00
Vasyka 098b8e5204 fix(edit-wo): use Filament's Page::getUrl() for dashboard button
Filament's static ::getUrl(['record' => id]) is the canonical way to
build a page URL with parameters, and it uses the actual route resolver
(handling tenant panel prefix, etc). This is cleaner than hand-building
the path and avoids any \$this-binding ambiguity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:08:40 +00:00
Vasyka 6aa9bfb769 fix(edit-wo): fall back to URL segment for dashboard link
Under some Livewire lifecycles \$this->record isn't hydrated when
getHeaderActions() runs, so \$this->record->id was null and the URL
came out as /app/work-orders//dashboard. Fall back to the URL segment
(always present because we're at /app/work-orders/{id}/edit) and log
which path was taken to help diagnose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:57:28 +00:00
Vasyka 5301c98621 fix(edit-wo): build dashboard URL eagerly, outside action closure
The action ->url() closure was resolving \$this to the Action instance
and its ->getRecord() returned null in header-action context, so the URL
came out as /app/work-orders//dashboard.

getHeaderActions() runs after mount(), so \$this->record is guaranteed
populated. Compute the URL string once and pass it as a plain value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:52:23 +00:00
Vasyka 67a87c5a67 fix(edit-wo): resolve dashboard URL via $livewire, not $this
Inside a Filament Action's ->url() closure, \$this can bind to the
Action instance rather than the Livewire page — so \$this->record was
null and the generated URL became /app/work-orders//dashboard, which
routed as record='' and hit our friendly redirect for id 0.

Switch to \$livewire and prefer getRecord() which is always populated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:46:52 +00:00
Vasyka c33e96caf8 fix(work-order-dashboard): use Livewire's $this->redirect() in mount
Livewire's Redirector doesn't have ->send(); the correct API inside a
Livewire mount() is $this->redirect($url).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:44:08 +00:00
Vasyka 33d1675182 feat(work-order-dashboard): friendly redirect for missing/foreign WO
Instead of a bare 404 when a work-order id doesn't resolve, redirect to
the list with a persistent notification explaining why: either the WO
doesn't exist, or it belongs to a different tenant.

This is the most common 404 cause: user types /app/work-orders/2/dashboard
but WO id 2 is in another tenant's slice — the tenant scope filters it
out and find() returns null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:37:00 +00:00
Vasyka 114ae31b15 fix(work-order-dashboard): mount signature + null-safe record property
Made record property nullable (?WorkOrder) and mount() calls find() +
explicit abort(404) instead of findOrFail() (which pass-through the
model's tenant-scoped scope but doesn't clarify the 404 source).

Added Feature test that boots the dashboard route via tenant subdomain
(psauto.service.mir.md-style host), authenticates a user, and verifies
the page returns non-404, non-500. Test passes both with and without
route:cache, so the routing itself is fine — user-side 404 is most
likely browser cache or CDN cache.

307/307 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:28:54 +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 6de9c615f7 fix(injector-protocols): derive protocol prefix from tenant slug (not hardcoded PS)
'PS' was baked in — wrong for multi-tenant. Now:
  1. Honor $tenant->settings['protocol_prefix'] if set
  2. Else use uppercase first 2 chars of tenant slug
     ('psauto' → 'PS', 'autoplus' → 'AP', 'plusrepair' → 'PL')
  3. Fallback 'PR' if no tenant resolvable

Format changed from 'PS-{companyId}-{year}-{seq}' to '{PREFIX}-{year}-{seq}'
— dropped the redundant companyId since prefix already identifies the
tenant contextually.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:52:14 +00:00
Vasyka 872bf80a13 i18n(nav): actually add Protocoale forsunki to RU + EN nav.php
Prior commit only landed the RO entry; the RU + EN Edits were silently
skipped (file-not-read guard). Now all 3 langs have the label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:49:31 +00:00
Vasyka cfe5aa622a i18n(nav): add Protocoale forsunki (was missing from all 3 nav.php files)
Previous sed insert-after-'Programări' silently failed because that key
didn't exist. Now placed alphabetically after 'Procentaj' in all 3
lang/nav.php files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:49:05 +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 f484167b51 fix(migration): case-insensitive dedupe on units backfill + use insertOrIgnore
MariaDB default collation is case-insensitive on VARCHAR unique indexes,
so 'L' and 'l' clash on units_company_id_code_unique. When a tenant had
'L' in parts.unit (uppercase) and the migration seeds 'l' (lowercase),
the extras loop tried to re-insert 'L' and errored out — blocking all
subsequent migrations (including the new injector_protocols tables).

Fix: lowercase the comparison + switch the extras insert to
insertOrIgnore so a residual race is a no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:39:34 +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 0e85035eff i18n: add 'Toate statusurile' (calendar-board status filter option)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:02:35 +00:00
Vasyka de2964c854 i18n(wo relations): translated snapshot names + explicit empty states
- WorksRelationManager Name column: getStateUsing(labor?->label()) so
  the row shows the labor's localized name (not the raw snapshot);
  fallback to stored snapshot when labor is missing.
- 4 explicit empty states with helper description + icon on Works,
  Parts, SubcontractJobs, Payments — replaces Filament's auto-generated
  'Не найдено X / Создать X для старта.'
- 4 CreateAction titles / modal headings for all four relation managers.
- Notification 'Piesa returnată în stoc' / 'Nimic de restituit' wrapped.
- +11 translation keys.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:59:47 +00:00
Vasyka 9609295a9e feat(labors): trilingual name — add name_en column + Labor::label() helper
- Migration: add nullable `labors.name_en` alongside existing name_ro/
  name_ru.
- Labor model: label(?locale) accessor returns name_{locale} with
  name_ro fallback.
- LaborResource form: expose 3rd 'Nume (EN)' field.
- Table + Select displays now go through label() so options show the
  locale-appropriate name:
    /app/labors table column,
    WorkOrderResource works Select ('[category] name (Nh)'),
    ServiceTemplate items Select,
    ServiceComposer WorkOrderWork snapshot on create.
- Snapshot name saved on wo_works.name at creation reflects the
  current locale; existing rows keep their stored snapshot untouched.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:54:00 +00:00
Vasyka 64a170b7f9 i18n(work-order works): translate labor Select category + h suffix + modal titles
- Labor Select options now build '[__(category)] name_ro (hours __(h))'
  so category chip and hour suffix translate on RU/EN. Labor name stays
  as name_ro (user data — separate long-term concern).
- CreateAction gets explicit label + modalHeading (was 'Create Work
  Order Work' auto-generated).
- Notification 'Piese implicite adăugate (N)' rewritten with __(':n')
  placeholder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:47:00 +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 6b5ab02e68 i18n(masters): translate Specializare column tokens (Edit was skipped)
Prior 'Everything up-to-date' commit was a no-op because Edit failed.
Now the change lands. Splits specialization by '/' and __()-translates
each token, matching Reports.php + CalendarBoard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:42:26 +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 8228689580 i18n(leads): +12 translations (sections, SOURCES, STATUSES, empty state)
Sections Mesaj client / Sursă & Atribuire / Marketing (UTM), the full
empty-state description, plus previously-missing Lead::SOURCES enum
values (Manual, Apel, Site, Google Maps, SEO, Recomandare) and
STATUSES (Contactat, Convertit, Pierdut).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:43:03 +00:00
Vasyka 8c2f10b5a5 i18n(bodyshop-jobs): +48 translations covering resource + enums + damage map
Full coverage for bodyshop resource: sections (Asigurare, Daune,
Status dosar), all TYPES enum (Tinichigerie, PDR, Vopsitorie,
Detailing, Ceramică, PPF, Polish), STATUSES + INSURANCE_STATUSES,
damage-point SEVERITIES, all 20 damage ZONES (Bară față, Capotă,
Aripă FS/FD/SS/SD, Ușă F/S ×2, Bara spate, Portbagaj, Plafon,
Parbriz, Lunetă, Prag S/D, Oglindă S/D, Jantă), and 6 damage KINDS
(Zgârietură, Lovitură, Fisură, Rugină, Vopsea sărită, Spart).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:40:53 +00:00
Vasyka 93f17e8945 i18n(backup): +10 translations covering full backup + export page
Page title, download button, format description, use-cases list,
sensitive-data warning, spinner text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:38:41 +00:00
Vasyka 7d81e69ca4 i18n(api-tokens): +17 translations for full page + token-generated banner
Covers page title, generate/revoke actions, freshly-created token
banner (copy/hide buttons + warning), permissions column, empty state,
endpoint documentation section (helpers + subdomain note).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:37:05 +00:00
Vasyka d97b9b3d6d i18n(settings): wrap delivery methods CheckboxList with I18n::opts + 3 keys
OnlineOrder::DELIVERY values (Ridicare din service / Curier / Poștă)
weren't translated; now go through I18n::opts and have RU/EN entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:36:10 +00:00
Vasyka a6f21762fc i18n(settings): fix currency I18n::opts + push notification + closing-quote strings
- Currency Select ->options() had 4 raw entries (MDL/EUR/USD/UAH)
  and 2 wrapped (RON/RUB); consolidated all 6 under I18n::opts()
- Push notification title 'Trimis pe N dispozitiv(e)' rebuilt with
  __(':n')-placeholder; empty state + helper now go through __()
- Added 4 long helper description keys with the closing „…"
  Romanian quotation marks (the previous keys were missing the
  trailing quote and dot; now match Settings.php byte-for-byte)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:33:52 +00:00
Vasyka a658e359b9 i18n(settings): +38 translations covering full company settings page
Full coverage across Logo & favicon / AI providers / Telegram bot /
Notifications (client reminders) / Shop / Currencies / Internal
margin. Includes long helper paragraphs and 'Setări salvate' /
'Tenant not resolved' notification titles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:27:37 +00:00
Vasyka 9296d9db11 i18n(permission-overrides): explicit empty state (was auto-generated class name)
Filament default rendered 'Не найдено user permission overrides /
Создать user permission override для старта.' from the model class
name. Overridden with translated heading + explanatory description
+ shield-exclamation icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:24:41 +00:00
Vasyka 07398dcbf4 fix(roles): actually apply permissionFields human labels (Edit was silently skipped)
Prior 'fix' commit was a no-op because git diff was empty. This time
the Edit lands the change on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:20:44 +00:00
Vasyka 49b491a7bd feat(rbac): human labels + descriptions for all 50 permissions
- Add Permissions::permissionLabels() mapping every slug to a
  human-readable RO label (e.g. 'clients.view_all' → 'Vezi toți
  clienții').
- RoleResource permission checkboxes now show translated labels
  as titles + technical slug as description (so admins see both
  the meaning and the key).
- PermissionOverridesRelationManager Select 'permission_id' now
  shows 'Human label (technical.slug)' format, plus helper text
  explaining GRANT/DENY. Same format on the display column.
- +57 translations (50 permission labels in RU/EN + role page
  supporting keys + module labels).

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:16:21 +00:00
Vasyka af0327d27d i18n(permission-overrides): wrap DENY option + explicit CreateAction labels
- 'DENY — interzice dreptul' select option was missing __() wrap
- CreateAction/EditAction now have explicit label/modalHeading so
  the modal shows 'Добавить исключение прав' instead of
  'Создать User Permission Override' (auto-generated from class name)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:11:37 +00:00
Vasyka 3d60e1d368 i18n(users): translate 'status' badge column + relabel Locale → Язык / Language
Status column showed raw DB enum values (active/inactive/blocked) with
no formatStateUsing → readers saw 'active' instead of the translated
'Активный / Активно' visible in the Select. Added formatStateUsing with
a match() that returns the appropriate __() key.

Locale header re-translated to 'Язык' (RU) / 'Language' (EN) since
'Локаль' is unusual for end users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:08:46 +00:00
Vasyka 58f168dd97 i18n(users): wrap role Select options with I18n::opts + 2FA status + Locale
- Both role selects (primary + additional roles multi) passed the raw
  roleLabels() array without translation; wrap with I18n::opts() so
  Proprietar/Administrator/… now render translated in RU/EN.
- 2FA status text ('✓ Activat (TOTP)' / '✗ Dezactivat') split so
  the checkmark stays and label runs through __().
- +3 translations (Activat (TOTP), Dezactivat, Locale) — Locale is
  Filament's auto-generated headline from the `locale` field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:04:30 +00:00
Vasyka 00afca8a17 i18n(users): +16 translations for sections, roles, 2FA, salary helpers
Covers Identitate/Acces/2FA sections, all helper texts on user
edit page (2FA reset, role sync, additional roles, margin helper,
salary formula), placeholder examples, RBAC role labels (Proprietar,
Vizitator, Recepție), 'Română' language option, and Permission
Overrides RelationManager helpers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:58:33 +00:00
Vasyka f96bb96fa1 i18n(posts): +4 translations (plural label + placeholder + helper + empty)
Fixes 'Posturi De Lucru' title (was RO fallback) and translates the
create form placeholder + default-mechanic helper text + no-default
option label.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:55:06 +00:00
Vasyka e43d79fea7 i18n(subcontract-jobs + filament core): +6 keys + fix missing 'no options' RU/EN
- +6 subcontract-jobs translations (Lucrare, Termene, Returnat, ETA,
  helper 'Setat manual dacă markup = 0.', empty-state description)
- Add lang/vendor/filament-forms/{ru,en}/components.php with select
  no_options_message + max_items_message. Filament ships RO copy with
  no RU override, so 'Nu sunt disponibile opțiuni.' bled through into
  RU/EN locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 10:51:42 +00:00