src/ApplicationBundle/Modules/System/Resources/views/layouts/main_layout.html.twig line 1

Open in your IDE?
  1. {#
  2.   @System/layouts/main_layout.html.twig
  3.   Base layout for CoPilot module and AI cockpit views.
  4.   Usage: {% extends '@System/layouts/main_layout.html.twig' %}
  5.   Blocks: title, breadcrumb, body, page_actions
  6. #}
  7. {% set page_title = block('title') %}
  8. {% include '@Application/codecovers_header.html.twig' %}
  9. {# ── CoPilot design tokens (scoped, separate from legacy ERP tokens) ─────── #}
  10. <style>
  11.   :root {
  12.     --cp-primary:          #854f00;
  13.     --cp-primary-hover:    #6d4000;
  14.     --cp-on-primary:       #ffffff;
  15.     --cp-amber:            #ffb86a;
  16.     --cp-amber-dim:        rgba(133,79,0,.10);
  17.     --cp-surface:          #f9f9f8;
  18.     --cp-surface-low:      #f3f4f3;
  19.     --cp-surface-container:#eeeeed;
  20.     --cp-surface-high:     #e8e8e7;
  21.     --cp-surface-white:    #ffffff;
  22.     --cp-text:             #1a1c1c;
  23.     --cp-text-muted:       #524437;
  24.     --cp-text-faint:       #847465;
  25.     --cp-outline:          #847465;
  26.     --cp-outline-variant:  #E5E5E5;
  27.     --cp-error:            #ba1a1a;
  28.     --cp-success:          #2e7d32;
  29.     --cp-warn:             #f57c00;
  30.     --cp-sidebar-w:        200px;
  31.     --cp-topbar-h:         52px;
  32.     --cp-radius:           8px;
  33.     --cp-radius-lg:        12px;
  34.     --cp-shadow-card:      0 1px 6px rgba(26,28,28,.07), 0 0 0 1px rgba(133,79,0,.05);
  35.     --cp-font-head:        'Montserrat', 'DM Sans', system-ui, sans-serif;
  36.     --cp-font-body:        'DM Sans', system-ui, sans-serif;
  37.     --cp-font-mono:        'JetBrains Mono', 'Courier New', monospace;
  38.     /* Typography SIZE scale (--cp-fs-*) is defined globally in
  39.        @Application/codecovers_header.html.twig (included above, and by ~395
  40.        other templates) so every page shares one scale — not just cp-shell
  41.        pages. Use those tokens; never hard-code font-size or bare rem. */
  42.   }
  43.   /* ── Layout shell ─────────────────────────────────────────────────────── */
  44.   .cp-shell {
  45.     display: flex;
  46.     height: 100vh;
  47.     overflow: hidden;
  48.     background: var(--cp-surface);
  49.     font-family: var(--cp-font-body);
  50.     color: var(--cp-text);
  51.   }
  52.   /* ── Sidebar ───────────────────────────────────────────────────────────── */
  53.   .cp-sidebar {
  54.     width: var(--cp-sidebar-w);
  55.     min-width: var(--cp-sidebar-w);
  56.     background: var(--cp-surface-white);
  57.     border-right: 1px solid var(--cp-outline-variant);
  58.     display: flex;
  59.     flex-direction: column;
  60.     overflow-y: auto;
  61.     z-index: 100;
  62.   }
  63.   .cp-sidebar-brand {
  64.     padding: 16px 14px 10px;
  65.     border-bottom: 1px solid var(--cp-outline-variant);
  66.   }
  67.   .cp-sidebar-brand-name {
  68.     font-family: var(--cp-font-head);
  69.     font-size: var(--cp-fs-base, 13px);
  70.     font-weight: 700;
  71.     color: var(--cp-primary);
  72.     letter-spacing: -0.01em;
  73.   }
  74.   .cp-sidebar-context {
  75.     font-size: var(--cp-fs-xs, 11.5px);
  76.     font-weight: 500;
  77.     color: var(--cp-text-muted);
  78.     text-transform: uppercase;
  79.     letter-spacing: 0.06em;
  80.     margin-top: 2px;
  81.   }
  82.   .cp-sidebar-section {
  83.     padding: 14px 14px 6px;
  84.   }
  85.   .cp-sidebar-section-label {
  86.     font-family: var(--cp-font-mono);
  87.     font-size: 11px;
  88.     font-weight: 600;
  89.     color: var(--cp-text-faint);
  90.     text-transform: uppercase;
  91.     letter-spacing: 0.08em;
  92.     margin-bottom: 6px;
  93.   }
  94.   .cp-badge-active {
  95.     display: inline-flex;
  96.     align-items: center;
  97.     gap: 4px;
  98.     background: var(--cp-amber-dim);
  99.     color: var(--cp-primary);
  100.     font-family: var(--cp-font-mono);
  101.     font-size: 11px;
  102.     font-weight: 600;
  103.     letter-spacing: 0.05em;
  104.     text-transform: uppercase;
  105.     padding: 2px 7px;
  106.     border-radius: 99px;
  107.     border: 1px solid rgba(133,79,0,.18);
  108.     margin-top: 4px;
  109.   }
  110.   .cp-badge-active::before {
  111.     content: '';
  112.     width: 5px;
  113.     height: 5px;
  114.     border-radius: 50%;
  115.     background: #2e7d32;
  116.     display: inline-block;
  117.   }
  118.   .cp-nav {
  119.     list-style: none;
  120.     margin: 0;
  121.     padding: 0 8px;
  122.   }
  123.   .cp-nav li { margin: 1px 0; }
  124.   .cp-nav-link {
  125.     display: flex;
  126.     align-items: center;
  127.     gap: 9px;
  128.     padding: 8px 10px;
  129.     border-radius: var(--cp-radius);
  130.     font-size: var(--cp-fs-sm, 12.5px);
  131.     font-weight: 500;
  132.     color: var(--cp-text-muted);
  133.     text-decoration: none;
  134.     transition: background 0.12s, color 0.12s;
  135.     white-space: nowrap;
  136.     overflow: hidden;
  137.   }
  138.   .cp-nav-link:hover {
  139.     background: var(--cp-surface-low);
  140.     color: var(--cp-text);
  141.     text-decoration: none;
  142.   }
  143.   .cp-nav-link.active {
  144.     background: var(--cp-amber-dim);
  145.     color: var(--cp-primary);
  146.     font-weight: 600;
  147.   }
  148.   .cp-nav-link .cp-nav-icon {
  149.     font-size: 14px;
  150.     width: 16px;
  151.     text-align: center;
  152.     flex-shrink: 0;
  153.   }
  154.   .cp-nav-divider {
  155.     height: 1px;
  156.     background: var(--cp-outline-variant);
  157.     margin: 8px 10px;
  158.   }
  159.   .cp-nav-step-badge {
  160.     margin-left: auto;
  161.     font-family: var(--cp-font-mono);
  162.     font-size: 11px;
  163.     font-weight: 600;
  164.     color: var(--cp-text-faint);
  165.   }
  166.   .cp-sidebar-footer {
  167.     margin-top: auto;
  168.     padding: 10px 8px 12px;
  169.     border-top: 1px solid var(--cp-outline-variant);
  170.   }
  171.   /* ── Main area ─────────────────────────────────────────────────────────── */
  172.   .cp-main {
  173.     flex: 1 1 0;
  174.     display: flex;
  175.     flex-direction: column;
  176.     overflow: hidden;
  177.   }
  178.   /* ── Topbar ────────────────────────────────────────────────────────────── */
  179.   .cp-topbar {
  180.     height: var(--cp-topbar-h);
  181.     background: var(--cp-surface-white);
  182.     border-bottom: 1px solid var(--cp-outline-variant);
  183.     display: flex;
  184.     align-items: center;
  185.     gap: 0;
  186.     padding: 0;
  187.     flex-shrink: 0;
  188.     z-index: 90;
  189.   }
  190.   .cp-topbar-logo {
  191.     display: flex;
  192.     align-items: center;
  193.     gap: 7px;
  194.     padding: 0 16px 0 20px;
  195.     font-family: var(--cp-font-head);
  196.     font-size: var(--cp-fs-base, 13px);
  197.     font-weight: 700;
  198.     color: var(--cp-primary);
  199.     letter-spacing: -0.01em;
  200.     white-space: nowrap;
  201.   }
  202.   .cp-topbar-logo .cp-logo-mark {
  203.     width: 24px;
  204.     height: 24px;
  205.     background: var(--cp-primary);
  206.     border-radius: 6px;
  207.     display: flex;
  208.     align-items: center;
  209.     justify-content: center;
  210.     color: var(--cp-on-primary, #fff);
  211.     font-size: 12px;
  212.   }
  213.   /* WHICH COMPANY AM I IN — users on multi-company logins could not tell, because this
  214.      topbar hard-coded the product name and never showed the tenant. Reuses the SAME
  215.      session keys as framework/_cp_topstrip.html.twig; no second source of truth. */
  216.   .cp-topbar-company {
  217.     display: flex;
  218.     align-items: center;
  219.     gap: 6px;
  220.     padding: 4px 11px;
  221.     margin-left: 2px;
  222.     border-radius: 999px;
  223.     border: 1px solid var(--cp-outline-variant);
  224.     background: var(--cp-surface-white);
  225.     font-family: var(--cp-font-body);
  226.     font-size: var(--cp-fs-sm);
  227.     font-weight: 700;
  228.     color: var(--cp-text);
  229.     white-space: nowrap;
  230.     max-width: 260px;
  231.     text-decoration: none;
  232.   }
  233.   .cp-topbar-company:hover { text-decoration: none; color: var(--cp-text); }
  234.   .cp-topbar-company .material-icons { font-size: var(--cp-fs-md, 15px); color: var(--cp-text-faint); }
  235.   .cp-topbar-company-nm { overflow: hidden; text-overflow: ellipsis; }
  236.   a.cp-topbar-company:hover { border-color: var(--cp-primary); }
  237.   @media (max-width: 720px) { .cp-topbar-company { max-width: 130px; } }
  238.   .cp-topbar-nav {
  239.     display: flex;
  240.     align-items: center;
  241.     gap: 0;
  242.     padding: 0 4px;
  243.     border-right: 1px solid var(--cp-outline-variant);
  244.   }
  245.   .cp-topbar-nav-item {
  246.     padding: 0 12px;
  247.     height: var(--cp-topbar-h);
  248.     display: flex;
  249.     align-items: center;
  250.     font-size: var(--cp-fs-sm, 12.5px);
  251.     font-weight: 500;
  252.     color: var(--cp-text-muted);
  253.     cursor: pointer;
  254.     border-bottom: 2px solid transparent;
  255.     text-decoration: none;
  256.     transition: color 0.1s, border-color 0.1s;
  257.   }
  258.   .cp-topbar-nav-item:hover { color: var(--cp-text); text-decoration: none; }
  259.   .cp-topbar-nav-item.active {
  260.     color: var(--cp-primary);
  261.     border-bottom-color: var(--cp-primary);
  262.     font-weight: 600;
  263.   }
  264.   .cp-topbar-ai-bar {
  265.     flex: 1 1 0;
  266.     padding: 0 16px;
  267.   }
  268.   .cp-topbar-ai-input {
  269.     width: 100%;
  270.     max-width: 480px;
  271.     height: 32px;
  272.     background: var(--cp-surface-low);
  273.     border: 1px solid var(--cp-outline-variant);
  274.     border-radius: 99px;
  275.     padding: 0 14px 0 34px;
  276.     font-size: 12px;
  277.     color: var(--cp-text);
  278.     font-family: var(--cp-font-body);
  279.     position: relative;
  280.     outline: none;
  281.   }
  282.   .cp-topbar-ai-wrap {
  283.     position: relative;
  284.     display: inline-flex;
  285.     align-items: center;
  286.   }
  287.   .cp-topbar-ai-wrap .cp-ai-icon {
  288.     position: absolute;
  289.     left: 10px;
  290.     font-size: var(--cp-fs-base, 13px);
  291.     color: var(--cp-primary);
  292.     pointer-events: none;
  293.   }
  294.   .cp-topbar-ai-input:focus {
  295.     background: var(--cp-surface-white);
  296.     border-color: var(--cp-primary);
  297.     box-shadow: 0 0 0 3px var(--cp-amber-dim);
  298.   }
  299.   .cp-topbar-bell { position: relative; text-decoration: none; }
  300.   .cp-topbar-bell:hover { text-decoration: none; }
  301.   .cp-topbar-bell-badge {
  302.     position: absolute; top: 1px; right: 0;
  303.     min-width: 15px; height: 15px; padding: 0 4px;
  304.     border-radius: 999px; background: var(--cp-error); color: var(--cp-on-primary, #fff);
  305.     font-family: var(--cp-font-mono); font-size: 9px; font-weight: 700;
  306.     line-height: 15px; text-align: center;
  307.   }
  308.   .cp-topbar-help { display: inline-block; }
  309.   .cp-topbar-help-menu { min-width: 190px; }
  310.   .cp-topbar-help-menu > li > a {
  311.     display: flex; align-items: center; gap: 9px;
  312.     padding: 8px 14px; font-size: var(--cp-fs-sm, 12.5px); color: var(--cp-text-muted);
  313.   }
  314.   .cp-topbar-help-menu > li > a:hover { background: var(--cp-surface-low); color: var(--cp-text); }
  315.   /* The report widget's FAB is redundant now it is in the Help menu — and it was pinned
  316.      bottom-left ON TOP of the sidebar footer's "Back to ERP". */
  317.   #si-fab { display: none !important; }
  318.   .cp-topbar-actions {
  319.     display: flex;
  320.     align-items: center;
  321.     gap: 4px;
  322.     padding: 0 14px;
  323.   }
  324.   .cp-topbar-icon-btn {
  325.     width: 32px;
  326.     height: 32px;
  327.     border-radius: var(--cp-radius);
  328.     border: none;
  329.     background: transparent;
  330.     color: var(--cp-text-muted);
  331.     display: flex;
  332.     align-items: center;
  333.     justify-content: center;
  334.     cursor: pointer;
  335.     font-size: var(--cp-fs-md, 15px);
  336.     transition: background 0.1s, color 0.1s;
  337.   }
  338.   .cp-topbar-icon-btn:hover {
  339.     background: var(--cp-surface-low);
  340.     color: var(--cp-text);
  341.   }
  342.   .cp-start-anywhere-btn {
  343.     height: 30px;
  344.     padding: 0 12px;
  345.     background: var(--cp-primary);
  346.     color: var(--cp-on-primary);
  347.     border: none;
  348.     border-radius: var(--cp-radius);
  349.     font-size: 12px;
  350.     font-weight: 600;
  351.     font-family: var(--cp-font-head);
  352.     cursor: pointer;
  353.     display: flex;
  354.     align-items: center;
  355.     gap: 5px;
  356.     transition: background 0.12s;
  357.     white-space: nowrap;
  358.   }
  359.   .cp-start-anywhere-btn:hover { background: var(--cp-primary-hover); }
  360.   /* ── Content area ──────────────────────────────────────────────────────── */
  361.   .cp-content {
  362.     flex: 1 1 0;
  363.     overflow-y: auto;
  364.     background: var(--cp-surface);
  365.   }
  366.   /* Render finance/central tool pages (those that swap in a custom sidebar) a bit larger. */
  367.   .cp-content.cp-content-zoom { zoom: 1.12; }
  368.   /* ── Page head ─────────────────────────────────────────────────────────── */
  369.   .cp-page-head {
  370.     padding: 22px 28px 16px;
  371.     background: var(--cp-surface-white);
  372.     border-bottom: 1px solid var(--cp-outline-variant);
  373.     display: flex;
  374.     align-items: flex-start;
  375.     justify-content: space-between;
  376.     gap: 16px;
  377.   }
  378.   .cp-page-title {
  379.     font-family: var(--cp-font-head);
  380.     font-size: var(--cp-fs-xl, 20px);
  381.     font-weight: 700;
  382.     color: var(--cp-text);
  383.     letter-spacing: -0.02em;
  384.     margin: 0 0 3px;
  385.   }
  386.   .cp-page-subtitle {
  387.     font-size: var(--cp-fs-sm, 12.5px);
  388.     color: var(--cp-text-muted);
  389.     margin: 0;
  390.   }
  391.   .cp-page-actions {
  392.     display: flex;
  393.     align-items: center;
  394.     gap: 8px;
  395.     flex-shrink: 0;
  396.   }
  397.   /* ── Card ──────────────────────────────────────────────────────────────── */
  398.   .cp-card {
  399.     background: var(--cp-surface-white);
  400.     border-radius: var(--cp-radius-lg);
  401.     border: 1px solid var(--cp-outline-variant);
  402.     box-shadow: var(--cp-shadow-card);
  403.     transition: border-color 0.18s, box-shadow 0.18s, transform 0.18s;
  404.   }
  405.   .cp-card:hover {
  406.     box-shadow: 0 4px 14px rgba(0,0,0,0.07);
  407.     transform: translateY(-2px);
  408.   }
  409.   .cp-card-body { padding: 18px 20px; }
  410.   .cp-card-head {
  411.     padding: 14px 18px;
  412.     border-bottom: 1px solid var(--cp-surface-container);
  413.     font-size: var(--cp-fs-sm, 12.5px);
  414.     font-weight: 600;
  415.     color: var(--cp-text);
  416.     font-family: var(--cp-font-head);
  417.   }
  418.   /* ── Buttons ───────────────────────────────────────────────────────────── */
  419.   .cp-btn {
  420.     display: inline-flex;
  421.     align-items: center;
  422.     gap: 6px;
  423.     height: 36px;
  424.     padding: 0 16px;
  425.     border-radius: var(--cp-radius);
  426.     border: 1px solid transparent;
  427.     font-size: var(--cp-fs-sm, 12.5px);
  428.     font-weight: 600;
  429.     font-family: var(--cp-font-head);
  430.     cursor: pointer;
  431.     text-decoration: none;
  432.     transition: background 0.12s, border-color 0.12s, color 0.12s;
  433.   }
  434.   .cp-btn-primary {
  435.     background: var(--cp-primary);
  436.     color: var(--cp-on-primary);
  437.     border-color: var(--cp-primary);
  438.   }
  439.   .cp-btn-primary:hover {
  440.     background: var(--cp-primary-hover);
  441.     border-color: var(--cp-primary-hover);
  442.     color: var(--cp-on-primary, #fff);
  443.     text-decoration: none;
  444.   }
  445.   .cp-btn-outline {
  446.     background: transparent;
  447.     color: var(--cp-primary);
  448.     border-color: var(--cp-primary);
  449.   }
  450.   .cp-btn-outline:hover {
  451.     background: var(--cp-amber-dim);
  452.     text-decoration: none;
  453.     color: var(--cp-primary);
  454.   }
  455.   .cp-btn-ghost {
  456.     background: transparent;
  457.     color: var(--cp-text-muted);
  458.     border-color: var(--cp-outline-variant);
  459.   }
  460.   .cp-btn-ghost:hover {
  461.     background: var(--cp-surface-low);
  462.     color: var(--cp-text);
  463.     text-decoration: none;
  464.   }
  465.   /* ── Status chips ──────────────────────────────────────────────────────── */
  466.   .cp-chip {
  467.     display: inline-flex;
  468.     align-items: center;
  469.     gap: 4px;
  470.     padding: 2px 9px;
  471.     border-radius: 99px;
  472.     font-family: var(--cp-font-mono);
  473.     font-size: var(--cp-fs-xs, 11.5px);
  474.     font-weight: 600;
  475.     letter-spacing: 0.03em;
  476.     text-transform: uppercase;
  477.   }
  478.   .cp-chip-success { background: rgba(46,125,50,.10); color: #2e7d32; }
  479.   .cp-chip-warn    { background: rgba(245,124,0,.10); color: #e65100; }
  480.   .cp-chip-error   { background: rgba(186,26,26,.10); color: #ba1a1a; }
  481.   .cp-chip-info    { background: rgba(133,79,0,.10);  color: #854f00; }
  482.   .cp-chip-neutral { background: var(--cp-surface-container); color: var(--cp-text-muted); }
  483.   /* ── Metric widget ─────────────────────────────────────────────────────── */
  484.   .cp-metric-value {
  485.     font-family: var(--cp-font-head);
  486.     font-size: 28px;
  487.     font-weight: 700;
  488.     color: var(--cp-text);
  489.     letter-spacing: -0.03em;
  490.     line-height: 1;
  491.   }
  492.   .cp-metric-label {
  493.     font-family: var(--cp-font-mono);
  494.     font-size: var(--cp-fs-xs, 11.5px);
  495.     font-weight: 700;
  496.     color: var(--cp-text-faint);
  497.     text-transform: uppercase;
  498.     letter-spacing: 0.09em;
  499.     margin-top: 4px;
  500.   }
  501.   /* ── Score ring ────────────────────────────────────────────────────────── */
  502.   .cp-score-ring {
  503.     width: 64px;
  504.     height: 64px;
  505.     border-radius: 50%;
  506.     background: conic-gradient(var(--cp-primary) calc(var(--score) * 3.6deg), var(--cp-surface-high) 0deg);
  507.     display: flex;
  508.     align-items: center;
  509.     justify-content: center;
  510.     position: relative;
  511.   }
  512.   .cp-score-ring::before {
  513.     content: '';
  514.     position: absolute;
  515.     width: 46px;
  516.     height: 46px;
  517.     border-radius: 50%;
  518.     background: var(--cp-surface-white);
  519.   }
  520.   .cp-score-ring-value {
  521.     position: relative;
  522.     z-index: 1;
  523.     font-family: var(--cp-font-head);
  524.     font-size: var(--cp-fs-md, 15px);
  525.     font-weight: 700;
  526.     color: var(--cp-primary);
  527.   }
  528.   /* ── Responsive ────────────────────────────────────────────────────────── */
  529.   @media (max-width: 768px) {
  530.     .cp-sidebar { width: 0; min-width: 0; overflow: hidden; position: absolute; z-index: 200; transition: width 0.2s; }
  531.     .cp-sidebar.open { width: var(--cp-sidebar-w); min-width: var(--cp-sidebar-w); }
  532.     .cp-topbar-nav { display: none; }
  533.   }
  534.   /* ── Global module rail (folds CoPilot/CRM onto the app-wide rail) ────────── */
  535.   /* Scoped to this shell so it borrows CoPilot's amber tokens and never collides
  536.      with the global cp-shell's own .cp-rail rules (different page, same names). */
  537.   .cp-shell > .cp-rail { width: 60px; flex-shrink: 0; background: var(--cp-surface-low);
  538.     border-right: 1px solid var(--cp-outline-variant); display: flex; flex-direction: column;
  539.     align-items: center; padding: 10px 0; gap: 3px; overflow-y: auto; z-index: 101; }
  540.   .cp-shell > .cp-rail .cp-rail-item { width: 46px; padding: 8px 0 6px; border-radius: 10px;
  541.     display: flex; flex-direction: column; align-items: center; gap: 3px;
  542.     color: var(--cp-text-faint); text-decoration: none; transition: background .12s, color .12s; }
  543.   .cp-shell > .cp-rail .cp-rail-item:hover { background: var(--cp-surface-white); color: var(--cp-text); text-decoration: none; }
  544.   .cp-shell > .cp-rail .cp-rail-item.active { background: var(--cp-amber-dim); color: var(--cp-primary); }
  545.   .cp-shell > .cp-rail .cp-rail-item .material-icons { font-size: var(--cp-fs-xl, 20px); }
  546.   .cp-shell > .cp-rail .cp-rail-lbl { font-size: var(--cp-fs-micro, 10px); font-weight: 600; text-align: center; line-height: 1.05; }
  547.   @media (max-width: 768px) { .cp-shell > .cp-rail { display: none; } }
  548. </style>
  549. {# Montserrat + JetBrains Mono (DM Sans already loaded by codecovers_header) #}
  550. <link rel="preconnect" href="https://fonts.googleapis.com">
  551. <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  552. <div class="cp-shell">
  553.   {# ── Global module rail — folds CoPilot/CRM into the app-wide navigation.
  554.        CoPilot lives under the Projects area, so highlight that unless a page overrides. #}
  555.   {% set active_module = active_module|default('projects') %}
  556.   {% include '@Application/framework/_cp_global_rail.html.twig' %}
  557.   {# ── Left sidebar (swappable; defaults to the CoPilot rail) ──────────────── #}
  558.   {% include sidebar_partial|default('@System/inc/_copilot_sidebar.html.twig') %}
  559.   {# ── Main column ───────────────────────────────────────────────────────── #}
  560.   <div class="cp-main">
  561.     {% include '@Application/inc/_support_session_banner.html.twig' %}
  562.     {# ── Topbar ──────────────────────────────────────────────────────────── #}
  563.     <div class="cp-topbar">
  564.       <div class="cp-topbar-logo">
  565.         <span class="cp-logo-mark"><i class="zmdi zmdi-hive"></i></span>
  566.         HoneyBee ERP
  567.       </div>
  568.       {# ── WHICH COMPANY AM I IN ────────────────────────────────────────────
  569.          Users on multi-company logins reported not being able to tell which
  570.          company they were working in: this topbar showed only the product
  571.          name. The company switcher already existed in
  572.          framework/_cp_topstrip.html.twig, but that partial is NOT included by
  573.          this layout — so every page built on the cp-shell was anonymous.
  574.          Same session keys, so the two can never disagree. Shown ALWAYS (the
  575.          topstrip only shows it when >1, which is exactly when a single-company
  576.          user still needs to know which tenant they are in); becomes a switcher
  577.          only when there really is something to switch to. #}
  578.       {% set _cpCompanyNames = session[UserConstants.USER_COMPANY_NAME_LIST]|default('[]')|jsonDecode %}
  579.       {% set _cpCompanyId    = session[UserConstants.USER_COMPANY_ID]|default(0) %}
  580.       {% set _cpCompanyName  = _cpCompanyNames[_cpCompanyId]|default('') %}
  581.       {% if _cpCompanyName is not empty %}
  582.         {% if _cpCompanyNames|length > 1 %}
  583.           <div class="dropdown" style="display:inline-block;">
  584.             <a href="#" data-toggle="dropdown" class="cp-topbar-company"
  585.                title="You are working in {{ _cpCompanyName }} — click to switch company">
  586.               <i class="material-icons">business</i>
  587.               <span class="cp-topbar-company-nm">{{ _cpCompanyName }}</span>
  588.               <i class="material-icons">arrow_drop_down</i>
  589.             </a>
  590.             <ul class="dropdown-menu">
  591.               <li class="dropdown-header">Switch company</li>
  592.               {% for _cid, _cname in _cpCompanyNames %}
  593.                 <li><a href="{{ url('change_company_dashboard') }}/{{ _cid }}">{{ _cname }}</a></li>
  594.               {% endfor %}
  595.             </ul>
  596.           </div>
  597.         {% else %}
  598.           <span class="cp-topbar-company" title="You are working in {{ _cpCompanyName }}">
  599.             <i class="material-icons">business</i>
  600.             <span class="cp-topbar-company-nm">{{ _cpCompanyName }}</span>
  601.           </span>
  602.         {% endif %}
  603.       {% endif %}
  604.       <nav class="cp-topbar-nav">
  605.         <a href="{{ path('dashboard') }}" class="cp-topbar-nav-item">Workspaces</a>
  606.         <a href="{{ path('copilot_dashboard') }}" class="cp-topbar-nav-item active">Stages</a>
  607.         <a href="{{ path('system_admin_user_list') }}" class="cp-topbar-nav-item">Roles</a>
  608.       </nav>
  609.       <div class="cp-topbar-ai-bar">
  610.         <div class="cp-topbar-ai-wrap">
  611.           <span class="cp-ai-icon zmdi zmdi-flare"></span>
  612.           <input type="text" class="cp-topbar-ai-input" id="cp-ai-command"
  613.                  placeholder="Ask HoneyBee AI to check, fix, explain…">
  614.         </div>
  615.       </div>
  616.       <div class="cp-topbar-actions">
  617.         {# NOTICES — this bell was a <button> with no href and no handler: the notices page
  618.            existed at /notification and nothing in the new shell linked to it, which is why
  619.            "the notices board is not in the main layout". Now a real link with a live unread
  620.            count from /api/notifications/unread_count. #}
  621.         <a href="{{ path('my_notification') }}" class="cp-topbar-icon-btn cp-topbar-bell" title="Notices">
  622.           <i class="zmdi zmdi-notifications-none"></i>
  623.           <span class="cp-topbar-bell-badge" id="cpTopbarNoticeCount" hidden>0</span>
  624.         </a>
  625.         {# HELP — was also a dead button. Now a dropdown, and it is where "Report an issue"
  626.            lives: the widget's own floating FAB is pinned bottom-left over the sidebar
  627.            footer, which is both easy to miss and covers "Back to ERP". #}
  628.         <div class="dropdown cp-topbar-help">
  629.           <button class="cp-topbar-icon-btn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Help">
  630.             <i class="zmdi zmdi-help-outline"></i>
  631.           </button>
  632.           <ul class="dropdown-menu dropdown-menu-right cp-topbar-help-menu">
  633.             {# WHAT'S NEW — /changelog/ was reachable only through the Administration nav, so the
  634.                people who asked for it (users, and developers who are not admins) could not find
  635.                it. Same cp_can_access gate as every other link; module 468 is eFA=1. #}
  636.             {% if cp_can_access('change_log') %}
  637.               <li><a href="{{ path('change_log') }}"><i class="zmdi zmdi-collection-text"></i> What&#39;s new</a></li>
  638.             {% endif %}
  639.             <li><a href="#" onclick="if(window.siOpen){siOpen();}return false;"><i class="zmdi zmdi-bug"></i> Report an issue</a></li>
  640.             <li><a href="{{ path('my_notification') }}"><i class="zmdi zmdi-notifications-none"></i> Notices</a></li>
  641.           </ul>
  642.         </div>
  643.         <button class="cp-start-anywhere-btn" data-toggle="modal" data-target="#startAnywhereModal">
  644.           <i class="zmdi zmdi-plus"></i> New Entry
  645.         </button>
  646.       </div>
  647.     </div>
  648.     {# ── Page content ────────────────────────────────────────────────────── #}
  649.     <div class="cp-content{{ sidebar_partial is defined ? ' cp-content-zoom' : '' }}">
  650.       {% block body %}{% endblock %}
  651.     </div>
  652.   </div>{# /.cp-main #}
  653. </div>{# /.cp-shell #}
  654. {# SENT-HUB — "Report an issue" widget for every logged-in user (testers). Server-side session-gated. #}
  655. {% if app.session is defined and app.session.get('userId')|default(0) %}
  656.   {% include '@AgentOS/sentinel/_report_issue_widget.html.twig' %}
  657. {% endif %}
  658. {# ── Start Anywhere modal ─────────────────────────────────────────────────── #}
  659. {% include '@System/inc/_start_anywhere_modal.html.twig' %}
  660. <script>
  661. /* Unread notice count for the topbar bell. Fails silently: a decorative badge must never
  662.    surface an error on every page, and a stale/absent count is not worth a console full of
  663.    noise. Polls modestly rather than on a tight timer. */
  664. (function () {
  665.   var el = document.getElementById('cpTopbarNoticeCount');
  666.   if (!el || !window.fetch) { return; }
  667.   function refresh() {
  668.     fetch('{{ path('api_notifications_unread_count') }}', {credentials: 'same-origin'})
  669.       .then(function (r) { return r.ok ? r.json() : null; })
  670.       .then(function (d) {
  671.         var n = d && d.count ? parseInt(d.count, 10) : 0;
  672.         if (n > 0) { el.textContent = n > 99 ? '99+' : n; el.hidden = false; }
  673.         else { el.hidden = true; }
  674.       })
  675.       .catch(function () { /* silent by design */ });
  676.   }
  677.   refresh();
  678.   setInterval(refresh, 120000);
  679. })();
  680. </script>
  681. {% include '@Application/footer/footer_js_codecovers.html.twig' %}
  682. {# Bootstrap Modal JS (already have jQuery from header) #}
  683. {#<script src="{{ absolute_url(path('dashboard')) }}condensed_assets/javascript_codecovers_minimal.js?version={{ constant('ApplicationBundle\\Constants\\GeneralConstant::ENTITY_APP_VERSION') }}"></script>#}
  684. <script>
  685. (function () {
  686.   'use strict';
  687.   var aiInput = document.getElementById('cp-ai-command');
  688.   if (aiInput) {
  689.     aiInput.addEventListener('keydown', function (e) {
  690.       if (e.key === 'Enter' && this.value.trim()) {
  691.         var q = encodeURIComponent(this.value.trim());
  692.         window.location.href = '/ai/insights/feed?q=' + q;
  693.       }
  694.     });
  695.   }
  696.   var mobileToggle = document.getElementById('cp-mobile-sidebar-toggle');
  697.   var sidebar = document.querySelector('.cp-sidebar');
  698.   if (mobileToggle && sidebar) {
  699.     mobileToggle.addEventListener('click', function () {
  700.       sidebar.classList.toggle('open');
  701.     });
  702.   }
  703. }());
  704. </script>
  705. {% include '@Application/inc/menu/_spotlight_search.html.twig' ignore missing %}
  706. {% block page_scripts %}{% endblock %}