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>
This commit is contained in:
2026-09-01 11:09:24 +00:00
parent 346fcf968f
commit 2ca6ab58a1
7 changed files with 110 additions and 27 deletions
+26
View File
@@ -60,6 +60,8 @@ class Settings extends Page
'services' => isset($settings['services']) ? implode(', ', (array) $settings['services']) : '', 'services' => isset($settings['services']) ? implode(', ', (array) $settings['services']) : '',
'cars' => isset($settings['cars']) ? implode(', ', (array) $settings['cars']) : '', 'cars' => isset($settings['cars']) ? implode(', ', (array) $settings['cars']) : '',
'faq_video_url' => $settings['faq_video_url'] ?? null, 'faq_video_url' => $settings['faq_video_url'] ?? null,
'nav_group_order' => collect($settings['nav_group_order'] ?? \App\Http\Middleware\ConfigureNavGroups::DEFAULT_ORDER)
->map(fn ($k) => ['key' => $k])->all(),
'notify_wo_ready' => $notify['wo_ready'] ?? true, 'notify_wo_ready' => $notify['wo_ready'] ?? true,
'notify_payment' => $notify['payment'] ?? true, 'notify_payment' => $notify['payment'] ?? true,
'notify_appointment' => $notify['appointment'] ?? true, 'notify_appointment' => $notify['appointment'] ?? true,
@@ -155,6 +157,28 @@ class Settings extends Page
->placeholder('https://www.youtube.com/watch?v=... sau https://vimeo.com/...') ->placeholder('https://www.youtube.com/watch?v=... sau https://vimeo.com/...')
->helperText(__('Afișat în pagina FAQ ca player embedded. Lasă gol pentru a-l ascunde.')), ->helperText(__('Afișat în pagina FAQ ca player embedded. Lasă gol pentru a-l ascunde.')),
]), ]),
Schemas\Components\Section::make(__('Ordine meniu lateral'))
->description(__('Trage grupurile pentru a schimba ordinea lor în sidebar-ul stâng.'))
->columns(1)
->schema([
Forms\Components\Repeater::make('nav_group_order')
->label('')
->schema([
Forms\Components\Select::make('key')
->label(__('Grup'))
->options(array_combine(
\App\Http\Middleware\ConfigureNavGroups::DEFAULT_ORDER,
array_map(fn ($k) => __('nav.group.' . $k), \App\Http\Middleware\ConfigureNavGroups::DEFAULT_ORDER),
))
->required()
->distinct(),
])
->reorderable()
->reorderableWithButtons()
->addable(false)
->deletable(false)
->columnSpanFull(),
]),
Schemas\Components\Section::make(__('Logo & favicon')) Schemas\Components\Section::make(__('Logo & favicon'))
->columns(2) ->columns(2)
->schema([ ->schema([
@@ -275,6 +299,8 @@ class Settings extends Page
'services' => array_values(array_filter(array_map('trim', explode(',', (string) ($data['services'] ?? ''))))), 'services' => array_values(array_filter(array_map('trim', explode(',', (string) ($data['services'] ?? ''))))),
'cars' => array_values(array_filter(array_map('trim', explode(',', (string) ($data['cars'] ?? ''))))), 'cars' => array_values(array_filter(array_map('trim', explode(',', (string) ($data['cars'] ?? ''))))),
'faq_video_url' => trim((string) ($data['faq_video_url'] ?? '')) ?: null, 'faq_video_url' => trim((string) ($data['faq_video_url'] ?? '')) ?: null,
'nav_group_order' => collect($data['nav_group_order'] ?? [])
->pluck('key')->filter()->values()->all() ?: null,
'notify' => [ 'notify' => [
'wo_ready' => (bool) ($data['notify_wo_ready'] ?? true), 'wo_ready' => (bool) ($data['notify_wo_ready'] ?? true),
'payment' => (bool) ($data['notify_payment'] ?? true), 'payment' => (bool) ($data['notify_payment'] ?? true),
@@ -0,0 +1,52 @@
<?php
namespace App\Http\Middleware;
use App\Tenancy\TenantManager;
use Closure;
use Filament\Facades\Filament;
use Filament\Navigation\NavigationGroup;
use Illuminate\Http\Request;
/**
* Read the current tenant's preferred navigation group order from
* settings.nav_group_order and apply it to the tenant panel so the
* sidebar groups render in that order.
*
* Falls back to the built-in default order when the setting is empty.
*/
class ConfigureNavGroups
{
/** Default order (matches the labels defined in lang/{ro,ru,en}/nav.php). */
public const DEFAULT_ORDER = [
'CRM',
'Service',
'Depozit',
'Finanțe',
'Analiză',
'Marketing',
'Magazin',
'Admin',
];
public function handle(Request $request, Closure $next)
{
$tenant = app(TenantManager::class)->current();
if (! $tenant) return $next($request);
$order = (array) ($tenant->settings['nav_group_order'] ?? []);
if (empty($order)) $order = self::DEFAULT_ORDER;
// Only touch the tenant panel — bail if the current request isn't in it.
$panel = Filament::getCurrentPanel();
if (! $panel || $panel->getId() !== 'tenant') return $next($request);
$groups = array_map(
fn (string $key) => NavigationGroup::make(__('nav.group.' . $key)),
$order,
);
$panel->navigationGroups($groups);
return $next($request);
}
}
+23 -19
View File
@@ -21,10 +21,14 @@ namespace App\Support;
class Hints class Hints
{ {
public const AREAS = [ public const AREAS = [
'service' => ['label' => ['ro' => 'Fișe lucru', 'ru' => 'Заказ-наряды', 'en' => 'Work orders'], 'icon' => '🔧'], 'service' => ['label' => ['ro' => 'Service (fișe lucru)', 'ru' => 'Сервис (заказы)', 'en' => 'Service (work orders)'], 'icon' => '🔧', 'color' => '#0ea5e9'],
'crm' => ['label' => ['ro' => 'CRM & Clienți', 'ru' => 'CRM и клиенты', 'en' => 'CRM & Customers'], 'icon' => '👥'], 'crm' => ['label' => ['ro' => 'CRM & Clienți', 'ru' => 'CRM и клиенты', 'en' => 'CRM & Customers'], 'icon' => '👥', 'color' => '#8b5cf6'],
'depozit' => ['label' => ['ro' => 'Depozit & Piese', 'ru' => 'Склад и запчасти', 'en' => 'Warehouse & Parts'], 'icon' => '📦'], 'depozit' => ['label' => ['ro' => 'Depozit & Piese', 'ru' => 'Склад и запчасти', 'en' => 'Warehouse & Parts'], 'icon' => '📦', 'color' => '#f59e0b'],
'finante' => ['label' => ['ro' => 'Finanțe', 'ru' => 'Финансы', 'en' => 'Finance'], 'icon' => '💰'], 'finante' => ['label' => ['ro' => 'Finanțe', 'ru' => 'Финансы', 'en' => 'Finance'], 'icon' => '💰', 'color' => '#10b981'],
'analiza' => ['label' => ['ro' => 'Analiză & Rapoarte', 'ru' => 'Аналитика и отчёты', 'en' => 'Analytics & Reports'], 'icon' => '📊', 'color' => '#ec4899'],
'marketing' => ['label' => ['ro' => 'Marketing', 'ru' => 'Маркетинг', 'en' => 'Marketing'], 'icon' => '📣', 'color' => '#f97316'],
'magazin' => ['label' => ['ro' => 'Magazin online', 'ru' => 'Онлайн-магазин', 'en' => 'Online shop'], 'icon' => '🛒', 'color' => '#06b6d4'],
'admin' => ['label' => ['ro' => 'Administrare', 'ru' => 'Администрирование', 'en' => 'Administration'], 'icon' => '⚙️', 'color' => '#64748b'],
]; ];
/** @return array<string, array{area:string,title:array,body:array,links?:array,next?:array}> */ /** @return array<string, array{area:string,title:array,body:array,links?:array,next?:array}> */
@@ -419,7 +423,7 @@ class Hints
// Magazin // Magazin
'shop.orders.overview' => [ 'shop.orders.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Comenzi online (magazin)', 'ru' => 'Онлайн-заказы (магазин)', 'en' => 'Online orders (shop)'], 'area' => 'magazin', 'title' => ['ro' => 'Comenzi online (magazin)', 'ru' => 'Онлайн-заказы (магазин)', 'en' => 'Online orders (shop)'],
'body' => [ 'body' => [
'ro' => 'Comenzi primite din magazin-ul public (subdomeniu tenant). Poți accepta/refuza, marca livrat, genera factură. Stocul scade automat la livrare.', 'ro' => 'Comenzi primite din magazin-ul public (subdomeniu tenant). Poți accepta/refuza, marca livrat, genera factură. Stocul scade automat la livrare.',
'ru' => 'Заказы из публичного магазина (тенант-поддомен). Принять/отклонить, отметить доставку, счёт. Остаток списывается при доставке.', 'ru' => 'Заказы из публичного магазина (тенант-поддомен). Принять/отклонить, отметить доставку, счёт. Остаток списывается при доставке.',
@@ -427,7 +431,7 @@ class Hints
], ],
], ],
'shop.customers.overview' => [ 'shop.customers.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Clienți magazin', 'ru' => 'Клиенты магазина', 'en' => 'Shop customers'], 'area' => 'magazin', 'title' => ['ro' => 'Clienți magazin', 'ru' => 'Клиенты магазина', 'en' => 'Shop customers'],
'body' => [ '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.', '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-клиент.', 'ru' => 'Клиенты онлайн-магазина (отдельно от CRM-базы). При приезде в сервис — 1 клик → CRM-клиент.',
@@ -437,7 +441,7 @@ class Hints
// Marketing // Marketing
'marketing.channels.overview' => [ 'marketing.channels.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Canale marketing', 'ru' => 'Маркетинговые каналы', 'en' => 'Marketing channels'], 'area' => 'marketing', 'title' => ['ro' => 'Canale marketing', 'ru' => 'Маркетинговые каналы', 'en' => 'Marketing channels'],
'body' => [ '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.', '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 по каналу.', 'ru' => 'Источники лидов: Google Ads, Facebook, рекомендация, walk-in, звонок. Каждая заявка привязывается к каналу → в Отчётах видишь ROI по каналу.',
@@ -445,7 +449,7 @@ class Hints
], ],
], ],
'marketing.templates.overview' => [ 'marketing.templates.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Template-uri mesaje', 'ru' => 'Шаблоны сообщений', 'en' => 'Message templates'], 'area' => 'marketing', 'title' => ['ro' => 'Template-uri mesaje', 'ru' => 'Шаблоны сообщений', 'en' => 'Message templates'],
'body' => [ '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.', '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}).', 'ru' => 'Готовые сообщения: «Заказ готов», «Подтверждение записи», «Оферта на запчасть». Поддерживают переменные ({client.name}, {wo.number}, {eta}).',
@@ -455,7 +459,7 @@ class Hints
// Analiză // Analiză
'analytics.recommendations.overview' => [ 'analytics.recommendations.overview' => [
'area' => 'finante', 'title' => ['ro' => 'Recomandări automate', 'ru' => 'Автоматические рекомендации', 'en' => 'Auto recommendations'], 'area' => 'analiza', 'title' => ['ro' => 'Recomandări automate', 'ru' => 'Автоматические рекомендации', 'en' => 'Auto recommendations'],
'body' => [ '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ă").', '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+ мес. без визитов». Кнопка действия рядом (напр. «Отправить оффер»).', 'ru' => 'Система анализирует и подсказывает: «Мёртвый сток X месяцев», «Убыточные работы», «Клиенты 6+ мес. без визитов». Кнопка действия рядом (напр. «Отправить оффер»).',
@@ -463,7 +467,7 @@ class Hints
], ],
], ],
'analytics.ai.overview' => [ 'analytics.ai.overview' => [
'area' => 'finante', 'title' => ['ro' => 'Asistent AI', 'ru' => 'AI-ассистент', 'en' => 'AI assistant'], 'area' => 'analiza', 'title' => ['ro' => 'Asistent AI', 'ru' => 'AI-ассистент', 'en' => 'AI assistant'],
'body' => [ '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.', '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 отвечает по твоим данным + ссылки на отчёты.', 'ru' => 'Спрашивай на естественном языке: «Сколько заработал в прошлом месяце?», «Какая запчасть чаще продаётся на BMW?», «Кто самый продуктивный механик?». AI отвечает по твоим данным + ссылки на отчёты.',
@@ -471,7 +475,7 @@ class Hints
], ],
], ],
'analytics.workload.overview' => [ 'analytics.workload.overview' => [
'area' => 'finante', 'title' => ['ro' => 'Încărcare STO', 'ru' => 'Загрузка СТО', 'en' => 'Workshop load'], 'area' => 'analiza', 'title' => ['ro' => 'Încărcare STO', 'ru' => 'Загрузка СТО', 'en' => 'Workshop load'],
'body' => [ '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.', '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' => 'График по посту × день: занятые часы + мощность. Красный — перегруз, зелёный — свободно. Помогает планировать записи.', 'ru' => 'График по посту × день: занятые часы + мощность. Красный — перегруз, зелёный — свободно. Помогает планировать записи.',
@@ -481,7 +485,7 @@ class Hints
// Admin // Admin
'admin.users.overview' => [ 'admin.users.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Utilizatori (echipa)', 'ru' => 'Пользователи (команда)', 'en' => 'Users (team)'], 'area' => 'admin', 'title' => ['ro' => 'Utilizatori (echipa)', 'ru' => 'Пользователи (команда)', 'en' => 'Users (team)'],
'body' => [ '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.', '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) — определяет права. Деактивация блокирует без удаления данных.', 'ru' => 'Приглашай по email — пользователь получает ссылку + временный пароль. Роль (owner/admin/master/accountant/receptionist) — определяет права. Деактивация блокирует без удаления данных.',
@@ -489,7 +493,7 @@ class Hints
], ],
], ],
'admin.roles.overview' => [ 'admin.roles.overview' => [
'area' => 'crm', 'title' => ['ro' => 'Roluri & drepturi', 'ru' => 'Роли и права', 'en' => 'Roles & permissions'], 'area' => 'admin', 'title' => ['ro' => 'Roluri & drepturi', 'ru' => 'Роли и права', 'en' => 'Roles & permissions'],
'body' => [ '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.', '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 — все. Можно создать кастомные роли.', 'ru' => 'У каждой роли — набор гранулярных прав (видит внутреннюю маржу? может удалять? экспортировать?). У Owner — все. Можно создать кастомные роли.',
@@ -497,7 +501,7 @@ class Hints
], ],
], ],
'admin.posts.overview' => [ 'admin.posts.overview' => [
'area' => 'service', 'title' => ['ro' => 'Posturi de lucru', 'ru' => 'Посты (рабочие места)', 'en' => 'Work posts'], 'area' => 'admin', 'title' => ['ro' => 'Posturi de lucru', 'ru' => 'Посты (рабочие места)', 'en' => 'Work posts'],
'body' => [ '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.', '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», «Развал-схождение», «Покраска». У поста может быть мастер по умолчанию. Используются в Календаре для группировки.', 'ru' => 'Физические места работ: «Подъёмник 1», «Развал-схождение», «Покраска». У поста может быть мастер по умолчанию. Используются в Календаре для группировки.',
@@ -505,7 +509,7 @@ class Hints
], ],
], ],
'admin.settings.overview' => [ 'admin.settings.overview' => [
'area' => 'service', 'title' => ['ro' => 'Setări service', 'ru' => 'Настройки сервиса', 'en' => 'Service settings'], 'area' => 'admin', 'title' => ['ro' => 'Setări service', 'ru' => 'Настройки сервиса', 'en' => 'Service settings'],
'body' => [ '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.', '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' => 'Общий конфиг сервиса: имя, лого, цвет темы, график, адрес, телефон, валюта, банк для счетов. Изменения видны всем сразу.', 'ru' => 'Общий конфиг сервиса: имя, лого, цвет темы, график, адрес, телефон, валюта, банк для счетов. Изменения видны всем сразу.',
@@ -513,7 +517,7 @@ class Hints
], ],
], ],
'admin.integrations.overview' => [ 'admin.integrations.overview' => [
'area' => 'service', 'title' => ['ro' => 'Integrări (Telegram/WhatsApp/Google/Facebook)', 'ru' => 'Интеграции', 'en' => 'Integrations'], 'area' => 'admin', 'title' => ['ro' => 'Integrări (Telegram/WhatsApp/Google/Facebook)', 'ru' => 'Интеграции', 'en' => 'Integrations'],
'body' => [ '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.', '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 автоматом.', 'ru' => 'Активируй каналы: Telegram-бот (токен от @BotFather), WhatsApp Business (Facebook), Google Ads, Facebook Lead Ads. После активации всё уходит в CRM автоматом.',
@@ -521,7 +525,7 @@ class Hints
], ],
], ],
'admin.tokens.overview' => [ 'admin.tokens.overview' => [
'area' => 'service', 'title' => ['ro' => 'API Tokens', 'ru' => 'API-токены', 'en' => 'API tokens'], 'area' => 'admin', 'title' => ['ro' => 'API Tokens', 'ru' => 'API-токены', 'en' => 'API tokens'],
'body' => [ '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.', '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' => 'Токены для внешних интеграций (мобильные приложения, скрипты). У каждого — имя + права. Токен показывается ОДИН РАЗ при создании — копируй сразу.', 'ru' => 'Токены для внешних интеграций (мобильные приложения, скрипты). У каждого — имя + права. Токен показывается ОДИН РАЗ при создании — копируй сразу.',
@@ -529,7 +533,7 @@ class Hints
], ],
], ],
'admin.backup.overview' => [ 'admin.backup.overview' => [
'area' => 'service', 'title' => ['ro' => 'Backup date', 'ru' => 'Резервные копии', 'en' => 'Data backup'], 'area' => 'admin', 'title' => ['ro' => 'Backup date', 'ru' => 'Резервные копии', 'en' => 'Data backup'],
'body' => [ '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).', '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' => 'Скачивай снапшот всех данных (БД + медиа) архивом. Рекомендуется раз в месяц. Восстановление — через поддержку.', 'ru' => 'Скачивай снапшот всех данных (БД + медиа) архивом. Рекомендуется раз в месяц. Восстановление — через поддержку.',
@@ -537,7 +541,7 @@ class Hints
], ],
], ],
'admin.activity.overview' => [ 'admin.activity.overview' => [
'area' => 'service', 'title' => ['ro' => 'Jurnal activitate', 'ru' => 'Журнал активности', 'en' => 'Activity log'], 'area' => 'admin', 'title' => ['ro' => 'Jurnal activitate', 'ru' => 'Журнал активности', 'en' => 'Activity log'],
'body' => [ '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.', '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 месяцев.', 'ru' => 'Полный аудит: кто, что, когда. Полезно для расследований («кто удалил заказ X?»). Фильтры: пользователь, сущность, период. Хранится 12 месяцев.',
+1
View File
@@ -31,6 +31,7 @@ return Application::configure(basePath: dirname(__DIR__))
\App\Http\Middleware\ResolveTenant::class, \App\Http\Middleware\ResolveTenant::class,
\App\Http\Middleware\CheckTenantStatus::class, \App\Http\Middleware\CheckTenantStatus::class,
\App\Http\Middleware\SetLocale::class, \App\Http\Middleware\SetLocale::class,
\App\Http\Middleware\ConfigureNavGroups::class,
]); ]);
// API routes also need tenant resolution by host so the same // API routes also need tenant resolution by host so the same
+3
View File
@@ -949,6 +949,7 @@
"Grafic": "Chart", "Grafic": "Chart",
"Gravitate": "Severity", "Gravitate": "Severity",
"Gravă": "Severe", "Gravă": "Severe",
"Grup": "Group",
"Grupare:": "Group:", "Grupare:": "Group:",
"Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet", "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet",
"Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.", "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.",
@@ -1428,6 +1429,7 @@
"Order number": "Order number", "Order number": "Order number",
"Orders": "Orders", "Orders": "Orders",
"Ordine": "Order", "Ordine": "Order",
"Ordine meniu lateral": "Sidebar menu order",
"Ordonare": "Sort", "Ordonare": "Sort",
"Ordonat": "Sorted", "Ordonat": "Sorted",
"Ordonează după": "Sort by", "Ordonează după": "Sort by",
@@ -2105,6 +2107,7 @@
"Tracking client": "Client tracking", "Tracking client": "Client tracking",
"Tracțiune": "Drive", "Tracțiune": "Drive",
"Trage fișierul aici": "Drop file here", "Trage fișierul aici": "Drop file here",
"Trage grupurile pentru a schimba ordinea lor în sidebar-ul stâng.": "Drag groups to change their order in the left sidebar.",
"Transfer": "Transfer", "Transfer": "Transfer",
"Transfer bancar": "Bank transfer", "Transfer bancar": "Bank transfer",
"Transfer bancar: pune doar IBAN-ul tău, fără cont gateway": "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway", "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway": "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway",
+3
View File
@@ -949,6 +949,7 @@
"Grafic": "График", "Grafic": "График",
"Gravitate": "Тяжесть", "Gravitate": "Тяжесть",
"Gravă": "Тяжёлое", "Gravă": "Тяжёлое",
"Grup": "Группа",
"Grupare:": "Группировка:", "Grupare:": "Группировка:",
"Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet", "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet",
"Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.", "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.": "Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet\") și aplică-l pe o fișă cu un click.",
@@ -1428,6 +1429,7 @@
"Order number": "№ заказа", "Order number": "№ заказа",
"Orders": "Заказы", "Orders": "Заказы",
"Ordine": "Порядок", "Ordine": "Порядок",
"Ordine meniu lateral": "Порядок бокового меню",
"Ordonare": "Сортировка", "Ordonare": "Сортировка",
"Ordonat": "Отсортировано", "Ordonat": "Отсортировано",
"Ordonează după": "Сортировать по", "Ordonează după": "Сортировать по",
@@ -2105,6 +2107,7 @@
"Tracking client": "Отслеживание клиента", "Tracking client": "Отслеживание клиента",
"Tracțiune": "Привод", "Tracțiune": "Привод",
"Trage fișierul aici": "Перетащите файл сюда", "Trage fișierul aici": "Перетащите файл сюда",
"Trage grupurile pentru a schimba ordinea lor în sidebar-ul stâng.": "Перетащи группы, чтобы изменить порядок в боковом меню слева.",
"Transfer": "Перевод", "Transfer": "Перевод",
"Transfer bancar": "Банковский перевод", "Transfer bancar": "Банковский перевод",
"Transfer bancar: pune doar IBAN-ul tău, fără cont gateway": "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway", "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway": "Transfer bancar: pune doar IBAN-ul tău, fără cont gateway",
@@ -7,13 +7,7 @@
$totalHints = collect($grouped)->sum(fn ($g) => count($g)); $totalHints = collect($grouped)->sum(fn ($g) => count($g));
$videoEmbed = $this->getVideoEmbedUrl(); $videoEmbed = $this->getVideoEmbedUrl();
$videoRaw = $this->getVideoRawUrl(); $videoRaw = $this->getVideoRawUrl();
// Distinct accent color per area for card headers // Read accent color from AREAS registry (single source of truth)
$areaColors = [
'service' => '#0ea5e9',
'crm' => '#8b5cf6',
'depozit' => '#f59e0b',
'finante' => '#10b981',
];
@endphp @endphp
<style> <style>
@@ -348,7 +342,7 @@
@foreach ($grouped as $areaKey => $hints) @foreach ($grouped as $areaKey => $hints)
@php @php
$area = $areas[$areaKey] ?? null; $area = $areas[$areaKey] ?? null;
$accent = $areaColors[$areaKey] ?? '#6b7280'; $accent = $area['color'] ?? '#6b7280';
@endphp @endphp
<div class="faq-card"> <div class="faq-card">
<div class="faq-card-hd" style="background:{{ $accent }};"> <div class="faq-card-hd" style="background:{{ $accent }};">