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>
This commit is contained in:
2026-09-01 09:57:54 +00:00
parent 06081159b6
commit 3c92ea5475
8 changed files with 190 additions and 7 deletions
@@ -214,6 +214,21 @@ class TenantPanelProvider extends PanelProvider
</style> </style>
BLADE) BLADE)
) )
// Small hint banner at the top of the main tenant dashboard.
->renderHook(
PanelsRenderHook::PAGE_START,
fn (): string => Blade::render(<<<'BLADE'
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;font-size:12px;color:#6b7280;">
<span>💡 {{ __('Fiecare card e clicabil — duce la lista relevantă.') }}</span>
<x-hint key="finante.dashboard.stats" />
<span style="margin-left:auto;font-size:11px;">
{{ __('Nu vezi „?" pe pagini?') }}
<a href="{{ url('/app/faq') }}" style="color:#2563eb;">{{ __('Activează sugestiile') }} </a>
</span>
</div>
BLADE),
scopes: [Dashboard::class],
)
->renderHook( ->renderHook(
PanelsRenderHook::BODY_END, PanelsRenderHook::BODY_END,
fn (): string => Blade::render(<<<'BLADE' fn (): string => Blade::render(<<<'BLADE'
+159
View File
@@ -97,6 +97,165 @@ class Hints
'en' => 'Each WO has a unique public token. "Tracking link" opens the customer-facing page you can send via SMS/WhatsApp/Telegram — status, ETA, photos.', 'en' => 'Each WO has a unique public token. "Tracking link" opens the customer-facing page you can send via SMS/WhatsApp/Telegram — status, ETA, photos.',
], ],
], ],
'wo.dashboard.topbar_actions' => [
'area' => 'service',
'title' => [
'ro' => 'Butoane rapide (top)',
'ru' => 'Быстрые кнопки (верх)',
'en' => 'Top-bar quick actions',
],
'body' => [
'ro' => '„Listă" — înapoi la toate fișele. „+ Nou" — creează una nouă (după save aterizezi tot pe dashboard). „Editare completă" — deschide formularul detaliat cu toate câmpurile. „Link tracking" — pagina publică pentru client.',
'ru' => '«Список» — все заказы. «+ Новый» — создать (после сохранения снова дашборд). «Полное редактирование» — детальная форма. «Ссылка отслеживания» — публичная страница для клиента.',
'en' => '"Listă" — back to all WOs. "+ Nou" — create a new one. "Editare completă" — full detail form. "Link tracking" — customer-facing public page.',
],
],
'wo.dashboard.meta' => [
'area' => 'service',
'title' => [
'ro' => 'Meta-antet (Data / ETA / Responsabil / Urgență)',
'ru' => 'Мета-шапка (даты / ETA / мастер / срочность)',
'en' => 'Meta header (dates / ETA / master / urgency)',
],
'body' => [
'ro' => 'Rezumat rapid al fișei. „Data creării" = când s-a introdus în sistem; „Deschis" = când s-a început efectiv lucrul; „ETA" = când promitem clientului. „Responsabil" = maistrul principal. „Urgență" influențează sortarea în calendar.',
'ru' => 'Быстрый обзор заказа. «Дата создания» — когда завели, «Открыт» — когда начали работать, «ETA» — обещанное клиенту время, «Ответственный» — главный мастер, «Срочность» влияет на сортировку.',
'en' => 'Quick snapshot: "Data creării" = when created; "Deschis" = actual work start; "ETA" = promised to client; "Responsabil" = primary master; "Urgență" affects calendar sort.',
],
],
'wo.dashboard.tabs' => [
'area' => 'service',
'title' => [
'ro' => 'Taburi (Lucrări/Piese/Diag/Foto/Doc/Note)',
'ru' => 'Табы (Работы/Запчасти/Диаг./Фото/Док./Заметки)',
'en' => 'Tabs (Works/Parts/Diag/Photos/Docs/Notes)',
],
'body' => [
'ro' => '„Lucrări" = manoperele efectuate. „Piese" = piesele consumate. „Diagnostic" = plângere client + concluzia mecanicului. „Foto" = poze de la lucrare. „Documente" = factura PDF + acte semnate. „Note" = observații interne, nu se văd de client.',
'ru' => '«Работы» — выполненные работы. «Запчасти» — использованные. «Диагностика» — жалоба клиента + заключение мастера. «Фото» — фотки. «Документы» — счёт + подписанные акты. «Заметки» — внутренние, клиент не видит.',
'en' => '"Works" = labor. "Parts" = consumed. "Diagnostic" = client complaint + master\'s conclusion. "Photos" = shoot from the job. "Docs" = invoice + signed papers. "Notes" = internal, not visible to client.',
],
],
'wo.dashboard.history' => [
'area' => 'service',
'title' => [
'ro' => 'Istoric reparații (mașină)',
'ru' => 'История ремонтов (авто)',
'en' => 'Repair history (vehicle)',
],
'body' => [
'ro' => 'Ultimele 5 fișe pentru această mașină. Click deschide dashboard-ul acelei fișe. Util să vezi ce s-a mai făcut înainte — evită sugestia dublă de lucrări.',
'ru' => 'Последние 5 заказов по этому авто. Клик — открыть тот заказ. Полезно проверить, что уже делали, чтобы не предлагать повторно.',
'en' => 'Last 5 WOs for this vehicle. Click opens that WO\'s dashboard. Handy to see prior work and avoid recommending duplicates.',
],
],
'wo.dashboard.finance' => [
'area' => 'service',
'title' => [
'ro' => 'Panou Finanțe (dreapta)',
'ru' => 'Панель финансов (справа)',
'en' => 'Finance panel (right)',
],
'body' => [
'ro' => 'Cost manopere + Cost piese = Subtotal. Discount = Total. Plătit = suma încasată deja (din plăți). Sold = ce mai rămâne de plătit. Totul se recalculează automat când adaugi ceva.',
'ru' => 'Стоимость работ + Стоимость запчастей = Промежуточный. − Скидка = Итого. «Оплачено» — уже внесено. «Остаток» — сколько ещё должен. Всё пересчитывается автоматом.',
'en' => 'Labor + Parts = Subtotal. Discount = Total. "Paid" = collected so far. "Balance" = still owed. Recomputes automatically.',
],
],
'wo.dashboard.timeline' => [
'area' => 'service',
'title' => [
'ro' => 'Timeline evenimente',
'ru' => 'Лента событий',
'en' => 'Event timeline',
],
'body' => [
'ro' => 'Cronologie automată: creare, deschidere, aprobare client, plăți primite, închidere, notificări trimise. Se agregă din câmpurile fișei + jurnalul de activitate. Util pentru audit.',
'ru' => 'Автохронология: создание, открытие, подтверждение клиента, оплаты, закрытие, отправки уведомлений. Собирается из полей + журнала. Полезно для аудита.',
'en' => 'Auto chronology: created, opened, approved, payments, closed, notifications sent. Aggregated from WO fields + activity log. Great for audit.',
],
],
'wo.dashboard.bottom' => [
'area' => 'service',
'title' => [
'ro' => 'Bara de jos (Prev/Next/Repetă/Închide)',
'ru' => 'Нижняя панель (Пред./След./Повтор/Закрыть)',
'en' => 'Bottom bar (Prev/Next/Repeat/Close)',
],
'body' => [
'ro' => 'Navighează rapid între fișe cu ← Precedent / Următor →. „Comandă repetată" creează o fișă nouă pre-populată cu același client + mașină. „Închide fișă" o marchează ca finalizată (status → done).',
'ru' => 'Быстрое переключение ← Пред. / След. →. «Повторный заказ» создаёт новый с теми же клиентом+авто. «Закрыть заказ» помечает как выполненный.',
'en' => 'Quick nav ← Prev / Next →. "Repeat order" creates a new WO pre-filled with same client + vehicle. "Close WO" marks it done.',
],
],
'wo.create.overview' => [
'area' => 'service',
'title' => [
'ro' => 'Fișă nouă — pași minimali',
'ru' => 'Новый заказ — минимальные шаги',
'en' => 'New WO — minimum steps',
],
'body' => [
'ro' => 'Obligatoriu: Client, Mașină, Data deschiderii. Numărul se generează automat (WO-{tenant}-{an}-{seq}). Poți adăuga direct un client nou / mașină nouă din meniul dropdown al câmpului. După save aterizezi pe dashboard.',
'ru' => 'Обязательно: клиент, авто, дата открытия. Номер — авто. Клиента/авто можно добавить прямо из выпадашки. После сохранения — дашборд.',
'en' => 'Required: Client, Vehicle, Opened at. Number is auto-generated. You can add a client/vehicle inline from the dropdown. After save you land on the dashboard.',
],
],
'crm.clients.overview' => [
'area' => 'crm',
'title' => [
'ro' => 'Clienți — baza de date',
'ru' => 'Клиенты — база',
'en' => 'Clients database',
],
'body' => [
'ro' => 'Fiecare client are: nume, telefon, email, adresă, sursă (de unde ne-a găsit), notițe. Poți lega un cont Telegram — atunci mesajele merg automat pe Telegram. „Datorii" arată totalul restant din toate fișele deschise.',
'ru' => 'У клиента: имя, телефон, email, адрес, источник, заметки. Можно привязать Telegram — тогда сообщения уходят в него. «Долг» — сумма непогашенных заказов.',
'en' => 'Each client: name, phone, email, address, source, notes. You can link a Telegram account so messages route there. "Debt" totals unpaid WOs.',
],
],
'crm.vehicles.overview' => [
'area' => 'crm',
'title' => [
'ro' => 'Automobile — istoricul mașinilor',
'ru' => 'Автомобили — история',
'en' => 'Vehicles — history',
],
'body' => [
'ro' => 'Fiecare mașină aparține unui client și are: marca, model, an, VIN, plăcuță, motor, kilometraj. „Decode VIN" (buton pe tabelul mașinilor) folosește AI pentru a completa datele tehnice din VIN. Poți încărca poze pe mașină — prima apare pe dashboard fișă.',
'ru' => 'У авто: марка, модель, год, VIN, номер, двигатель, пробег. «Decode VIN» — AI-заполнение по VIN. Фото на авто — первое показывается на дашборде заказа.',
'en' => 'Each vehicle belongs to a client: make, model, year, VIN, plate, engine, mileage. "Decode VIN" (table action) uses AI to fill technical fields. Vehicle photos — first one shows on the WO dashboard.',
],
],
'finante.dashboard.stats' => [
'area' => 'finante',
'title' => [
'ro' => 'Widget-uri dashboard (numere)',
'ru' => 'Виджеты дашборда (числа)',
'en' => 'Dashboard widgets (numbers)',
],
'body' => [
'ro' => 'Fiecare card e clicabil și duce la pagina relevantă cu filtrele setate. Ex: „Fișe în lucru" → lista filtrată pe status=in_work. „Datorii clienți" → fișe cu pay_status=unpaid. Cardurile se recalculează automat la fiecare vizită.',
'ru' => 'Каждая карточка кликабельна и ведёт на страницу с настроенным фильтром. «Заказы в работе» → список по статусу in_work. «Долги» → неоплаченные. Пересчёт при каждом заходе.',
'en' => 'Each stat card is clickable and jumps to the relevant page with filters applied. E.g. "Fișe în lucru" → list filtered by status=in_work. Recomputed on every visit.',
],
],
'finante.expenses.overview' => [
'area' => 'finante',
'title' => [
'ro' => 'Cheltuieli — flux de bani ieșit',
'ru' => 'Расходы — исходящий поток',
'en' => 'Expenses — money out',
],
'body' => [
'ro' => 'Categorie (chirie/utilități/piese/salarii/altele), sumă, dată, poza chitanței. Cheltuielile aici scad din profit (Dashboard → „Profit (luna)"). Distinct de „Achiziții" (piese pentru stoc care se recuperează la vânzare).',
'ru' => 'Категория (аренда/коммунальные/запчасти/зарплата/др.), сумма, дата, фото чека. Уменьшают прибыль. Отличается от «Закупки» (запчасти в склад).',
'en' => 'Category (rent/utilities/parts/salary/other), amount, date, receipt photo. Deducts from profit. Distinct from "Achiziții" (parts stocked to warehouse).',
],
],
'wo.dashboard.chat' => [ 'wo.dashboard.chat' => [
'area' => 'service', 'area' => 'service',
'title' => [ 'title' => [
+3
View File
@@ -80,6 +80,7 @@
"Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat": "Enables the public shop at <slug>.service.mir.md/shop. Parts appear only if marked \"Published\".", "Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat": "Enables the public shop at <slug>.service.mir.md/shop. Parts appear only if marked \"Published\".",
"Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat\".": "Enables the public shop at <slug>.service.mir.md/shop. Parts appear only if marked \"Published\".", "Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat\".": "Enables the public shop at <slug>.service.mir.md/shop. Parts appear only if marked \"Published\".",
"Activează plata prin transfer": "Enable bank transfer", "Activează plata prin transfer": "Enable bank transfer",
"Activează sugestiile": "Turn on hints",
"Activi": "Active", "Activi": "Active",
"Activitate recentă": "Recent activity", "Activitate recentă": "Recent activity",
"Activă": "Active", "Activă": "Active",
@@ -843,6 +844,7 @@
"Față-Stânga": "Front-Left", "Față-Stânga": "Front-Left",
"Februarie": "February", "Februarie": "February",
"Femeie": "Female", "Femeie": "Female",
"Fiecare card e clicabil — duce la lista relevantă.": "Every card is clickable — jumps to the relevant list.",
"File": "File", "File": "File",
"Files": "Files", "Files": "Files",
"Filter": "Filter", "Filter": "Filter",
@@ -1359,6 +1361,7 @@
"Nu pot porni camera: ": "Cannot start camera: ", "Nu pot porni camera: ": "Cannot start camera: ",
"Nu re-trimite mai des de X zile": "Don't resend more often than every X days", "Nu re-trimite mai des de X zile": "Don't resend more often than every X days",
"Nu te poți șterge pe tine!": "You can't delete yourself!", "Nu te poți șterge pe tine!": "You can't delete yourself!",
"Nu vezi „?\" pe pagini?": "Don't see \"?\" on pages?",
"Nu, renunță": "No, cancel", "Nu, renunță": "No, cancel",
"Numar": "Number", "Numar": "Number",
"Number": "Number", "Number": "Number",
+3
View File
@@ -80,6 +80,7 @@
"Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat": "Активирует публичный магазин на <slug>.service.mir.md/shop. Запчасти появляются только если отмечены как «Опубликовано».", "Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat": "Активирует публичный магазин на <slug>.service.mir.md/shop. Запчасти появляются только если отмечены как «Опубликовано».",
"Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat\".": "Активирует публичный магазин на <slug>.service.mir.md/shop. Запчасти появляются только если отмечены как «Опубликовано».", "Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat\".": "Активирует публичный магазин на <slug>.service.mir.md/shop. Запчасти появляются только если отмечены как «Опубликовано».",
"Activează plata prin transfer": "Включить оплату переводом", "Activează plata prin transfer": "Включить оплату переводом",
"Activează sugestiile": "Включить подсказки",
"Activi": "Активные", "Activi": "Активные",
"Activitate recentă": "Недавняя активность", "Activitate recentă": "Недавняя активность",
"Activă": "Активна", "Activă": "Активна",
@@ -843,6 +844,7 @@
"Față-Stânga": "Перед-Лево", "Față-Stânga": "Перед-Лево",
"Februarie": "Февраль", "Februarie": "Февраль",
"Femeie": "Женский", "Femeie": "Женский",
"Fiecare card e clicabil — duce la lista relevantă.": "Каждая карточка кликабельна — открывает нужный список.",
"File": "Файл", "File": "Файл",
"Files": "Файлы", "Files": "Файлы",
"Filter": "Фильтр", "Filter": "Фильтр",
@@ -1359,6 +1361,7 @@
"Nu pot porni camera: ": "Не удалось запустить камеру: ", "Nu pot porni camera: ": "Не удалось запустить камеру: ",
"Nu re-trimite mai des de X zile": "Не отправлять повторно чаще чем раз в X дней", "Nu re-trimite mai des de X zile": "Не отправлять повторно чаще чем раз в X дней",
"Nu te poți șterge pe tine!": "Нельзя удалить себя!", "Nu te poți șterge pe tine!": "Нельзя удалить себя!",
"Nu vezi „?\" pe pagini?": "Не видишь «?» на страницах?",
"Nu, renunță": "Нет, отменить", "Nu, renunță": "Нет, отменить",
"Numar": "Номер", "Numar": "Номер",
"Number": "Номер", "Number": "Номер",
@@ -156,7 +156,7 @@
<div class="cv-head"> <div class="cv-head">
<div> <div>
<div class="cv-breadcrumb">{{ __('CRM Programări ') }} <strong style="color:var(--cv-text);">{{ __('Calendar vizual') }}</strong></div> <div class="cv-breadcrumb">{{ __('CRM Programări ') }} <strong style="color:var(--cv-text);">{{ __('Calendar vizual') }}</strong></div>
<h1>{{ __('Calendar vizual') }}</h1> <h1>{{ __('Calendar vizual') }}<x-hint key="crm.calendar.overview" /></h1>
</div> </div>
<div class="cv-btn-group"> <div class="cv-btn-group">
@php @php
@@ -49,7 +49,7 @@
<div class="wiz-card"> <div class="wiz-card">
{{-- STEP 1: Upload --}} {{-- STEP 1: Upload --}}
@if ($step === 1) @if ($step === 1)
<h2 style="font-size:18px;font-weight:600;margin-bottom:16px;">{{ __('Pasul 1: Încarcă fișierul') }}</h2> <h2 style="font-size:18px;font-weight:600;margin-bottom:16px;">{{ __('Pasul 1: Încarcă fișierul') }}<x-hint key="depozit.excel.import" /></h2>
<div class="wiz-field"> <div class="wiz-field">
<label>{{ __('Furnizor *') }}</label> <label>{{ __('Furnizor *') }}</label>
@@ -199,7 +199,7 @@
<div class="pb-shell" x-data="{ view: 'kanban', dragKey: null }" wire:poll.10s> <div class="pb-shell" x-data="{ view: 'kanban', dragKey: null }" wire:poll.10s>
{{-- TOPBAR --}} {{-- TOPBAR --}}
<div class="pb-topbar"> <div class="pb-topbar">
<span class="pb-topbar-title">{{ __('Pipeline') }}</span> <span class="pb-topbar-title">{{ __('Pipeline') }}<x-hint key="crm.pipeline.overview" /></span>
<div class="pb-topbar-sep"></div> <div class="pb-topbar-sep"></div>
<div class="pb-search"> <div class="pb-search">
<span class="pb-search-icon">🔍</span> <span class="pb-search-icon">🔍</span>
@@ -385,6 +385,7 @@
<span style="opacity:.6;"></span> <span style="opacity:.6;"></span>
</span> </span>
<div class="wd-topbar-actions"> <div class="wd-topbar-actions">
<x-hint key="wo.dashboard.topbar_actions" />
<a class="wd-btn" href="{{ url('/app/work-orders') }}" title="{{ __('Toate fișele') }}"> <a class="wd-btn" href="{{ url('/app/work-orders') }}" title="{{ __('Toate fișele') }}">
{{ __('Listă') }} {{ __('Listă') }}
</a> </a>
@@ -405,7 +406,7 @@
{{-- ── META HEADER ── --}} {{-- ── META HEADER ── --}}
<div class="wd-meta"> <div class="wd-meta">
<div class="wd-meta-item"> <div class="wd-meta-item">
<span class="wd-meta-lbl">{{ __('Data creării') }}</span> <span class="wd-meta-lbl">{{ __('Data creării') }}<x-hint key="wo.dashboard.meta" /></span>
<span class="wd-meta-val">{{ $wo->created_at?->format('d.m.Y H:i') ?? '—' }}</span> <span class="wd-meta-val">{{ $wo->created_at?->format('d.m.Y H:i') ?? '—' }}</span>
</div> </div>
<div class="wd-meta-item"> <div class="wd-meta-item">
@@ -541,7 +542,7 @@
{{-- REPAIR HISTORY --}} {{-- REPAIR HISTORY --}}
<div class="wd-card"> <div class="wd-card">
<div class="wd-card-hd"> <div class="wd-card-hd">
<h3>{{ __('Istoric reparații') }}</h3> <h3>{{ __('Istoric reparații') }}<x-hint key="wo.dashboard.history" /></h3>
</div> </div>
@forelse ($history as $h) @forelse ($history as $h)
<div class="wd-history-item"> <div class="wd-history-item">
@@ -560,6 +561,7 @@
{{-- ═══ MIDDLE: TABS ═══ --}} {{-- ═══ MIDDLE: TABS ═══ --}}
<div class="wd-col-mid"> <div class="wd-col-mid">
<div class="wd-tabs-bar"> <div class="wd-tabs-bar">
<x-hint key="wo.dashboard.tabs" />
@foreach ($tabs as $key => $t) @foreach ($tabs as $key => $t)
<button type="button" class="wd-tab" :class="tab === '{{ $key }}' && 'active'" @click="tab = '{{ $key }}'"> <button type="button" class="wd-tab" :class="tab === '{{ $key }}' && 'active'" @click="tab = '{{ $key }}'">
{{ $t['label'] }} {{ $t['label'] }}
@@ -740,7 +742,7 @@
<div class="wd-col-right"> <div class="wd-col-right">
<div class="wd-card"> <div class="wd-card">
<div class="wd-card-hd"> <div class="wd-card-hd">
<h3>{{ __('Finanțe') }}</h3> <h3>{{ __('Finanțe') }}<x-hint key="wo.dashboard.finance" /></h3>
</div> </div>
<div style="display:flex;flex-direction:column;gap:6px;font-size:12px;"> <div style="display:flex;flex-direction:column;gap:6px;font-size:12px;">
<div style="display:flex;justify-content:space-between;"> <div style="display:flex;justify-content:space-between;">
@@ -776,7 +778,7 @@
{{-- TIMELINE --}} {{-- TIMELINE --}}
<div class="wd-card"> <div class="wd-card">
<div class="wd-card-hd"> <div class="wd-card-hd">
<h3>{{ __('Timeline') }}</h3> <h3>{{ __('Timeline') }}<x-hint key="wo.dashboard.timeline" /></h3>
</div> </div>
@if (! empty($timeline)) @if (! empty($timeline))
<div class="wd-timeline"> <div class="wd-timeline">
@@ -854,6 +856,7 @@
{{-- ── BOTTOM ACTION BAR ── --}} {{-- ── BOTTOM ACTION BAR ── --}}
<div class="wd-bottom"> <div class="wd-bottom">
<x-hint key="wo.dashboard.bottom" />
<div class="wd-bottom-nav"> <div class="wd-bottom-nav">
@if ($prev) @if ($prev)
<a class="wd-btn" href="{{ url('/app/work-orders/' . $prev->id . '/dashboard') }}"> <a class="wd-btn" href="{{ url('/app/work-orders/' . $prev->id . '/dashboard') }}">