src/ApplicationBundle/Resources/views/framework/_cp_global_rail.html.twig line 1

Open in your IDE?
  1. {#
  2.   framework/_cp_global_rail.html.twig
  3.   ══════════════════════════════════════════════════════════════════════════════
  4.   GLOBAL module rail — the app's top-level areas, one click away from anywhere.
  5.   This is the single source of truth for the rail; add a module = one row here.
  6.   `active_module` (set by each module layout) highlights the current area.
  7.   ACCESS: each module carries an ORDERED list of candidate landing routes. The rail
  8.   lands on the first one the user can actually reach (registered on this box AND not
  9.   in their USER_PROHIBIT_LIST), via cp_first_allowed_route(). So a user who is denied
  10.   the module *dashboard* but can see a sub-page lands on that sub-page instead of the
  11.   permission-denied wall; a user with NO accessible page in a module has that whole
  12.   module hidden from the rail. `active_module` (set by each module layout) highlights
  13.   the current area. Candidates are all param-free routes so path() never needs args.
  14.   ══════════════════════════════════════════════════════════════════════════════
  15.   "My Tasks" (2026-07-30): users reported being unable to find their task list to
  16.   clock in. /tasks/my was linked ONLY from _home_nav, the legacy skeleton menu and
  17.   the manager dashboard, so it was reachable only if you were already on Home. Its
  18.   candidate routes below are ordered my → board → calendar → manager → reviewer →
  19.   kpi, so cp_first_allowed_route() still lands a reviewer-only user somewhere real.
  20.   NOTE this makes the task list FINDABLE; it does not add clock-in. There is no
  21.   internal web clock-in anywhere in the ERP — the only clock routes are
  22.   /external-workforce/clock and the ZKTeco device endpoints, and task_clock_in
  23.   exists solely as an AI/MCP handler. That is a separate build.
  24.   ══════════════════════════════════════════════════════════════════════════════
  25. #}
  26. {% set active_module = active_module|default('') %}
  27. {% set _mods = [
  28.   { key:'home',         label:'Home',         icon:'home',            routes:['dashboard'] },
  29.   { key:'supply_chain', label:'Purchasing',   icon:'shopping_cart',   routes:['purchase_dashboard','purchase_orders','pr_list','purchase_invoices','supplier_list'] },
  30.   { key:'inventory',    label:'Inventory',    icon:'archive',         routes:['inventory_dashboard','product_list','grn_list','stock_consumption_note_list','sr_list'] },
  31.   { key:'sales',        label:'Sales',        icon:'attach_money',    routes:['sales_dashboard','sales_orders','sales_proposal_list','lead_list','client_list'] },
  32.   { key:'bids',         label:'Bids',         icon:'gavel',           routes:['bid_list'] },
  33.   { key:'distribution', label:'Distribution', icon:'local_shipping',  routes:['distribution_dashboard','delivery_pending_orders','sales_vs_delivery_list'] },
  34.   { key:'pos',          label:'POS',          icon:'local_atm',       routes:['pos_dashboard'] },
  35.   { key:'production',   label:'Production',   icon:'build',           routes:['production_dashboard','production_entry_list','production_bom_list','production_schedule_list'] },
  36.   { key:'finance',      label:'Finance',      icon:'account_balance', routes:['finance_dashboard','voucher_list','sales_invoices','expense_invoices','chart_of_accounts','bank_account_list'] },
  37.   { key:'assets',       label:'Assets',       icon:'domain',          routes:['fixed_asset_list','fixed_asset_conversion_note_list'] },
  38.   { key:'projects',     label:'Projects',     icon:'work',            routes:['projects_dashboard','project_list','copilot_dashboard'] },
  39.   { key:'tasks',        label:'My Tasks',     icon:'assignment_turned_in', routes:['tasks_my_dashboard','task_management_board','task_calendar','tasks_manager_dashboard','tasks_review_dashboard','tasks_kpi_dashboard'] },
  40.   { key:'solar_studio', label:'HoneyWatt', icon:'wb_sunny',        routes:['solar_studio'] },
  41.   { key:'people',       label:'People',       icon:'supervisor_account',          routes:['hrm_cockpit','employee_list','current_attendance','payslip_list','leave_application_list'] },
  42.   { key:'agentos',      label:'Agent OS',     icon:'memory',          routes:['agent_os_dashboard','automation_settings','mcp_ai_connection'] },
  43.   { key:'admin',        label:'Admin',        icon:'security',        routes:['access_control_home','access_control_users','company_admin_home','system_admin_user_list','file_management_list'] },
  44.   { key:'directory',    label:'All Pages',    icon:'apps',            routes:['cp_directory'] }
  45. ] %}
  46. {# M2E M1.1 — Map-to-Execution appears ONLY when this tenant has central m2e_enabled ON (m2e_enabled()
  47.    is fail-safe OFF), slotted before "All Pages". cp_first_allowed_route still gates on route access. #}
  48. {% if m2e_enabled() %}
  49.   {% set _mods = _mods|slice(0, _mods|length - 1)
  50.     |merge([{ key:'m2e', label:'Map-to-Execution', icon:'map', routes:['m2e_projects'] }])
  51.     |merge([_mods[_mods|length - 1]]) %}
  52. {% endif %}
  53. <div class="cp-rail">
  54.   {% for m in _mods %}
  55.     {% set _r = cp_first_allowed_route(m.routes) %}
  56.     {% if _r %}
  57.       <a href="{{ path(_r) }}" class="cp-rail-item {{ m.key == active_module ? 'active' : '' }}" title="{{ m.label }}" aria-label="{{ m.label }}">
  58.         <i class="material-icons">{{ m.icon }}</i>
  59.         <span class="cp-rail-lbl">{{ m.label }}</span>
  60.       </a>
  61.     {% endif %}
  62.   {% endfor %}
  63. </div>