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