templates/base.html.twig line 1

  1. {# templates/layout/management_workspace.html.twig #}
  2. <!doctype html>
  3. <html lang="en">
  4. <head>
  5.     <!-- Required meta tags -->
  6.     <meta charset="utf-8">
  7.     <meta name="viewport" content="width=device-width, initial-scale=1">
  8.     <!-- Site Title -->
  9.     <title>Smart-Immo | Management</title>
  10.     <!-- Favicon -->
  11.     <link rel="shortcut icon" href="{{ asset('assets/img/smartimmoManagementHead.png') }}" type="image/x-icon">
  12.     {# === Vendor & App CSS (same stack as Espace Agent) === #}
  13.     <link href="{{ asset('assets/vendor/bootstrap/dist/css/bootstrap.min.css') }}" rel="stylesheet">
  14.     <link href="{{ asset('assets/vendor/icons/css/materialdesignicons.min.css') }}" rel="stylesheet">
  15.     <link rel="stylesheet" href="{{ asset('assets/vendor/date-picker/date-picker.css') }}">
  16.     <link rel="stylesheet" type="text/css" href="{{ asset('assets/vendor/slick/slick.min.css') }}" />
  17.     <link rel="stylesheet" type="text/css" href="{{ asset('assets/vendor/slick/slick-theme.min.css') }}" />
  18.     <link href="{{ asset('assets/css/style.css') }}" rel="stylesheet">
  19.     <link href="{{ asset('assets/admin/css/dasboard.css') }}" rel="stylesheet">
  20.     <link href="{{ asset('assets/admin/css/menu.css') }}" rel="stylesheet">  {# add this last #}
  21.     <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
  22.     <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
  23.     <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
  24.     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" defer></script>
  25.     <style type="text/css" media="print">
  26.         .no-print { display: none; }
  27.     </style>
  28.     <style>
  29.         .logo {
  30.             filter: brightness(0) saturate(100%) invert(23%) sepia(89%) saturate(2111%) hue-rotate(209deg) brightness(91%) contrast(97%);
  31.         }
  32.     </style>
  33.     <style>
  34.         .loader-inner.ball-pulse > div{
  35.             width:12px;height:12px;margin:0 4px;display:inline-block;border-radius:50%;
  36.             background:#2C5FD7;animation:ball-pulse 1s infinite ease-in-out;
  37.         }
  38.         .loader-inner.ball-pulse > div:nth-child(2){animation-delay:.2s}
  39.         .loader-inner.ball-pulse > div:nth-child(3){animation-delay:.4s}
  40.         @keyframes ball-pulse{0%,80%,100%{transform:scale(0)}40%{transform:scale(1)}}
  41.     </style>
  42.     {# optional page-level styles #}
  43.     {% block style %}{% endblock %}
  44. </head>
  45. <body class="bg-light">
  46. <div class="container-fluid p-0">
  47.     <div class="row g-0 position-relative align-items-start">
  48.         {# ========== Offcanvas (mobile sidebar) ========== #}
  49.         <div class="offcanvas offcanvas-start admin-offcanvas" tabindex="-1" id="adminSidebar" aria-labelledby="adminSidebarLabel">
  50.             <div class="offcanvas-header">
  51.                 <a href="{{ path('app_dashboard') }}" class="text-decoration-none d-flex align-items-center gap-2">
  52.                     <img src="{{ asset('assets/img/smartImmoManagement.png') }}" alt="Smart-Immo" class="logo" style="max-width:160px;height:auto;">
  53.                 </a>
  54.                 <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Fermer"></button>
  55.             </div>
  56.             <div class="offcanvas-body p-0">
  57.                 <nav class="admin-nav w-100" role="navigation" aria-label="Menu principal">
  58.                     <div class="nav-section">Menu</div>
  59.                     <a class="nav-link{% if app.request.pathinfo == '/admin' %} is-active{% endif %}"
  60.                        href="{{ path('app_dashboard') }}">
  61.                         <span class="mdi mdi-view-dashboard"></span> Tableau de bord
  62.                     </a>
  63.                     <a class="nav-link {% if app.request.pathinfo starts with "/admin/entreprise" %} active{% endif %}" href="{{ path('app_entreprise') }}">
  64.                         <span class="mdi mdi-office-building-outline"></span> Entreprises
  65.                     </a>
  66.                     <a class="nav-link {% if app.request.pathinfo starts with "/admin/account" %} active{% endif %}" href="{{ path('admin_accounts_index') }}">
  67.                         <span class="mdi mdi-folder-multiple"></span> Comptes
  68.                     </a>
  69.                     <a class="nav-link {% if app.request.pathinfo starts with "/admin/sms" %} active{% endif %}" href="{{ path('app_sms_config_index') }}">
  70.                         <span class="mdi mdi-message-bookmark"></span> SMS
  71.                     </a>
  72.                     <a href="{{ path('app_habitat') }}" class="nav-link{% if app.request.pathinfo starts with "/admin/biens" %} active{% endif %}">
  73.                         <span class="mdi mdi-home"></span>Biens immobiliers
  74.                     </a>
  75.                     <a href="{{ path('app_profil') }}" class="nav-link {% if app.request.pathinfo starts with "/admin/profil/information" %} active{% endif %}"><i class="demo-pli-male-female fs-5 me-2"></i>
  76.                         <span class="nav-label mininav-content ms-1">Mon profil</span>
  77.                     </a>
  78.                     <a class="nav-link" href="{{ path('app_logout') }}">
  79.                         <span class="mdi mdi-logout"></span> Déconnexion
  80.                     </a>
  81.                 </nav>
  82.             </div>
  83.         </div>
  84.         {# ========== Sidebar (desktop) ========== #}
  85.         <aside class="col-lg-2 d-none d-lg-block admin-sidebar">
  86.             <nav class="admin-nav">
  87.                 <div class="brand">
  88.                     <a href="{{ path('app_dashboard') }}" class="text-decoration-none">
  89.                         <img src="{{ asset('assets/img/smartImmoManagement.png') }}" alt="Smart-Immo" class="logo">
  90.                     </a>
  91.                 </div>
  92.                 <div class="mainnav__profile mt-2 d-flex align-items-center flex-column text-center">
  93.                     <div class="mininav-toggle py-2">
  94.                             <img class="mainnav__avatar rounded-circle border"
  95.                                  src="{{ asset('assets/img/imageIcon.png') }}"
  96.                                  alt="Profile Picture"
  97.                                  style="width:60px; height:60px; object-fit:cover;">
  98.                     </div>
  99.                     <div class="d-mn-max w-100">
  100.                         <div class="d-grid">
  101.                             <div class="btn shadow-none p-2 d-flex flex-column align-items-center">
  102.                                 <p class="mb-1">
  103.                                     <span class="text-gray-500">Utilisateur:</span>
  104.                                     <strong>
  105.                                     {% if is_granted('IS_AUTHENTICATED') and app.user.username is not null %}
  106.                                         {{ app.user.username }}
  107.                                     {% endif %}
  108.                                     </strong>
  109.                                 </p>
  110.                                 <small class="text-success">
  111.                                     {% if is_granted('IS_AUTHENTICATED') %}
  112.                                         {{ app.user.role }}
  113.                                     {% endif %}
  114.                                 </small>
  115.                             </div>
  116.                             <div id="usernav" class="nav flex-column collapse">
  117.                                 <a href="{{ path('app_logout') }}" class="nav-link d-flex align-items-center justify-content-center">
  118.                                     <i class="demo-pli-unlock fs-5 me-2"></i>
  119.                                     <span>Logout</span>
  120.                                 </a>
  121.                             </div>
  122.                         </div>
  123.                     </div>
  124.                 </div>
  125.                 <div class="nav-section">Menu</div>
  126.                 <a class="nav-link{% if app.request.pathinfo == '/admin' %} is-active{% endif %}" href="{{ path('app_dashboard') }}">
  127.                     <span class="mdi mdi-view-dashboard"></span> Tableau de bord
  128.                 </a>
  129.                 <a class="nav-link {% if app.request.pathinfo starts with "/admin/entreprise" %} active{% endif %}" href="{{ path('app_entreprise') }}">
  130.                     <span class="mdi mdi-office-building-outline"></span> Entreprises
  131.                 </a>
  132.                 <a class="nav-link {% if app.request.pathinfo starts with "/admin/account" %} active{% endif %}" href="{{ path('admin_accounts_index') }}">
  133.                     <span class="mdi mdi-folder-multiple"></span> Comptes
  134.                 </a>
  135.                 <a class="nav-link {% if app.request.pathinfo starts with "/admin/settings/sms" %} active{% endif %}" href="{{ path('app_sms_config_index') }}">
  136.                     <span class="mdi mdi-message"></span> SMS
  137.                 </a>
  138.                 <a href="{{ path('app_habitat') }}" class="nav-link{% if app.request.pathinfo starts with "/admin/biens" %} active{% endif %}">
  139.                     <span class="mdi mdi-home"></span>Biens immobiliers
  140.                 </a>
  141.                 <a href="{{ path('app_profil') }}" class="nav-link {% if app.request.pathinfo starts with "/admin/profil" %} active{% endif %}">
  142.                     <span class="mdi mdi-account"></span>Mon profil
  143.                 </a>
  144.                 {% if is_granted('ROLE_ADMIN') %}
  145.                     <a href="{{ path('app_user_index') }}" class="nav-link {% if app.request.pathinfo starts with "/admin/user" %} active{% endif %}">
  146.                         <span class="mdi mdi-account-multiple"></span>Utilisateurs
  147.                     </a>
  148.                 {% endif %}
  149.                 <a class="nav-link" href="{{ path('app_logout') }}">
  150.                     <span class="mdi mdi-logout"></span> Déconnexion
  151.                 </a>
  152.             </nav>
  153.         </aside>
  154.         {# ========== Content ========== #}
  155.         <div class="col-lg-10 content-dashboard p-3 p-lg-4">
  156.             {# Top bar #}
  157.             <!-- Sticky Top Bar Card -->
  158.             <div class="topbar-card topbar-sticky mb-3">
  159.                 <div class="row gx-3 gy-3 align-items-center">
  160.                     <div class="col-6 d-flex align-items-center">
  161.                         <div class="toggle me-2 d-lg-none">
  162.                             <button class="btn bg-white rounded-3 px-2 py-1" type="button"
  163.                                     data-bs-toggle="offcanvas" data-bs-target="#adminSidebar" aria-controls="adminSidebar">
  164.                                 <i class="mdi mdi-menu fs-5"></i>
  165.                             </button>
  166.                         </div>
  167.                         {% set hour = "now"|date("G") %}
  168.                         {% set greet = hour < 12 ? 'Bonjour' : (hour < 18 ? 'Bon après-midi' : 'Bonsoir') %}
  169.                         <span class="greet-chip d-none d-md-inline-flex align-items-center me-3" aria-live="polite">
  170.                 <i class="mdi mdi-emoticon-happy-outline me-1"></i>
  171.                 {{ greet }}{% if is_granted('IS_AUTHENTICATED') and app.user and app.user.name is defined and app.user.name %}, {{ app.user.lastname }}{% endif %} 👋
  172.             </span>
  173.                         <!-- Live Clock -->
  174.                         <span id="live-clock" class="badge bg-light text-dark border ms-2">--:--:--</span>
  175.                     </div>
  176.                     <div class="col-6 d-flex justify-content-end align-items-center">
  177.                         <div class="dropdown profile-dropdown">
  178.                             <button class="btn bg-white rounded-9 btn-sm d-flex align-items-center dropdown-toggle py-1 ps-1 pe-2"
  179.                                     type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">
  180.                                 {% if is_granted('IS_AUTHENTICATED') %}
  181.                                         <img src="{{ asset('assets/img/imageIcon.png') }}" class="img-fluid rounded-9" alt="">
  182.                                 {% endif %}
  183.                                 <span class="ms-2 me-1">{{ app.user.name }}</span>
  184.                             </button>
  185.                             <ul class="dropdown-menu border-0 rounded-9 dropdown-menu-end" aria-labelledby="dropdownMenuButton1">
  186.                                 <li><a class="dropdown-item" href="{{ path('app_profil') }}"><span class="mdi mdi-account"></span> Mon profil</a></li>
  187.                                 <li><a class="dropdown-item" href="{{ path('app_logout') }}"><span class="mdi mdi-logout"></span> Logout</a></li>
  188.                             </ul>
  189.                         </div>
  190.                     </div>
  191.                 </div>
  192.             </div>
  193.             {# Toasts (ported) #}
  194.             <div aria-live="polite" aria-atomic="true" class="position-relative">
  195.                 <div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 2000;">
  196.                     {% set levels = {'info':'info', 'success':'success', 'warning':'warning', 'error':'danger'} %}
  197.                     {% for level, bs in levels %}
  198.                         {% for message in app.session.flashBag.get(level) %}
  199.                             <div class="toast align-items-center text-bg-{{ bs }} border-0"
  200.                                  role="alert" aria-live="assertive" aria-atomic="true"
  201.                                  data-bs-delay="4000">
  202.                                 <div class="d-flex">
  203.                                     <div class="toast-body">{{ message|raw }}</div>
  204.                                     <button type="button" class="btn-close btn-close-white me-2 m-auto"
  205.                                             data-bs-dismiss="toast" aria-label="Fermer"></button>
  206.                                 </div>
  207.                             </div>
  208.                         {% endfor %}
  209.                     {% endfor %}
  210.                 </div>
  211.             </div>
  212.             <div class="dashboard-content pt-2 pt-lg-3">
  213.                 {% block bodyHeader %}{% endblock %}
  214.                 {% block bodyContent %}{% endblock %}
  215.             </div>
  216.             <footer class="mt-auto py-3">
  217.                 <div class="d-flex flex-column flex-md-row align-items-md-center">
  218.                     <div class="text-nowrap mb-2 mb-md-0 small">
  219.                         Copyright &copy; {{ "now"|date("Y") }}
  220.                         <a href="https://smarttech-rdc.com" class="ms-1 btn-link fw-bold">Smart-Technology</a>
  221.                     </div>
  222.                 </div>
  223.             </footer>
  224.         </div>
  225.         {# /Content #}
  226.     </div>
  227. </div>
  228. {# ========== Modal AJAX loader ========== #}
  229. <div class="modal fade" id="modalCenter" tabindex="-1" aria-labelledby="modalCenterTitle" aria-hidden="true">
  230.     <div class="modal-dialog modal-lg modal-dialog-centered">
  231.         <div class="modal-content shadow-lg rounded-4 border-0">
  232.             {# Header with subtle background #}
  233.             <div class="modal-header bg-light border-0 rounded-top-4">
  234.                 <h5 class="modal-title fw-semibold text-primary" id="modalCenterTitle">
  235.                     Chargement en cours
  236.                 </h5>
  237.                 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
  238.             </div>
  239.             {# Dynamic content wrapper #}
  240.             <div id="modal-content-ajax">
  241.                 <div class="modal-body d-flex justify-content-center align-items-center flex-column py-5">
  242.                     <div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
  243.                         <span class="visually-hidden">Chargement...</span>
  244.                     </div>
  245.                     <p class="text-muted small mb-0">Veuillez patienter...</p>
  246.                 </div>
  247.             </div>
  248.         </div>
  249.     </div>
  250. </div>
  251. {# ========== JS Config & loaders ========== #}
  252. <script>
  253.     class Config {
  254.         constructor() {
  255.             this.basePath = '{{ app.request.basepath }}';
  256.             Object.seal(this);
  257.         }
  258.     }
  259.     var config = new Config();
  260.     var loader = `<div class="d-flex justify-content-center align-items-center flex-column py-3">
  261.                     <div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
  262.                         <span class="visually-hidden">Chargement...</span>
  263.                     </div>
  264.                     <p class="text-muted small mb-0">Veuillez patienter...</p>
  265.                 </div>`;
  266.     var loaderNode = document.createElement("img");
  267.     loaderNode.src = config.basePath + "/assets/img/ajax-loader.gif";
  268.     var modalLoader = '<div class="modal-body">' + loader + '</div>';
  269. </script>
  270. {# === Footer scripts (same stack as Espace Agent) === #}
  271. {#<script src="{{ asset('assets/vendor/bootstrap/dist/js/bootstrap.bundle.min.js') }}"></script>#}
  272. {#<script type="text/javascript" src="{{ asset('assets/vendor/slick/slick.min.js') }}"></script>#}
  273. {#<script src="{{ asset('assets/vendor/date-picker/date-picker.js') }}"></script>#}
  274. {##}
  275. {#<script type="text/javascript" src="{{ asset('assets/js/bodyevents.js') }}"></script>#}
  276. <script src="{{ asset('assets/js/ajax_1.0.1.js') }}"></script>
  277. <script src="{{ asset('assets/js/settings.js') }}"></script>
  278. {#<script src="{{ asset('assets/js/osahan.js') }}"></script>#}
  279. <script>
  280.     function updateClock(){
  281.         const now = new Date();
  282.         const h = String(now.getHours()).padStart(2,'0');
  283.         const m = String(now.getMinutes()).padStart(2,'0');
  284.         const s = String(now.getSeconds()).padStart(2,'0');
  285.         document.getElementById('live-clock').textContent = `${h}:${m}:${s}`;
  286.     }
  287.     setInterval(updateClock,1000);
  288.     updateClock();
  289. </script>
  290. {# Auto-show toasts #}
  291. <script>
  292.     (function () {
  293.         const run = () => {
  294.             if (!window.bootstrap) return;
  295.             document.querySelectorAll('.toast')
  296.                 .forEach(el => bootstrap.Toast.getOrCreateInstance(el).show());
  297.         };
  298.         if (document.readyState === 'loading') {
  299.             document.addEventListener('DOMContentLoaded', run);
  300.         } else {
  301.             run();
  302.         }
  303.     })();
  304. </script>
  305. {# optional page-level scripts #}
  306. {% block javascripts %}{% endblock %}
  307. </body>
  308. </html>