diff --git a/app/Providers/Filament/TenantPanelProvider.php b/app/Providers/Filament/TenantPanelProvider.php index 615d3fb..2c2e57a 100644 --- a/app/Providers/Filament/TenantPanelProvider.php +++ b/app/Providers/Filament/TenantPanelProvider.php @@ -229,6 +229,19 @@ class TenantPanelProvider extends PanelProvider BLADE), scopes: [Dashboard::class], ) + // Auto-injected hint on every page whose URL maps to a hint key. + // Reads the current request path and looks it up in Hints::forPath(). + // Blades that already embedded stay untouched — this only + // fires on pages that don't have their own. + ->renderHook( + PanelsRenderHook::PAGE_HEADER_ACTIONS_BEFORE, + fn (): string => Blade::render(<<<'BLADE' + @php $__hintKey = \App\Support\Hints::forPath(request()->path()); @endphp + @if ($__hintKey) + + @endif + BLADE), + ) ->renderHook( PanelsRenderHook::BODY_END, fn (): string => Blade::render(<<<'BLADE' diff --git a/app/Support/Hints.php b/app/Support/Hints.php index 98ea2f5..0bac8e6 100644 --- a/app/Support/Hints.php +++ b/app/Support/Hints.php @@ -242,6 +242,309 @@ class Hints '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.', ], ], + 'service.mechanic_board.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Atelierul meu', 'ru' => 'Мой цех', 'en' => 'My workshop'], + 'body' => [ + 'ro' => 'Vedere mobile-first pentru mecanic: doar fișele lui active (grupate pe status), butoane mari, actualizare status/kilometraj/foto direct din listă, fără să treci prin edit. Ideal pe tabletă în hala service.', + 'ru' => 'Мобильный интерфейс для механика: только его активные заказы (по статусу), крупные кнопки, обновление статуса/пробега/фото прямо из списка. Удобно на планшете в цехе.', + 'en' => 'Mobile-first view for a mechanic: only their active WOs (grouped by status), big buttons, update status/mileage/photos inline without opening edit. Made for tablets on the shop floor.', + ], + ], + 'service.kpi.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'KPI mecanici', 'ru' => 'KPI механиков', 'en' => 'Mechanic KPIs'], + 'body' => [ + 'ro' => 'Pentru fiecare mecanic, pentru perioada aleasă: nr. fișe închise, ore norm-facturate vs. ore reale, venit generat, marja internă. Culorile roșu/verde arată abaterea de la ținta lunară.', + 'ru' => 'По каждому механику за период: закрытые заказы, нормо-часы vs. фактические, выручка, внутренняя маржа. Красный/зелёный — отклонение от плана.', + 'en' => 'Per mechanic for the chosen period: closed WOs, billed vs. actual hours, revenue generated, internal margin. Red/green shows deviation from monthly target.', + ], + ], + 'service.labors.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Norme-ore (catalog manopere)', 'ru' => 'Нормо-часы (каталог работ)', 'en' => 'Labor norms (catalog)'], + 'body' => [ + 'ro' => 'Catalog cu toate manoperele standard: denumire (RO/RU/EN), timp normat, preț recomandat, categoria. Aici se definesc șabloanele. La adăugarea unei manopere într-o fișă, sistemul completează automat timpul și prețul.', + 'ru' => 'Каталог стандартных работ: название (RO/RU/EN), норматив времени, рекомендованная цена, категория. При добавлении в заказ время и цена подставляются автоматом.', + 'en' => 'Standard labor catalog: name (RO/RU/EN), normed time, suggested price, category. Adding a labor to a WO auto-fills time + price.', + ], + ], + 'service.masters.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Tehnicieni (maiștri)', 'ru' => 'Мастера', 'en' => 'Technicians'], + 'body' => [ + 'ro' => 'Aici gestionezi lista de maiștri: nume, culoare (pentru calendar), tarif orar, marja internă, specializare. Sunt utilizatori cu rol „master" — pot vedea doar fișele proprii în „Atelierul meu".', + 'ru' => 'Список мастеров: имя, цвет (для календаря), почасовая ставка, маржа, специализация. Это пользователи с ролью «master» — видят только свои заказы в «Мой цех».', + 'en' => 'Manage master mechanics: name, calendar color, hourly rate, internal margin, specialty. These are users with role "master" — they only see their own WOs in "My workshop".', + ], + ], + 'service.templates.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Șabloane servicii', 'ru' => 'Шаблоны услуг', 'en' => 'Service templates'], + 'body' => [ + 'ro' => 'Pachete predefinite (ex: „Schimb ulei + filtru", „Diagnoză computerizată"). Din edit fișă → „Aplică șablon" adaugi toate manoperele + piesele deodată. Reduce dublă introducere pentru servicii recurente.', + 'ru' => 'Готовые пакеты («Замена масла + фильтр», «Компьютерная диагностика»). В заказе — «Применить шаблон» добавляет все работы + запчасти сразу. Уменьшает ручной ввод.', + 'en' => 'Predefined bundles ("Oil + filter change", "OBD diagnostic"). From WO edit → "Apply template" adds all labor + parts at once. Cuts repetitive data entry.', + ], + ], + 'service.injector.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Protocoale forsunki', 'ru' => 'Протоколы форсунок', 'en' => 'Injector protocols'], + 'body' => [ + 'ro' => 'Formular specializat pentru diagnostic injectoare (debit/pulverizare/retur pentru fiecare injector). Numărul se generează auto: {PREFIX}-{AN}-{SEQ}. Export PDF cu semnătură pentru client.', + 'ru' => 'Специализированная форма для диагностики форсунок (расход/распыл/возврат по каждой). Номер: {PREFIX}-{ГОД}-{SEQ}. PDF с подписью для клиента.', + 'en' => 'Specialized form for injector diagnostics (flow/spray/return per injector). Number auto-generated: {PREFIX}-{YEAR}-{SEQ}. Signed PDF for the customer.', + ], + ], + 'service.bodyshop.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Tinichigerie / Detailing', 'ru' => 'Кузовной / Детейлинг', 'en' => 'Bodyshop / Detailing'], + 'body' => [ + 'ro' => 'Lucrări separate de service mecanic: reparații caroserie, vopsit, polish, detailing. Deviz + foto înainte/după + status separat. Se leagă opțional de o fișă principală.', + 'ru' => 'Отдельно от механики: кузов, покраска, полировка, детейлинг. Смета + фото до/после + свой статус. Опционально привязка к главному заказу.', + 'en' => 'Separate from mechanical service: body repairs, paint, polish, detailing. Quote + before/after photos + own status. Optionally links to a parent WO.', + ], + ], + 'service.tires.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Seturi anvelope', 'ru' => 'Комплекты шин', 'en' => 'Tire sets'], + 'body' => [ + 'ro' => 'Depozitare sezonieră: set anvelope (4 buc) legat de mașină, dimensiuni, DOT, adâncime protector, locație în depozit. Notificare automată la schimb sezonier pentru clienți.', + 'ru' => 'Сезонное хранение: комплект (4 шт.) на авто, размеры, DOT, глубина протектора, место на складе. Автонапоминания клиентам о сезонной замене.', + 'en' => 'Seasonal storage: 4-tire set per vehicle, sizes, DOT code, tread depth, warehouse slot. Auto reminders to clients when the seasonal swap is due.', + ], + ], + 'service.subcontract.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Lucrări terți (subcontract)', 'ru' => 'Работы у подрядчика', 'en' => 'Subcontract jobs'], + 'body' => [ + 'ro' => 'Când o lucrare pleacă la un partener extern (ex: rectificare bloc). Se leagă de o fișă activă, se înregistrează costul plătit terțului + prețul cerut clientului. Diferența = marja pe subcontract.', + 'ru' => 'Работа уходит к внешнему партнёру (напр. шлифовка блока). Привязка к заказу, стоимость подрядчика + цена клиенту. Разница = маржа.', + 'en' => 'When work goes out to an external partner (e.g. head resurfacing). Ties to an active WO — logs cost paid to subcontractor + price charged to client. Delta = subcontract margin.', + ], + ], + 'service.subcontractors.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Subcontractori (parteneri)', 'ru' => 'Подрядчики', 'en' => 'Subcontractors'], + 'body' => [ + 'ro' => 'Lista partenerilor externi: nume, telefon, specializare (rectificări, geometrie roți, tapițerie). Se atașează la Lucrări terți pentru istoric complet + statistici pe partener.', + 'ru' => 'Список внешних партнёров: имя, телефон, специализация. Подставляются в «Работы у подрядчика» для истории + статистики.', + 'en' => 'External partner list: name, phone, specialty (machining, wheel alignment, upholstery). Referenced from Subcontract jobs for history + per-partner stats.', + ], + ], + + // CRM extras + 'crm.calls.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Apeluri client', 'ru' => 'Звонки клиентам', 'en' => 'Customer calls'], + 'body' => [ + 'ro' => 'Jurnal manual + integrare SIP (dacă e configurată): apel intrat/ieșit, durată, notițe, legarea de un client sau lead. Util pentru follow-up și evaluarea echipei.', + 'ru' => 'Ручной журнал + интеграция SIP (если настроена): входящие/исходящие, длительность, заметки, привязка к клиенту/лиду. Для follow-up и оценки команды.', + 'en' => 'Manual log + SIP integration (if configured): inbound/outbound, duration, notes, tied to a client or lead. Useful for follow-up and team review.', + ], + ], + + // Depozit extras + 'depozit.purchases.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Achiziții de la furnizor', 'ru' => 'Закупки у поставщика', 'en' => 'Supplier purchases'], + 'body' => [ + 'ro' => 'Documentul de intrare (aviz + factură) al pieselor: furnizor, dată, poziții (piesă + cant + preț cost). La salvare, stocul crește automat și prețul de cost al piesei se actualizează.', + 'ru' => 'Приходный документ (накладная + счёт): поставщик, дата, позиции (запчасть + кол + цена). При сохранении остаток растёт, себестоимость обновляется.', + 'en' => 'Goods receipt document (waybill + invoice): supplier, date, lines (part + qty + cost). Saving increases stock and updates part cost.', + ], + ], + 'depozit.suppliers.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Furnizori', 'ru' => 'Поставщики', 'en' => 'Suppliers'], + 'body' => [ + 'ro' => 'Fișa furnizorului: nume, cod fiscal, contact, condiții plată. Fiecare Achiziție se leagă aici. Maparea coloanelor de la Import Excel se salvează per furnizor pentru viitor.', + 'ru' => 'Карточка поставщика: имя, реквизиты, контакт, условия оплаты. Каждая закупка привязывается сюда. Соответствие колонок Excel-импорта запоминается по поставщику.', + 'en' => 'Supplier card: name, tax ID, contact, payment terms. Every purchase links here. Excel import column mapping is remembered per supplier for next time.', + ], + ], + 'depozit.warehouses.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Depozite (locații)', 'ru' => 'Склады (локации)', 'en' => 'Warehouses (locations)'], + 'body' => [ + 'ro' => 'Dacă ai mai multe locații de stocare (ex: „Central", „Punct de lucru 2", „Vitrina"), le definești aici. Fiecare piesă are stoc separat pe depozit. Achizițiile pot merge într-un depozit specific.', + 'ru' => 'Если несколько мест хранения (напр. «Центральный», «Точка 2», «Витрина») — задай их здесь. Остатки ведутся раздельно. Закупки могут идти в конкретный склад.', + 'en' => 'If you have multiple storage locations (e.g. "Central", "Site 2", "Showroom"), define them here. Each part has per-warehouse stock. Purchases can target a specific warehouse.', + ], + ], + 'depozit.pricing.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Coeficienți de preț', 'ru' => 'Ценовые коэффициенты', 'en' => 'Pricing coefficients'], + 'body' => [ + 'ro' => 'Reguli automate: preț manoperă × coeficient pe clasa mașinii (economy/premium/luxury) × coeficient urgență (normal/urgent). Definite o dată — se aplică automat pe toate fișele noi.', + 'ru' => 'Автоправила: цена работ × коэфф. класса авто × коэфф. срочности. Настроил один раз — применяется на всех новых заказах.', + 'en' => 'Auto rules: labor price × vehicle-class coeff × urgency coeff. Set once — applies to all new WOs automatically.', + ], + ], + 'depozit.markup.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Reguli procentaj (markup)', 'ru' => 'Правила наценки', 'en' => 'Markup rules'], + 'body' => [ + 'ro' => 'Cât adaugi la costul piesei pentru preț client, în funcție de: categoria piesei, prețul cost (praguri), furnizor. Ex: „Piese sub 100 MDL → +40%, peste → +25%". Se aplică automat la achiziții.', + 'ru' => 'Наценка от закупки к цене клиента по: категории, ценовому диапазону, поставщику. Пример: «до 100 MDL → +40%, свыше → +25%». Применяется автоматом на закупках.', + 'en' => 'Cost-to-client markup by: part category, cost thresholds, supplier. E.g. "Under 100 MDL → +40%, above → +25%". Applied automatically on purchases.', + ], + ], + 'depozit.units.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'Unități de măsură', 'ru' => 'Единицы измерения', 'en' => 'Units of measure'], + 'body' => [ + 'ro' => 'Buc / L / kg / m / set etc., cu denumire în 3 limbi. Se folosesc pe piese și pe achiziții pentru claritate în facturi și avize.', + 'ru' => 'Шт. / Л / кг / м / компл. — с названием на 3-х языках. Используются в запчастях и закупках для ясности в документах.', + 'en' => 'Pcs / L / kg / m / set — with names in 3 languages. Used on parts and purchases for clarity on invoices.', + ], + ], + 'depozit.vin_search.overview' => [ + 'area' => 'depozit', 'title' => ['ro' => 'VIN-căutare (piese OEM)', 'ru' => 'VIN-поиск (OEM)', 'en' => 'VIN part search'], + 'body' => [ + 'ro' => 'Introdu VIN-ul mașinii → AI-ul decodează marca/modelul/anul și sugerează codurile OEM ale pieselor necesare pentru operația căutată (ex: „plăcuțe frână față"). Copiază codul, verifică la furnizori.', + 'ru' => 'Введи VIN → AI декодирует марку/модель/год и подскажет OEM-коды нужных запчастей (напр. «передние тормозные колодки»). Копируй код и проверяй у поставщиков.', + 'en' => 'Enter a VIN → AI decodes make/model/year and suggests OEM codes for the requested operation (e.g. "front brake pads"). Copy the code and check with suppliers.', + ], + ], + + // Finanțe extras + 'finante.adjustments.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Bonusuri & avansuri', 'ru' => 'Бонусы и авансы', 'en' => 'Bonuses & advances'], + 'body' => [ + 'ro' => 'Ajustări manuale la salariul unui angajat: bonus (adaugă) sau avans (scade la salariu). Cu dată, sumă, motiv. Se aplică automat la următorul „Închide perioadă" din Salarii.', + 'ru' => 'Ручные корректировки: бонус (плюс) или аванс (минус). Дата, сумма, причина. Применяются на ближайшем «Закрыть период» в Зарплатах.', + 'en' => 'Manual salary adjustments: bonus (adds) or advance (deducts). Date, amount, reason. Auto-applied on the next Salaries "Close period".', + ], + ], + 'finante.employees.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Angajați (profile financiare)', 'ru' => 'Сотрудники (финансовые профили)', 'en' => 'Employees (finance profiles)'], + 'body' => [ + 'ro' => 'Fiecare angajat are: tarif orar de bază, marja internă (%), tip contract (procent din manoperă sau salariu fix), CNP/CIF pentru facturi. Datele astea alimentează calculul automat din Salarii.', + 'ru' => 'У каждого сотрудника: базовая ставка, внутренняя маржа, тип договора (% с работы или оклад), налоговый номер. Эти данные питают авторасчёт зарплат.', + 'en' => 'Each employee: base hourly rate, internal margin, contract type (% of labor or fixed salary), tax ID. These feed the Salaries auto-calculation.', + ], + ], + 'finante.consolidated.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Finanțe consolidat', 'ru' => 'Финансы (сводно)', 'en' => 'Finance (consolidated)'], + 'body' => [ + 'ro' => 'Vedere pe o singură pagină: încasări zi/săptămână/lună, cheltuieli, salariu de plătit, profit net. Comparație cu perioada anterioară. Ideal la începutul zilei sau pentru manager la sfârșit de lună.', + 'ru' => 'Одна страница: приход день/неделя/месяц, расход, к выплате, чистая прибыль. Сравнение с прошлым периодом. Для утреннего обзора или отчёта месяца.', + 'en' => 'Single page overview: income day/week/month, expenses, salaries due, net profit. Compared to prior period. Great for a morning check or end-of-month review.', + ], + ], + + // Magazin + 'shop.orders.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Comenzi online (magazin)', 'ru' => 'Онлайн-заказы (магазин)', 'en' => 'Online orders (shop)'], + 'body' => [ + 'ro' => 'Comenzi primite din magazin-ul public (subdomeniu tenant). Poți accepta/refuza, marca livrat, genera factură. Stocul scade automat la livrare.', + 'ru' => 'Заказы из публичного магазина (тенант-поддомен). Принять/отклонить, отметить доставку, счёт. Остаток списывается при доставке.', + 'en' => 'Orders from the public shop (tenant subdomain). Accept/refuse, mark delivered, generate invoice. Stock decrements on delivery.', + ], + ], + 'shop.customers.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Clienți magazin', 'ru' => 'Клиенты магазина', 'en' => 'Shop customers'], + 'body' => [ + 'ro' => 'Clienți care s-au înregistrat pe magazin-ul online (separați de baza CRM service). Poți converti într-un client CRM cu 1 click atunci când vine în service.', + 'ru' => 'Клиенты онлайн-магазина (отдельно от CRM-базы). При приезде в сервис — 1 клик → CRM-клиент.', + 'en' => 'Customers registered in the online shop (separate from service CRM base). One click converts to a CRM client when they show up at the service.', + ], + ], + + // Marketing + 'marketing.channels.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Canale marketing', 'ru' => 'Маркетинговые каналы', 'en' => 'Marketing channels'], + 'body' => [ + 'ro' => 'Sursele de leaduri: Google Ads, Facebook, recomandare, walk-in, telefon direct. Fiecare cerere se atribuie unui canal → vezi în Rapoarte care canal aduce cel mai bun ROI.', + 'ru' => 'Источники лидов: Google Ads, Facebook, рекомендация, walk-in, звонок. Каждая заявка привязывается к каналу → в Отчётах видишь ROI по каналу.', + 'en' => 'Lead sources: Google Ads, Facebook, referral, walk-in, direct call. Each lead ties to a channel → Reports show which channel has best ROI.', + ], + ], + 'marketing.templates.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Template-uri mesaje', 'ru' => 'Шаблоны сообщений', 'en' => 'Message templates'], + 'body' => [ + 'ro' => 'Mesaje pre-scrise pentru trimitere rapidă către client: „Fișa e gata", „Confirmare programare", „Ofertă piesă". Suportă variabile ({client.name}, {wo.number}, {eta}). Se folosesc din Chat client sau notificări automate.', + 'ru' => 'Готовые сообщения: «Заказ готов», «Подтверждение записи», «Оферта на запчасть». Поддерживают переменные ({client.name}, {wo.number}, {eta}).', + 'en' => 'Pre-written messages: "WO ready", "Booking confirmed", "Part quote". Support variables ({client.name}, {wo.number}, {eta}).', + ], + ], + + // Analiză + 'analytics.recommendations.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Recomandări automate', 'ru' => 'Автоматические рекомендации', 'en' => 'Auto recommendations'], + 'body' => [ + 'ro' => 'Sistemul analizează activitatea și îți spune: „Piese cu stoc mort de X luni", „Manopere neprofitabile", „Clienți inactivi > 6 luni". Fiecare are un buton de acțiune (ex: „Trimite ofertă").', + 'ru' => 'Система анализирует и подсказывает: «Мёртвый сток X месяцев», «Убыточные работы», «Клиенты 6+ мес. без визитов». Кнопка действия рядом (напр. «Отправить оффер»).', + 'en' => 'The system analyzes activity and suggests: "Dead stock X months", "Unprofitable labor", "Clients inactive 6+ months". Each has an action button (e.g. "Send offer").', + ], + ], + 'analytics.ai.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Asistent AI', 'ru' => 'AI-ассистент', 'en' => 'AI assistant'], + 'body' => [ + 'ro' => 'Întreabă în limbaj natural: „Cât am câștigat luna trecută?", „Care e piesa cea mai vândută la BMW-uri?", „Ce mecanic e cel mai productiv?". AI-ul răspunde din datele tale, cu link-uri către raportul relevant.', + 'ru' => 'Спрашивай на естественном языке: «Сколько заработал в прошлом месяце?», «Какая запчасть чаще продаётся на BMW?», «Кто самый продуктивный механик?». AI отвечает по твоим данным + ссылки на отчёты.', + 'en' => 'Ask in plain language: "How much did I earn last month?", "Best-selling part for BMWs?", "Most productive mechanic?". AI answers from your data with links to relevant reports.', + ], + ], + 'analytics.workload.overview' => [ + 'area' => 'finante', 'title' => ['ro' => 'Încărcare STO', 'ru' => 'Загрузка СТО', 'en' => 'Workshop load'], + 'body' => [ + 'ro' => 'Grafic pe post × zi cu ore rezervate + capacitate. Roșu = supra-încărcat, verde = liber. Ideal pentru planificarea programărilor și evitarea aglomerării unui maistru.', + 'ru' => 'График по посту × день: занятые часы + мощность. Красный — перегруз, зелёный — свободно. Помогает планировать записи.', + 'en' => 'Chart per post × day: booked hours + capacity. Red = overloaded, green = free. Helps schedule bookings without overloading a master.', + ], + ], + + // Admin + 'admin.users.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Utilizatori (echipa)', 'ru' => 'Пользователи (команда)', 'en' => 'Users (team)'], + 'body' => [ + 'ro' => 'Invită oameni pe email — primesc link de acceptare + parolă temporară. Alocare rol (owner/admin/master/accountant/receptionist) → rolul definește ce văd și ce pot modifica. Dezactivarea unui user îl blochează fără să șterge datele.', + 'ru' => 'Приглашай по email — пользователь получает ссылку + временный пароль. Роль (owner/admin/master/accountant/receptionist) — определяет права. Деактивация блокирует без удаления данных.', + 'en' => 'Invite people via email — they get an accept link + temp password. Role (owner/admin/master/accountant/receptionist) sets what they can see + change. Deactivating blocks access without deleting data.', + ], + ], + 'admin.roles.overview' => [ + 'area' => 'crm', 'title' => ['ro' => 'Roluri & drepturi', 'ru' => 'Роли и права', 'en' => 'Roles & permissions'], + 'body' => [ + 'ro' => 'Fiecare rol are un set de permisiuni granulare (poate vedea marja internă? poate șterge fișe? poate exporta rapoarte?). Owner-ul are toate. Poți crea roluri custom pentru cazuri speciale.', + 'ru' => 'У каждой роли — набор гранулярных прав (видит внутреннюю маржу? может удалять? экспортировать?). У Owner — все. Можно создать кастомные роли.', + 'en' => 'Each role has granular permissions (can see internal margin? can delete WOs? can export reports?). Owner has all. You can create custom roles for edge cases.', + ], + ], + 'admin.posts.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Posturi de lucru', 'ru' => 'Посты (рабочие места)', 'en' => 'Work posts'], + 'body' => [ + 'ro' => 'Locurile fizice unde se face lucrarea: „Elevator 1", „Elevator 2", „Zona geometrie", „Vopsitorie". Fiecare post are un maistru default (opțional). Se folosesc în Calendar pentru gruparea programărilor.', + 'ru' => 'Физические места работ: «Подъёмник 1», «Развал-схождение», «Покраска». У поста может быть мастер по умолчанию. Используются в Календаре для группировки.', + 'en' => 'Physical work bays: "Lift 1", "Alignment", "Paint booth". Each post can have a default master. Used in Calendar to group appointments.', + ], + ], + 'admin.settings.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Setări service', 'ru' => 'Настройки сервиса', 'en' => 'Service settings'], + 'body' => [ + 'ro' => 'Config-ul general al service-ului: nume, logo, culoare temă, program de lucru, adresă, telefon public, moneda, contul bancar pentru facturi. Modificările sunt vizibile imediat pentru toți userii.', + 'ru' => 'Общий конфиг сервиса: имя, лого, цвет темы, график, адрес, телефон, валюта, банк для счетов. Изменения видны всем сразу.', + 'en' => 'Service-wide config: name, logo, theme color, working hours, address, public phone, currency, bank account for invoices. Changes are visible to everyone immediately.', + ], + ], + 'admin.integrations.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Integrări (Telegram/WhatsApp/Google/Facebook)', 'ru' => 'Интеграции', 'en' => 'Integrations'], + 'body' => [ + 'ro' => 'Activează canale: Telegram bot (token de la @BotFather), WhatsApp Business (token Facebook), Google Ads, Facebook Lead Ads. După activare, mesajele/lead-urile intră automat în CRM.', + 'ru' => 'Активируй каналы: Telegram-бот (токен от @BotFather), WhatsApp Business (Facebook), Google Ads, Facebook Lead Ads. После активации всё уходит в CRM автоматом.', + 'en' => 'Enable channels: Telegram bot (token from @BotFather), WhatsApp Business (Facebook), Google Ads, Facebook Lead Ads. Once enabled, messages/leads flow into CRM automatically.', + ], + ], + 'admin.tokens.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'API Tokens', 'ru' => 'API-токены', 'en' => 'API tokens'], + 'body' => [ + 'ro' => 'Token-uri pentru integrări externe (aplicații mobile, script-uri, terți). Fiecare token are un nume + drepturi. Tokenul se afișează O SINGURĂ DATĂ la creare — copiază-l imediat.', + 'ru' => 'Токены для внешних интеграций (мобильные приложения, скрипты). У каждого — имя + права. Токен показывается ОДИН РАЗ при создании — копируй сразу.', + 'en' => 'Tokens for external integrations (mobile apps, scripts, 3rd parties). Each has a name + permissions. The token is shown ONCE at creation — copy it right away.', + ], + ], + 'admin.backup.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Backup date', 'ru' => 'Резервные копии', 'en' => 'Data backup'], + 'body' => [ + 'ro' => 'Descarcă un snapshot complet al datelor tenantului (DB + fișiere media) sub formă de arhivă. Recomandat: o dată pe lună. Se poate importa înapoi manual (contactează suport).', + 'ru' => 'Скачивай снапшот всех данных (БД + медиа) архивом. Рекомендуется раз в месяц. Восстановление — через поддержку.', + 'en' => 'Download a full snapshot of your tenant data (DB + media files) as an archive. Recommended monthly. Restore is manual via support.', + ], + ], + 'admin.activity.overview' => [ + 'area' => 'service', 'title' => ['ro' => 'Jurnal activitate', 'ru' => 'Журнал активности', 'en' => 'Activity log'], + 'body' => [ + 'ro' => 'Audit-log complet: cine, ce, când. Utile pentru investigație (ex: „cine a șters fișa X?"). Filtrează pe user, pe entitate, pe interval. Se păstrează 12 luni.', + 'ru' => 'Полный аудит: кто, что, когда. Полезно для расследований («кто удалил заказ X?»). Фильтры: пользователь, сущность, период. Хранится 12 месяцев.', + 'en' => 'Full audit log: who, what, when. Great for investigating ("who deleted WO X?"). Filter by user, entity, period. Retained 12 months.', + ], + ], + 'finante.expenses.overview' => [ 'area' => 'finante', 'title' => [ @@ -411,6 +714,90 @@ class Hints return static::all()[$key] ?? null; } + /** + * URL path → hint key map. Used by the tenant panel render hook to + * auto-inject a "?" on top of any page that has a matching entry. + * Order matters — first match wins, so put specific paths before + * broader ones. Paths are matched with str_starts_with(). + */ + public static function forPath(string $path): ?string + { + $path = trim($path, '/'); + $map = [ + // Service / Work orders + 'app/work-orders/create' => 'wo.create.overview', + 'app/work-orders' => 'wo.list.overview', + 'app/mechanic-board' => 'service.mechanic_board.overview', + 'app/mechanic-kpi' => 'service.kpi.overview', + 'app/labors' => 'service.labors.overview', + 'app/masters' => 'service.masters.overview', + 'app/service-templates' => 'service.templates.overview', + 'app/injector-protocols' => 'service.injector.overview', + 'app/bodyshop-jobs' => 'service.bodyshop.overview', + 'app/tire-sets' => 'service.tires.overview', + 'app/subcontract-jobs' => 'service.subcontract.overview', + 'app/subcontractors' => 'service.subcontractors.overview', + + // CRM + 'app/clients' => 'crm.clients.overview', + 'app/vehicles' => 'crm.vehicles.overview', + 'app/leads' => 'crm.leads.overview', + 'app/deals' => 'crm.pipeline.overview', + 'app/pipeline' => 'crm.pipeline.overview', + 'app/calendar-board' => 'crm.calendar.overview', + 'app/appointments' => 'crm.calendar.overview', + 'app/calls' => 'crm.calls.overview', + + // Depozit + 'app/parts' => 'depozit.parts.overview', + 'app/purchases' => 'depozit.purchases.overview', + 'app/suppliers' => 'depozit.suppliers.overview', + 'app/warehouses' => 'depozit.warehouses.overview', + 'app/pricing-coefficients' => 'depozit.pricing.overview', + 'app/markup-rules' => 'depozit.markup.overview', + 'app/units' => 'depozit.units.overview', + 'app/scanner' => 'depozit.scanner.usage', + 'app/vin-search' => 'depozit.vin_search.overview', + 'app/excel-import-wizard' => 'depozit.excel.import', + + // Finanțe + 'app/payments' => 'finante.payments.overview', + 'app/expenses' => 'finante.expenses.overview', + 'app/payroll-runs' => 'finante.salaries.overview', + 'app/payroll-adjustments' => 'finante.adjustments.overview', + 'app/employee-profiles' => 'finante.employees.overview', + 'app/reports' => 'finante.reports.overview', + 'app/finance' => 'finante.consolidated.overview', + + // Magazin + 'app/online-orders' => 'shop.orders.overview', + 'app/shop-customers' => 'shop.customers.overview', + + // Marketing + 'app/marketing-channels' => 'marketing.channels.overview', + 'app/message-templates' => 'marketing.templates.overview', + + // Analiză + 'app/recommendations' => 'analytics.recommendations.overview', + 'app/ai-assistant' => 'analytics.ai.overview', + 'app/workload' => 'analytics.workload.overview', + + // Admin + 'app/users' => 'admin.users.overview', + 'app/roles' => 'admin.roles.overview', + 'app/posts' => 'admin.posts.overview', + 'app/settings' => 'admin.settings.overview', + 'app/integrations' => 'admin.integrations.overview', + 'app/api-tokens' => 'admin.tokens.overview', + 'app/backup' => 'admin.backup.overview', + 'app/activities' => 'admin.activity.overview', + ]; + foreach ($map as $prefix => $key) { + if (str_starts_with($path, $prefix)) return $key; + } + return null; + } + /** Group all hints by area, preserving insertion order. */ public static function byArea(): array {