{#
framework/_cp_app_launcher.html.twig
"All Apps" launcher — a grouped grid of every top feature, reachable from any cp-shell page
(trigger button lives in _cp_topstrip). The safety net so nothing is lost while modules are
still being folded into their sidebars. Every route here is verified to exist (a bad path()
would 500 every page). Add a feature = one row in _groups below.
#}
{% set _groups = [
{ title: 'Areas', items: [
{ l:'Home', r:'dashboard', i:'home' },
{ l:'Purchasing', r:'purchase_dashboard', i:'shopping_cart' },
{ l:'Sales', r:'sales_dashboard', i:'attach_money' },
{ l:'Distribution', r:'distribution_dashboard', i:'local_shipping' },
{ l:'Inventory', r:'inventory_dashboard', i:'archive' },
{ l:'POS', r:'pos_dashboard', i:'local_atm' },
{ l:'Production', r:'production_dashboard', i:'build' },
{ l:'Finance', r:'finance_dashboard', i:'account_balance' },
{ l:'Projects', r:'copilot_dashboard', i:'work' },
{ l:'People', r:'hrm_cockpit', i:'supervisor_account' },
{ l:'All Pages (Directory)', r:'cp_directory', i:'apps' }
] },
{ title: 'Purchasing & Trade', items: [
{ l:'Express Purchase', r:'express_purchase_new', i:'flash_on' },
{ l:'Import & LC', r:'import_lc_dashboard', i:'local_shipping' },
{ l:'Suppliers', r:'supplier_list', i:'store' }
] },
{ title: 'Sales & CRM', items: [
{ l:'Leads', r:'lead_list', i:'people' },
{ l:'Clients', r:'client_list', i:'contacts' },
{ l:'Opportunities', r:'opportunity_list', i:'trending_up' }
] },
{ title: 'Finance & Compliance', items: [
{ l:'Chart of Accounts', r:'chart_of_accounts', i:'account_balance_wallet' },
{ l:'Bank Connections', r:'bank_connect_index', i:'account_balance' },
{ l:'Aspire Connect', r:'aspire_connection_list', i:'sync' },
{ l:'Tax & Compliance', r:'tax_home', i:'gavel' },
{ l:'Reconciliation', r:'reconciliation_home', i:'verified_user' },
{ l:'Payment Explainer', r:'unexplained_payments', i:'help_outline' },
{ l:'Recurring Billing', r:'recurring_billing', i:'event_note' },
{ l:'Cash-Flow', r:'cashflow_forecast', i:'show_chart' },
{ l:'Config Readiness', r:'config_readiness', i:'settings' },
{ l:'Company Secretarial', r:'company_admin_statutory', i:'gavel' },
{ l:'Compliance Autopilot (Annual Return)', r:'company_admin_compliance', i:'playlist_add_check' },
{ l:'Ledger Maintenance', r:'ledger_maintenance', i:'build' }
] },
{ title: 'People & Field', items: [
{ l:'Field Force', r:'hrm_field_force_map', i:'map' },
{ l:'Tasks', r:'task_management_board', i:'assignment' },
{ l:'My Approvals', r:'pending_approval_list', i:'assignment_turned_in' }
] },
{ title: 'Admin & Data', items: [
{ l:'Access Control', r:'access_control_home', i:'lock' },
{ l:'Company Administration', r:'company_admin_home', i:'business' },
{ l:'Data Protection', r:'data_protection_home', i:'security' }
] },
{ title: 'Tools', items: [
{ l:'Instant Solar Estimate', r:'honeybee_tool', p:{slug:'rooftop-estimate'}, i:'wb_sunny' }
] }
] %}
<div class="cp-launcher" id="cpAppLauncher" onclick="if(event.target===this)cpToggleAppLauncher(false)">
<div class="cp-launcher-panel">
<div class="cp-launcher-head">
<div><i class="material-icons" style="vertical-align:-5px;color:var(--cp-accent);">apps</i> <strong>All Apps</strong>
<span style="color:var(--cp-text-faint);font-size:12px;">— everything in HoneyBee, one click away</span></div>
<a href="#" onclick="cpToggleAppLauncher(false);return false;" class="cp-launcher-x"><i class="material-icons">close</i></a>
</div>
<div class="cp-launcher-body">
{% for g in _groups %}
{% set _shown = g.items|filter(it => route_exists(it.r)) %}
{% if _shown is not empty %}
<div class="cp-launcher-sec">{{ g.title }}</div>
<div class="cp-launcher-grid">
{% for it in _shown %}
<a class="cp-launcher-tile" href="{{ path(it.r, it.p|default({})) }}">
<span class="cp-launcher-ico"><i class="material-icons">{{ it.i }}</i></span>
<span class="cp-launcher-lbl">{{ it.l }}</span>
</a>
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<style>
.cp-launcher{ display:none; position:fixed; inset:0; z-index:1400; background:rgba(16,20,26,.45); backdrop-filter:blur(2px); }
.cp-launcher.open{ display:block; }
.cp-launcher-panel{ position:absolute; top:54px; left:50%; transform:translateX(-50%); width:min(920px,94vw); max-height:82vh; overflow:auto;
background:var(--cp-surface-white); border:1px solid var(--cp-outline); border-radius:14px; box-shadow:0 24px 60px rgba(16,24,40,.28); }
.cp-launcher-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 18px; border-bottom:1px solid var(--cp-outline); position:sticky; top:0; background:var(--cp-surface-white); }
.cp-launcher-x{ color:var(--cp-text-faint); display:flex; }
.cp-launcher-x:hover{ color:var(--cp-text); }
.cp-launcher-body{ padding:12px 18px 20px; }
.cp-launcher-sec{ font-family:var(--cp-font-mono); font-size:var(--cp-fs-xs, 11.5px); font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--cp-text-faint); margin:16px 0 8px; }
.cp-launcher-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:8px; }
.cp-launcher-tile{ display:flex; flex-direction:column; align-items:center; gap:7px; padding:14px 8px; border-radius:10px; text-decoration:none;
color:var(--cp-text-muted); border:1px solid transparent; transition:background .12s,border-color .12s; text-align:center; }
.cp-launcher-tile:hover{ background:var(--cp-accent-dim); border-color:var(--cp-accent); color:var(--cp-text); text-decoration:none; }
.cp-launcher-ico .material-icons{ font-size:26px; color:var(--cp-accent); }
.cp-launcher-lbl{ font-size:12px; font-weight:600; line-height:1.2; }
</style>
<script>
function cpToggleAppLauncher(open){
var el=document.getElementById('cpAppLauncher'); if(!el) return;
var show = (typeof open==='boolean') ? open : !el.classList.contains('open');
el.classList.toggle('open', show);
}
document.addEventListener('keydown', function(e){ if(e.key==='Escape') cpToggleAppLauncher(false); });
</script>