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:
@@ -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
@@ -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 месяцев.',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 }};">
|
||||||
|
|||||||
Reference in New Issue
Block a user