diff --git a/Bootstrap_new/css/style_office.css b/Bootstrap_new/css/style_office.css index a415323..767541a 100644 --- a/Bootstrap_new/css/style_office.css +++ b/Bootstrap_new/css/style_office.css @@ -1,698 +1,738 @@ - /* Style Office pour le Portail RH*/ - :root { - /* PowerPoint/Office 2019 Color Palette */ - --office-primary: #b7472a; /* Sidebar color - PowerPoint accent */ - --office-secondary: #2b579a; /* Header/buttons - Office blue */ - --office-accent: #107c10; /* Success/active states - Excel green */ - --office-light: #f3f2f1; /* Background - Office gray */ - --office-common: #e6e6e6; /* Background - Office gray */ - --office-dark: #323130; /* Text color */ - --office-border: #d0d0d0; /* Borders */ - --office-hover: #f0f0f0; /* Hover states */ - --office-card: #ffffff; /* Card backgrounds */ - - --sidebar-width: 260px; - --sidebar-collapsed: 70px; - --header-height: 64px; - --transition-speed: 0.3s; - } +/* ============================================ + STYLE OFFICE POUR LE PORTAIL RH - VERSION STRUCTURE + ============================================ */ + +:root { + /* PowerPoint/Office 2019 Color Palette */ + --office-primary: #b7472a; /* Sidebar color - PowerPoint accent */ + --office-secondary: #2b579a; /* Header/buttons - Office blue */ + --office-accent: #107c10; /* Success/active states - Excel green */ + --office-light: #f3f2f1; /* Background - Office gray */ + --office-common: #e6e6e6; /* Background - Office gray */ + --office-dark: #323130; /* Text color */ + --office-border: #d0d0d0; /* Borders */ + --office-hover: #f0f0f0; /* Hover states */ + --office-card: #ffffff; /* Card backgrounds */ - /* Base Styles */ - body { - font-family: 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif; - background-color: var(--office-common); - color: var(--office-dark); - line-height: 1.5; - overflow-x: hidden; - } + /* Dimensions */ + --sidebar-width: 260px; + --sidebar-collapsed: 70px; + --header-height: 64px; + --transition-speed: 0.3s; - /* Header - PowerPoint/Office Style */ - .app-header { - background-color: var(--office-light); - height: var(--header-height); - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 1030; - } - - .header-content { - display: flex; - align-items: center; - justify-content: space-between; - height: 100%; - padding: 0 24px; - } - - .logo-container { - display: flex; - align-items: center; - gap: 16px; - } - - .app-logo { - display: flex; - align-items: center; - color: #313c4c; - text-decoration: none; - font-weight: 600; - font-size: 20px; - } - - .app-logo img { - height: 36px; - /*filter: brightness(0) invert(1);*/ - } - - /* Sidebar - PowerPoint Orange (#b7472a) */ + /* Responsive breakpoints */ + --breakpoint-tablet: 1200px; + --breakpoint-mobile: 768px; +} + +/* ============================================ + BASE STYLES & RESET + ============================================ */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif; + background-color: var(--office-common); + color: var(--office-dark); + line-height: 1.5; + overflow-x: hidden; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ============================================ + HEADER STYLES + ============================================ */ +.app-header { + background-color: var(--office-light); + height: var(--header-height); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1030; +} + +.header-content { + display: flex; + align-items: center; + justify-content: space-between; + height: 100%; + padding: 0 24px; +} + +.logo-container { + display: flex; + align-items: center; + gap: 16px; +} + +.app-logo { + display: flex; + align-items: center; + color: #313c4c; + text-decoration: none; + font-weight: 600; + font-size: 20px; +} + +.app-logo img { + height: 36px; +} + +/* ============================================ + SIDEBAR STYLES + ============================================ */ +.app-sidebar { + width: var(--sidebar-width); + background: linear-gradient(180deg, var(--office-primary) 0%, #a53e24 100%); + position: fixed; + top: var(--header-height); + left: 0; + bottom: 0; + z-index: 1020; + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); + transition: width var(--transition-speed) ease; + overflow-y: auto; + overflow-x: hidden; +} + +/* Pattern overlay */ +.app-sidebar::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-image: + radial-gradient(circle at 10% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 20%), + radial-gradient(circle at 90% 80%, rgba(255, 255, 255, 0.03) 0%, transparent 20%); + pointer-events: none; +} + +.sidebar-nav { + padding: 24px 0; +} + +.nav-section { + margin-bottom: 32px; + padding: 0 20px; +} + +.nav-title { + color: rgba(255, 255, 255, 0.7); + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 12px; + padding: 0 16px; +} + +.nav-item { + margin-bottom: 4px; +} + +.nav-link { + display: flex; + align-items: center; + color: rgba(255, 255, 255, 0.85); + padding: 12px 16px; + text-decoration: none; + border-radius: 6px; + transition: all 0.2s ease; + position: relative; + overflow: hidden; +} + +.nav-link:hover { + background-color: rgba(255, 255, 255, 0.1); + color: white; +} + +.nav-link.active { + background-color: rgba(255, 255, 255, 0.15); + color: white; + font-weight: 500; +} + +.nav-link i { + width: 24px; + font-size: 18px; + margin-right: 12px; + text-align: center; +} + +.nav-text { + flex: 1; + font-size: 14px; +} + +.nav-badge { + background-color: var(--office-accent); + color: white; + font-size: 11px; + padding: 2px 6px; + border-radius: 10px; + margin-left: 8px; +} + +.nav-arrow { + font-size: 12px; + opacity: 0.7; + transition: transform 0.2s ease; +} + +.nav-link[aria-expanded="true"] .nav-arrow { + transform: rotate(90deg); +} + +.nav-submenu { + background-color: rgba(0, 0, 0, 0.1); + border-radius: 6px; + margin-top: 4px; + padding: 8px 0; +} + +.nav-submenu .nav-link { + padding: 8px 16px 8px 52px; + font-size: 13px; +} + +.nav-submenu .nav-link::before { + content: '•'; + position: absolute; + left: 36px; + opacity: 0.6; +} + +/* ============================================ + MAIN CONTENT AREA + ============================================ */ +.app-main { + margin-left: var(--sidebar-width); + padding-top: var(--header-height); + min-height: 100vh; + transition: margin-left var(--transition-speed) ease; + background-color: var(--office-light); +} + +.content-area { + padding: 24px; + max-width: 1400px; + margin: 0 auto; +} + +/* ============================================ + NAVIGATION TABS + ============================================ */ +.nav-bar { + background-color: white; + border-radius: 8px; + padding: 16px 24px; + margin-bottom: 24px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); + border: 1px solid var(--office-border); +} + +.nav-tabs { + display: flex; + gap: 8px; + overflow-x: auto; + padding-bottom: 4px; +} + +.nav-tab { + display: inline-flex; + align-items: center; + padding: 8px 16px; + background-color: transparent; + color: var(--office-dark); + text-decoration: none; + border-radius: 4px; + font-size: 14px; + font-weight: 500; + white-space: nowrap; + transition: all 0.2s ease; + border: 1px solid transparent; +} + +.nav-tab:hover { + background-color: var(--office-hover); + color: var(--office-secondary); +} + +.nav-tab.active { + background-color: var(--office-secondary); + color: white; + border-color: var(--office-secondary); +} + +.nav-tab i { + margin-right: 8px; + font-size: 16px; +} + +/* ============================================ + CONTENT CARDS + ============================================ */ +.content-card { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + border: 1px solid var(--office-border); + margin-bottom: 24px; + overflow: hidden; +} + +.card-header { + background-color: #fafafa; + border-bottom: 1px solid var(--office-border); + padding: 20px 24px; + font-weight: 600; + color: var(--office-dark); + font-size: 18px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.card-body { + padding: 24px; +} + +/* ============================================ + HEADER CONTROLS + ============================================ */ +.header-controls { + display: flex; + align-items: center; + gap: 16px; +} + +.header-btn { + background: transparent; + border: none; + color: #313c4c; + padding: 8px; + border-radius: 4px; + cursor: pointer; + position: relative; + transition: background-color 0.2s ease; +} + +.header-btn:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.notification-badge { + position: absolute; + top: 2px; + right: 2px; + background-color: var(--office-accent); + color: white; + font-size: 10px; + font-weight: 600; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.user-menu { + display: flex; + align-items: center; + gap: 12px; + cursor: pointer; + padding: 4px 12px; + border-radius: 6px; + transition: background-color 0.2s ease; +} + +.user-menu:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, var(--office-primary), #ff6b35); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; +} + +.user-info { + display: flex; + flex-direction: column; +} + +.user-name { + color: #313c4c; + font-weight: 500; + font-size: 14px; +} + +.user-role { + color: #313c4c; + font-size: 12px; +} + +/* ============================================ + CONTEXT PANEL + ============================================ */ +.context-panel { + position: fixed; + right: -380px; + top: var(--header-height); + bottom: 0; + width: 380px; + background-color: white; + box-shadow: -2px 0 12px rgba(0, 0, 0, 0.1); + z-index: 1015; + transition: right 0.3s ease; + display: flex; + flex-direction: column; + border-left: 1px solid var(--office-border); +} + +.context-panel.open { + right: 0; +} + +.context-header { + background: linear-gradient(135deg, var(--office-primary), #ee6a49); + color: white; + padding: 20px 24px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.context-title { + font-size: 18px; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; +} + +.context-close { + background: transparent; + border: none; + color: white; + font-size: 20px; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: background-color 0.2s ease; +} + +.context-close:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.context-body { + flex: 1; + padding: 24px; + overflow-y: auto; +} + +.context-section { + margin-bottom: 28px; +} + +.section-title { + font-size: 14px; + font-weight: 600; + color: var(--office-secondary); + margin-bottom: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.section-title i { + font-size: 16px; +} + +.info-box { + background-color: #fafafa; + border: 1px solid var(--office-border); + border-radius: 6px; + padding: 16px; + margin-bottom: 12px; +} + +.info-label { + font-size: 12px; + color: #666; + margin-bottom: 4px; +} + +.info-value { + font-size: 15px; + font-weight: 500; + color: var(--office-dark); + line-height: 1.4; +} + +.action-btn { + width: 100%; + padding: 12px; + background-color: white; + border: 1px solid var(--office-border); + border-radius: 6px; + color: var(--office-dark); + font-weight: 500; + text-align: left; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + transition: all 0.2s ease; + margin-bottom: 8px; +} + +.action-btn:hover { + background-color: var(--office-hover); + border-color: var(--office-secondary); + color: var(--office-secondary); +} + +.action-btn i { + width: 20px; + text-align: center; + font-size: 18px; +} + +.photo-container { + text-align: center; + padding: 16px; + background-color: #fafafa; + border-radius: 8px; + border: 1px solid var(--office-border); +} + +.patient-photo { + width: 150px; + height: 150px; + border-radius: 8px; + object-fit: cover; + border: 3px solid white; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + cursor: pointer; + transition: transform 0.3s ease; +} + +.patient-photo:hover { + transform: scale(1.05); +} + +/* ============================================ + CONTEXT TOGGLE BUTTON + ============================================ */ +.context-toggle { + position: fixed; + right: 24px; + bottom: 24px; + width: 56px; + height: 56px; + border-radius: 50%; + background: linear-gradient(135deg, var(--office-primary), #ff6b35); + color: white; + border: none; + box-shadow: 0 4px 12px rgba(183, 71, 42, 0.3); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + z-index: 1010; + transition: all 0.3s ease; +} + +.context-toggle:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(183, 71, 42, 0.4); +} + +/* ============================================ + TEST MODE INDICATOR + ============================================ */ +.test-indicator { + background: linear-gradient(45deg, #ffd700, #ffed4e); + color: #8a6d3b; + padding: 4px 12px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 12px; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +} + +/* ============================================ + LANGUAGE SELECTOR + ============================================ */ +.language-selector { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s ease; + color: #313c4c; +} + +.language-selector:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +.language-flag { + width: 24px; + height: 16px; + object-fit: cover; + border-radius: 2px; +} + +/* ============================================ + MODAL CUSTOMIZATION + ============================================ */ +.modal-office .modal-header { + background: linear-gradient(135deg, var(--office-secondary), #1e4a8b); + color: white; + border-bottom: none; +} + +.modal-office .modal-title { + font-weight: 600; +} + +/* ============================================ + RESPONSIVE DESIGN + ============================================ */ +@media (max-width: 1200px) { .app-sidebar { - width: var(--sidebar-width); - background: linear-gradient(180deg, var(--office-primary) 0%, #a53e24 100%); - position: fixed; - top: var(--header-height); - left: 0; - bottom: 0; - z-index: 1020; - box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); - transition: width var(--transition-speed) ease; - overflow-y: auto; - overflow-x: hidden; + width: var(--sidebar-collapsed); } - /* Subtle pattern overlay for modern look */ - .app-sidebar::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: - radial-gradient(circle at 10% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 20%), - radial-gradient(circle at 90% 80%, rgba(255, 255, 255, 0.03) 0%, transparent 20%); - pointer-events: none; + .app-main { + margin-left: var(--sidebar-collapsed); } - .sidebar-nav { - padding: 24px 0; - } - - .nav-section { - margin-bottom: 32px; - padding: 0 20px; - } - - .nav-title { - color: rgba(255, 255, 255, 0.7); - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 12px; - padding: 0 16px; - } - - .nav-item { - margin-bottom: 4px; + .nav-text, + .nav-title, + .nav-badge, + .nav-arrow { + display: none; } .nav-link { - display: flex; - align-items: center; - color: rgba(255, 255, 255, 0.85); - padding: 12px 16px; - text-decoration: none; - border-radius: 6px; - transition: all 0.2s ease; - position: relative; - overflow: hidden; - } - - .nav-link:hover { - background-color: rgba(255, 255, 255, 0.1); - color: white; - transform: translateX(4px); - } - - .nav-link.active { - background-color: rgba(255, 255, 255, 0.15); - color: white; - font-weight: 500; - box-shadow: inset 3px 0 0 white; + justify-content: center; + padding: 16px; } .nav-link i { - width: 24px; - font-size: 18px; - margin-right: 12px; - text-align: center; - } - - .nav-text { - flex: 1; - font-size: 14px; - } - - .nav-badge { - background-color: var(--office-accent); - color: white; - font-size: 11px; - padding: 2px 6px; - border-radius: 10px; - margin-left: 8px; - } - - .nav-arrow { - font-size: 12px; - opacity: 0.7; - transition: transform 0.2s ease; - } - - .nav-link[aria-expanded="true"] .nav-arrow { - transform: rotate(90deg); - } - - .nav-submenu { - background-color: rgba(0, 0, 0, 0.1); - border-radius: 6px; - margin-top: 4px; - padding: 8px 0; + margin-right: 0; + font-size: 20px; } .nav-submenu .nav-link { - padding: 8px 16px 8px 52px; - font-size: 13px; + padding: 12px; + justify-content: center; } .nav-submenu .nav-link::before { - content: '•'; - position: absolute; - left: 36px; - opacity: 0.6; + display: none; + } + + .context-panel { + width: 100%; + right: -100%; + } +} + +@media (max-width: 768px) { + .app-sidebar { + transform: translateX(-100%); + width: var(--sidebar-width); + } + + .app-sidebar.open { + transform: translateX(0); } - /* Main Content Area */ .app-main { - margin-left: var(--sidebar-width); - padding-top: var(--header-height); - min-height: 100vh; - transition: margin-left var(--transition-speed) ease; - background-color: var(--office-light); + margin-left: 0; + } + + .header-content { + padding: 0 16px; } .content-area { - padding: 24px; - max-width: 1400px; - margin: 0 auto; - } - - /* Breadcrumb/Navigation Bar */ - .nav-bar { - background-color: white; - border-radius: 8px; - padding: 16px 24px; - margin-bottom: 24px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - border: 1px solid var(--office-border); - } - - .nav-tabs { - display: flex; - gap: 8px; - overflow-x: auto; - padding-bottom: 4px; - } - - .nav-tab { - display: inline-flex; - align-items: center; - padding: 8px 16px; - background-color: transparent; - color: var(--office-dark); - text-decoration: none; - border-radius: 4px; - font-size: 14px; - font-weight: 500; - white-space: nowrap; - transition: all 0.2s ease; - border: 1px solid transparent; - } - - .nav-tab:hover { - background-color: var(--office-hover); - color: var(--office-secondary); - } - - .nav-tab.active { - background-color: var(--office-secondary); - color: white; - border-color: var(--office-secondary); - } - - .nav-tab i { - margin-right: 8px; - font-size: 16px; - } - - /* Content Cards */ - .content-card { - background-color: white; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); - border: 1px solid var(--office-border); - margin-bottom: 24px; - overflow: hidden; - } - - .card-header { - background-color: #fafafa; - border-bottom: 1px solid var(--office-border); - padding: 20px 24px; - font-weight: 600; - color: var(--office-dark); - font-size: 18px; - display: flex; - align-items: center; - justify-content: space-between; - } - - .card-body { - padding: 24px; - } - - /* Header Controls */ - .header-controls { - display: flex; - align-items: center; - gap: 16px; - } - - .header-btn { - background: transparent; - border: none; - color: #313c4c; - padding: 8px; - border-radius: 4px; - cursor: pointer; - position: relative; - transition: background-color 0.2s ease; - } - - .header-btn:hover { - background-color: rgba(255, 255, 255, 0.1); - } - - .notification-badge { - position: absolute; - top: 2px; - right: 2px; - background-color: var(--office-accent); - color: white; - font-size: 10px; - font-weight: 600; - width: 18px; - height: 18px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - } - - .user-menu { - display: flex; - align-items: center; - gap: 12px; - cursor: pointer; - padding: 4px 12px; - border-radius: 6px; - transition: background-color 0.2s ease; - } - - .user-menu:hover { - background-color: rgba(255, 255, 255, 0.1); - } - - .user-avatar { - width: 36px; - height: 36px; - border-radius: 50%; - background: linear-gradient(135deg, var(--office-primary), #ff6b35); - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 14px; + padding: 16px; } .user-info { - display: flex; - flex-direction: column; + display: none; } - .user-name { - color: #313c4c; - font-weight: 500; - font-size: 14px; - } - - .user-role { - color: #313c4c; - font-size: 12px; - } - - /* Context Panel */ - .context-panel { - position: fixed; - right: -380px; - top: var(--header-height); - bottom: 0; - width: 380px; - background-color: white; - box-shadow: -2px 0 12px rgba(0, 0, 0, 0.1); - z-index: 1015; - transition: right 0.3s ease; - display: flex; - flex-direction: column; - border-left: 1px solid var(--office-border); - } - - .context-panel.open { - right: 0; - } - - .context-header { - background: linear-gradient(135deg, var(--office-primary), #ee6a49); - color: white; - padding: 20px 24px; - display: flex; - align-items: center; - justify-content: space-between; - } - - .context-title { - font-size: 18px; - font-weight: 600; - display: flex; - align-items: center; - gap: 10px; - } - - .context-close { - background: transparent; - border: none; - color: white; - font-size: 20px; - cursor: pointer; - padding: 4px; - border-radius: 4px; - transition: background-color 0.2s ease; - } - - .context-close:hover { - background-color: rgba(255, 255, 255, 0.1); - } - - .context-body { - flex: 1; - padding: 24px; - overflow-y: auto; - } - - .context-section { - margin-bottom: 28px; - } - - .section-title { - font-size: 14px; - font-weight: 600; - color: var(--office-secondary); - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; - display: flex; - align-items: center; - gap: 8px; - } - - .section-title i { - font-size: 16px; - } - - .info-box { - background-color: #fafafa; - border: 1px solid var(--office-border); - border-radius: 6px; - padding: 16px; - margin-bottom: 12px; - } - - .info-label { - font-size: 12px; - color: #666; - margin-bottom: 4px; - } - - .info-value { - font-size: 15px; - font-weight: 500; - color: var(--office-dark); - line-height: 1.4; - } - - .action-btn { - width: 100%; - padding: 12px; - background-color: white; - border: 1px solid var(--office-border); - border-radius: 6px; - color: var(--office-dark); - font-weight: 500; - text-align: left; - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - transition: all 0.2s ease; - margin-bottom: 8px; - } - - .action-btn:hover { - background-color: var(--office-hover); - border-color: var(--office-secondary); - color: var(--office-secondary); - transform: translateX(4px); - } - - .action-btn i { - width: 20px; - text-align: center; - font-size: 18px; - } - - .photo-container { - text-align: center; - padding: 16px; - background-color: #fafafa; - border-radius: 8px; - border: 1px solid var(--office-border); - } - - .patient-photo { - width: 150px; - height: 150px; - border-radius: 8px; - object-fit: cover; - border: 3px solid white; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - cursor: pointer; - transition: transform 0.3s ease; - } - - .patient-photo:hover { - transform: scale(1.05); - } - - /* Toggle Button */ - .context-toggle { - position: fixed; - right: 24px; - bottom: 24px; - width: 56px; - height: 56px; - border-radius: 50%; - background: linear-gradient(135deg, var(--office-primary), #ff6b35); - color: white; - border: none; - box-shadow: 0 4px 12px rgba(183, 71, 42, 0.3); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - z-index: 1010; - transition: all 0.3s ease; - } - - .context-toggle:hover { - transform: translateY(-2px); - box-shadow: 0 6px 16px rgba(183, 71, 42, 0.4); - } - - /* Test Mode Indicator */ - .test-indicator { - background: linear-gradient(45deg, #ffd700, #ffed4e); - color: #8a6d3b; - padding: 4px 12px; - border-radius: 4px; - font-size: 12px; - font-weight: 600; - display: inline-flex; - align-items: center; - gap: 6px; - margin-left: 12px; - animation: pulse 2s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - /* Language Selector */ - .language-selector { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - background-color: rgba(255, 255, 255, 0.1); - border-radius: 4px; - cursor: pointer; - transition: background-color 0.2s ease; - color: #313c4c; - } - - .language-selector:hover { - background-color: rgba(255, 255, 255, 0.2); - } - - .language-flag { - width: 24px; - height: 16px; - object-fit: cover; - border-radius: 2px; - } - - /* Modal Customization */ - .modal-office .modal-header { - background: linear-gradient(135deg, var(--office-secondary), #1e4a8b); - color: white; - border-bottom: none; - } - - .modal-office .modal-title { - font-weight: 600; - } - - /* Responsive Design */ - @media (max-width: 1200px) { - .app-sidebar { - width: var(--sidebar-collapsed); - } - - .app-main { - margin-left: var(--sidebar-collapsed); - } - - .nav-text, - .nav-title, - .nav-badge, - .nav-arrow { - display: none; - } - - .nav-link { - justify-content: center; - padding: 16px; - } - - .nav-link i { - margin-right: 0; - font-size: 20px; - } - - .nav-submenu .nav-link { - padding: 12px; - justify-content: center; - } - - .nav-submenu .nav-link::before { - display: none; - } - - .context-panel { - width: 100%; - right: -100%; - } - } - - @media (max-width: 768px) { - .app-sidebar { - transform: translateX(-100%); - width: var(--sidebar-width); - } - - .app-sidebar.open { - transform: translateX(0); - } - - .app-main { - margin-left: 0; - } - - .header-content { - padding: 0 16px; - } - - .content-area { - padding: 16px; - } - - .user-info { - display: none; - } - - .nav-text, - .nav-title, - .nav-badge, - .nav-arrow { - display: block; - } - } - - /* Scrollbar Styling */ - .app-sidebar::-webkit-scrollbar, - .context-body::-webkit-scrollbar { - width: 6px; - } - - .app-sidebar::-webkit-scrollbar-track, - .context-body::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); - border-radius: 3px; - } - - .app-sidebar::-webkit-scrollbar-thumb, - .context-body::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 3px; - } - - .app-sidebar::-webkit-scrollbar-thumb:hover, - .context-body::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.4); + .nav-text, + .nav-title, + .nav-badge, + .nav-arrow { + display: block; } +} + +/* ============================================ + SCROLLBAR STYLING + ============================================ */ +.app-sidebar::-webkit-scrollbar, +.context-body::-webkit-scrollbar { + width: 6px; +} + +.app-sidebar::-webkit-scrollbar-track, +.context-body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; +} + +.app-sidebar::-webkit-scrollbar-thumb, +.context-body::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 3px; +} + +.app-sidebar::-webkit-scrollbar-thumb:hover, +.context-body::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.4); +} \ No newline at end of file diff --git a/Bootstrap_new/css/ux_enhancements.css b/Bootstrap_new/css/ux_enhancements.css new file mode 100644 index 0000000..e04f7d5 --- /dev/null +++ b/Bootstrap_new/css/ux_enhancements.css @@ -0,0 +1,833 @@ +/* ============================================ + ENHANCEMENTS UX POUR PORTAIL INTER SANTÉ + ============================================ */ + +:root { + /* Variables UX avancées */ + --ux-transition-smooth: cubic-bezier(0.4, 0, 0.2, 1); + --ux-shadow-soft: 0 2px 15px rgba(0, 0, 0, 0.08); + --ux-shadow-medium: 0 5px 20px rgba(0, 0, 0, 0.12); + --ux-shadow-hard: 0 10px 30px rgba(0, 0, 0, 0.15); + --ux-border-radius-sm: 8px; + --ux-border-radius-md: 12px; + --ux-border-radius-lg: 20px; + + /* Variables d'animation */ + --animation-duration-fast: 150ms; + --animation-duration-normal: 300ms; + --animation-duration-slow: 500ms; +} + +/* ============================================ + ENHANCED SIDEBAR UX + ============================================ */ +.app-sidebar { + --sidebar-hover-glow: 0 0 20px rgba(255, 255, 255, 0.1); +} + +.nav-item { + position: relative; +} + +.nav-link { + position: relative; + overflow: visible; + transition: all var(--animation-duration-normal) var(--ux-transition-smooth); + border-left: 3px solid transparent; + will-change: transform, background-color, border-color; +} + +.nav-link:hover { + background: linear-gradient(90deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 100%); + transform: translateX(8px); + border-left-color: rgba(255, 255, 255, 0.5); + box-shadow: var(--sidebar-hover-glow); +} + +.nav-link.active { + background: linear-gradient(90deg, + rgba(255, 255, 255, 0.2) 0%, + rgba(255, 255, 255, 0.1) 100%); + border-left-color: #ffffff; + box-shadow: inset 0 0 30px rgba(255, 255, 255, 0.1), + 0 0 20px rgba(255, 255, 255, 0.15); + font-weight: 600; + letter-spacing: 0.3px; +} + +/* Indicateur visuel pour lien actif */ +.nav-link.active::before { + content: ''; + position: absolute; + left: -3px; + top: 50%; + transform: translateY(-50%); + width: 6px; + height: 6px; + background-color: white; + border-radius: 50%; + box-shadow: 0 0 10px white; + animation: pulse-active 2s infinite; +} + +@keyframes pulse-active { + 0%, 100% { + opacity: 1; + transform: translateY(-50%) scale(1); + } + 50% { + opacity: 0.7; + transform: translateY(-50%) scale(1.2); + } +} + +/* Sous-menus améliorés */ +.nav-submenu { + background: linear-gradient(180deg, + rgba(0, 0, 0, 0.15) 0%, + rgba(0, 0, 0, 0.1) 100%); + border-radius: var(--ux-border-radius-sm); + margin: 6px 12px; + border-left: 2px solid rgba(255, 255, 255, 0.2); + transition: all 0.4s var(--ux-transition-smooth); + max-height: 0; + overflow: hidden; +} + +.nav-submenu.show { + max-height: 500px; + padding: 8px 0; + margin-top: 8px; + margin-bottom: 12px; + animation: slideDown 0.4s var(--ux-transition-smooth); +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + max-height: 0; + } + to { + opacity: 1; + transform: translateY(0); + max-height: 500px; + } +} + +/* ============================================ + CONTEXT BUTTON - QUILLBOT STYLE + ============================================ */ +.context-toggle { + position: fixed; + right: 30px; + bottom: 30px; + width: 60px; + height: 60px; + border-radius: 50%; + background: linear-gradient(135deg, #b7472a 0%, #ff6b35 100%); + color: white; + border: 3px solid white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + z-index: 1050; + transition: all 0.5s var(--ux-transition-smooth); + opacity: 0.2; + transform: scale(0.8); + box-shadow: + 0 4px 25px rgba(183, 71, 42, 0.15), + inset 0 0 20px rgba(255, 255, 255, 0.2); + backdrop-filter: blur(10px); + will-change: transform, opacity, box-shadow; +} + +/* Animation d'attente */ +.context-toggle::after { + content: ''; + position: absolute; + width: 72px; + height: 72px; + border: 2px solid rgba(183, 71, 42, 0.3); + border-radius: 50%; + animation: ripple 3s infinite; +} + +@keyframes ripple { + 0% { + transform: scale(0.8); + opacity: 1; + } + 100% { + transform: scale(1.2); + opacity: 0; + } +} + +/* État au survol et activation */ +.context-toggle:hover, +.context-toggle.active, +body:has(.proximity-hover-area:hover) .context-toggle { + opacity: 1 !important; + transform: scale(1.1) !important; + box-shadow: + 0 8px 35px rgba(183, 71, 42, 0.4), + 0 0 30px rgba(255, 107, 53, 0.3), + inset 0 0 25px rgba(255, 255, 255, 0.3); + animation: bounce 0.5s var(--ux-transition-smooth); +} + +@keyframes bounce { + 0%, 100% { transform: scale(1.1); } + 50% { transform: scale(1.15); } +} + +/* Rotation quand le panel est ouvert */ +.context-panel.open ~ .context-toggle { + transform: rotate(180deg) scale(1.1); + background: linear-gradient(135deg, #2b579a 0%, #1e4a8b 100%); + right: 410px; +} + +/* Zone de détection de proximité invisible */ +.proximity-hover-area { + position: fixed; + right: 0; + bottom: 0; + width: 200px; + height: 200px; + z-index: 1049; + pointer-events: none; +} + +/* Feedback visuel pour la zone */ +.proximity-hover-area::before { + content: ''; + position: absolute; + right: 30px; + bottom: 30px; + width: 100px; + height: 100px; + background: radial-gradient(circle, rgba(183, 71, 42, 0.1) 0%, transparent 70%); + border-radius: 50%; + opacity: 0; + transition: opacity var(--animation-duration-normal); +} + +.proximity-hover-area:hover::before { + opacity: 1; +} + +/* ============================================ + ENHANCED CONTEXT PANEL + ============================================ */ +.context-panel { + transition: right var(--animation-duration-slow) var(--ux-transition-smooth); + box-shadow: + -5px 0 30px rgba(0, 0, 0, 0.15), + 0 0 0 1px rgba(255, 255, 255, 0.1) inset; + border-left: 3px solid var(--office-primary); + will-change: right; +} + +.context-panel.open { + animation: slideInRight 0.5s var(--ux-transition-smooth); +} + +@keyframes slideInRight { + from { + transform: translateX(100px); + opacity: 0.8; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +.context-header { + background: linear-gradient(135deg, var(--office-primary) 0%, #d9534f 100%); + padding: 25px 30px; + border-bottom: 3px solid rgba(255, 255, 255, 0.2); +} + +.context-title { + font-size: 20px; + font-weight: 700; +} + +.context-close { + transition: all var(--animation-duration-normal); + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; +} + +.context-close:hover { + background: rgba(255, 255, 255, 0.2); + transform: rotate(90deg); +} + +/* Sections avec effet de profondeur */ +.context-section { + background: white; + border-radius: var(--ux-border-radius-md); + padding: 20px; + margin-bottom: 25px; + box-shadow: var(--ux-shadow-soft); + border: 1px solid var(--office-border); + transition: all var(--animation-duration-normal); + position: relative; + overflow: hidden; +} + +.context-section:hover { + transform: translateY(-3px); + box-shadow: var(--ux-shadow-medium); + border-color: var(--office-secondary); +} + +.context-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + background: linear-gradient(to bottom, var(--office-primary), var(--office-secondary)); + opacity: 0.7; +} + +.section-title { + color: var(--office-secondary); + font-size: 15px; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 2px solid rgba(43, 87, 154, 0.1); + position: relative; +} + +.section-title::after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 50px; + height: 2px; + background: linear-gradient(90deg, var(--office-primary), var(--office-secondary)); +} + +/* Cards améliorées */ +.info-box { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: var(--ux-border-radius-sm); + padding: 18px; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.03); + transition: all var(--animation-duration-normal); +} + +.info-box:hover { + border-color: var(--office-secondary); + box-shadow: + inset 0 2px 8px rgba(43, 87, 154, 0.05), + 0 3px 10px rgba(0, 0, 0, 0.05); +} + +.info-value { + font-size: 16px; + font-weight: 600; + color: var(--office-dark); + line-height: 1.5; +} + +/* Boutons d'action améliorés */ +.action-btn { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border: 2px solid var(--office-border); + border-radius: var(--ux-border-radius-sm); + padding: 15px 20px; + margin-bottom: 12px; + transition: all var(--animation-duration-normal) var(--ux-transition-smooth); + position: relative; + overflow: hidden; + will-change: transform, border-color, background; +} + +.action-btn:hover { + transform: translateX(8px); + border-color: var(--office-secondary); + background: linear-gradient(135deg, #f0f7ff 0%, #e3eeff 100%); + box-shadow: + 5px 5px 15px rgba(43, 87, 154, 0.1), + inset 0 0 20px rgba(255, 255, 255, 0.5); +} + +.action-btn::before { + content: ''; + position: absolute; + left: -100%; + top: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, + transparent, + rgba(255, 255, 255, 0.4), + transparent); + transition: left 0.6s; +} + +.action-btn:hover::before { + left: 100%; +} + +/* Photo patient améliorée */ +.photo-container { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: var(--ux-border-radius-md); + padding: 25px; + border: 2px dashed var(--office-border); + transition: all var(--animation-duration-normal); +} + +.photo-container:hover { + border-color: var(--office-primary); + background: linear-gradient(135deg, #fff5f2 0%, #ffffff 100%); + transform: scale(1.02); +} + +.patient-photo { + width: 180px; + height: 180px; + border-radius: var(--ux-border-radius-md); + object-fit: cover; + border: 5px solid white; + box-shadow: + 0 10px 30px rgba(0, 0, 0, 0.15), + 0 0 0 1px rgba(0, 0, 0, 0.05); + transition: all 0.4s var(--ux-transition-smooth); + cursor: pointer; + will-change: transform, box-shadow; +} + +.patient-photo:hover { + transform: scale(1.08) rotate(2deg); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.2), + 0 0 0 3px rgba(183, 71, 42, 0.3); +} + +/* ============================================ + HEADER ENHANCEMENTS + ============================================ */ +.app-header { + backdrop-filter: blur(10px); + background: linear-gradient(135deg, + rgba(243, 242, 241, 0.95) 0%, + rgba(255, 255, 255, 0.98) 100%); + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: + 0 4px 20px rgba(0, 0, 0, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.8); +} + +.user-avatar { + background: linear-gradient(135deg, var(--office-primary) 0%, #ff8c69 100%); + box-shadow: + 0 4px 15px rgba(183, 71, 42, 0.3), + inset 0 0 20px rgba(255, 255, 255, 0.3); + transition: all var(--animation-duration-normal); + will-change: transform, box-shadow; +} + +.user-avatar:hover { + transform: scale(1.1) rotate(5deg); + box-shadow: + 0 6px 25px rgba(183, 71, 42, 0.4), + inset 0 0 25px rgba(255, 255, 255, 0.4); +} + +.notification-badge { + background: linear-gradient(135deg, #107c10 0%, #20a020 100%); + box-shadow: 0 3px 10px rgba(16, 124, 16, 0.4); + animation: pulse-notification 2s infinite; +} + +@keyframes pulse-notification { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.1); } +} + +/* ============================================ + MAIN CONTENT ENHANCEMENTS + ============================================ */ +.app-main { + background: linear-gradient(135deg, + #f3f2f1 0%, + #f8f7f6 30%, + #fefefe 100%); +} + +.content-area { + animation: fadeIn 0.6s var(--ux-transition-smooth); +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.nav-bar { + background: linear-gradient(135deg, #ffffff 0%, #fafafa 100%); + border-radius: var(--ux-border-radius-md); + box-shadow: var(--ux-shadow-soft); + border: 1px solid rgba(0, 0, 0, 0.06); + backdrop-filter: blur(10px); +} + +.nav-tab { + transition: all var(--animation-duration-normal); + position: relative; + overflow: hidden; + will-change: transform, box-shadow; +} + +.nav-tab:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(43, 87, 154, 0.1); +} + +.nav-tab.active { + background: linear-gradient(135deg, var(--office-secondary) 0%, #3a6bc0 100%); + box-shadow: + 0 5px 20px rgba(43, 87, 154, 0.3), + inset 0 0 20px rgba(255, 255, 255, 0.2); + transform: translateY(-2px); + animation: tab-active-pulse 3s infinite; +} + +@keyframes tab-active-pulse { + 0%, 100% { + box-shadow: 0 5px 20px rgba(43, 87, 154, 0.3); + } + 50% { + box-shadow: 0 5px 25px rgba(43, 87, 154, 0.5); + } +} + +.content-card { + background: linear-gradient(135deg, #ffffff 0%, #fcfcfc 100%); + border-radius: var(--ux-border-radius-md); + box-shadow: var(--ux-shadow-soft); + border: 1px solid rgba(0, 0, 0, 0.05); + transition: all 0.4s; + will-change: transform, box-shadow; +} + +.content-card:hover { + transform: translateY(-3px); + box-shadow: var(--ux-shadow-medium); + border-color: rgba(43, 87, 154, 0.1); +} + +/* ============================================ + LOADING STATES + ============================================ */ +.loading-placeholder { + background: linear-gradient(90deg, + #f0f0f0 25%, + #e0e0e0 50%, + #f0f0f0 75%); + background-size: 200% 100%; + animation: loading 1.5s infinite; + border-radius: var(--ux-border-radius-sm); +} + +@keyframes loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* ============================================ + ACCESSIBILITY ENHANCEMENTS + ============================================ */ + +/* Navigation au clavier */ +body.keyboard-navigation .nav-link:focus, +body.keyboard-navigation .action-btn:focus, +body.keyboard-navigation .context-toggle:focus { + outline: 3px solid rgba(43, 87, 154, 0.5); + outline-offset: 3px; + box-shadow: 0 0 0 6px rgba(43, 87, 154, 0.1); +} + +/* Lien skip pour accessibilité */ +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: var(--office-primary); + color: white; + padding: 8px 16px; + border-radius: 0 0 4px 0; + text-decoration: none; + font-weight: 600; + z-index: 9999; + transition: top 0.3s; +} + +.skip-link:focus { + top: 0; + outline: 3px solid white; + outline-offset: -3px; +} + +/* High contrast mode */ +@media (prefers-contrast: high) { + .nav-link.active { + border-left: 4px solid white; + background-color: rgba(255, 255, 255, 0.3); + } + + .context-section { + border: 2px solid var(--office-dark); + } + + .action-btn { + border: 2px solid currentColor; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .context-toggle::after { + animation: none; + } +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .app-header { + background: rgba(30, 30, 30, 0.95); + border-bottom-color: rgba(255, 255, 255, 0.1); + } + + .content-card { + background: #2a2a2a; + border-color: #404040; + color: #e0e0e0; + } + + .nav-bar { + background: #2a2a2a; + border-color: #404040; + } + + .info-box { + background: #333; + border-color: #444; + } + + .action-btn { + background: #333; + border-color: #444; + color: #e0e0e0; + } +} + +/* ============================================ + RESPONSIVE ENHANCEMENTS + ============================================ */ +@media (max-width: 1200px) { + .app-sidebar:hover { + width: var(--sidebar-width); + } + + .app-sidebar:hover .nav-text, + .app-sidebar:hover .nav-arrow { + display: block; + animation: fadeIn 0.3s; + } + + .context-panel { + width: 90%; + } + + .context-panel.open ~ .context-toggle { + right: calc(90% + 20px); + } +} + +@media (max-width: 768px) { + .context-toggle { + width: 50px; + height: 50px; + font-size: 20px; + right: 20px; + bottom: 20px; + } + + .context-toggle::after { + width: 60px; + height: 60px; + } + + .context-panel { + width: 100%; + } + + .context-panel.open ~ .context-toggle { + right: calc(100% - 70px); + } + + .nav-bar { + padding: 12px 15px; + margin: 10px; + } + + .nav-tabs { + padding: 5px 0; + } + + .nav-tab { + padding: 8px 12px; + font-size: 13px; + } + + /* Masquer certains éléments sur mobile */ + .user-info .user-role { + display: none; + } + + .test-indicator span { + display: none; + } + + .test-indicator { + padding: 4px 8px; + } +} + +@media (max-width: 480px) { + .header-content { + padding: 0 12px; + } + + .app-logo span { + font-size: 16px; + } + + .user-name { + display: none; + } + + .context-body { + padding: 16px; + } + + .context-section { + padding: 15px; + } + + .patient-photo { + width: 140px; + height: 140px; + } +} + +/* ============================================ + UTILITY CLASSES + ============================================ */ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.text-balance { + text-wrap: balance; +} + +.text-pretty { + text-wrap: pretty; +} + +/* Connection warning */ +.connection-warning { + position: fixed; + top: 70px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + max-width: 90%; + animation: slideDown 0.3s ease-out; +} + +/* Animation for loaded images */ +img.loaded { + animation: fadeInUp 0.5s ease-out; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Print styles */ +@media print { + .app-sidebar, + .app-header, + .context-panel, + .context-toggle, + .nav-bar { + display: none !important; + } + + .app-main { + margin-left: 0; + padding-top: 0; + } + + .content-card { + box-shadow: none; + border: 1px solid #ddd; + } + + body { + background: white; + color: black; + } +} \ No newline at end of file diff --git a/Bootstrap_new/js/ux-manager.js b/Bootstrap_new/js/ux-manager.js new file mode 100644 index 0000000..bb653ed --- /dev/null +++ b/Bootstrap_new/js/ux-manager.js @@ -0,0 +1,1377 @@ +/** + * UX Manager pour Portail Inter Santé + * Gestion avancée de l'expérience utilisateur + * Version: 2025.12.20.01 + */ + +// Configuration globale +const UXConfig = { + debug: false, + transitionSpeed: 300, + proximityRadius: 150, + notificationPollInterval: 30000, + saveState: true +}; + +// Module principal UX Manager +class UXManager { + constructor() { + this.contextPanelOpen = false; + this.sidebarOpen = false; + this.activeMenuId = window.appConfig?.activeParentId || null; + this.proximityDetectionActive = true; + + this.modules = { + navigation: null, + context: null, + notifications: null, + accessibility: null, + performance: null + }; + + this.init(); + } + + /** + * Initialisation du système UX + */ + init() { + try { + console.log('🚀 UX Manager initialisation...'); + + // Initialiser les modules + this.modules.navigation = new NavigationManager(); + this.modules.context = new ContextPanelManager(); + this.modules.notifications = new NotificationManager(); + this.modules.accessibility = new AccessibilityManager(); + this.modules.performance = new PerformanceManager(); + + // Restaurer l'état précédent + this.restoreState(); + + // Configurer les événements globaux + this.setupGlobalEvents(); + + // Lancer les pollings + this.startPollings(); + + console.log('✅ UX Manager prêt'); + + } catch (error) { + console.error('❌ Erreur initialisation UX Manager:', error); + } + } + + /** + * Restaurer l'état depuis localStorage + */ + restoreState() { + if (!UXConfig.saveState) return; + + try { + const savedPanelState = localStorage.getItem('contextPanelOpen'); + if (savedPanelState === 'true') { + // Ouvrir avec délai pour meilleure UX + setTimeout(() => { + this.modules.context.togglePanel(); + }, 1000); + } + + const savedMenuState = localStorage.getItem('expandedMenus'); + if (savedMenuState) { + this.modules.navigation.restoreMenuState(JSON.parse(savedMenuState)); + } + + } catch (e) { + if (UXConfig.debug) console.warn('Impossible de restaurer l\'état:', e); + } + } + + /** + * Configurer les événements globaux + */ + setupGlobalEvents() { + // Gestionnaire de redimensionnement avec debounce + let resizeTimeout; + window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + this.handleResize(); + }, 250); + }); + + // Navigation au clavier + document.addEventListener('keydown', (e) => { + this.handleKeyboardNavigation(e); + }); + + // Prévenir la fermeture accidentelle avec données non sauvegardées + window.addEventListener('beforeunload', (e) => { + // Ici vous pouvez ajouter une logique pour vérifier les données non sauvegardées + // if (this.hasUnsavedChanges()) { + // e.preventDefault(); + // e.returnValue = ''; + // } + }); + } + + /** + * Gérer le redimensionnement de la fenêtre + */ + handleResize() { + const isMobile = window.innerWidth < 768; + document.body.classList.toggle('mobile-view', isMobile); + + // Ajuster le comportement du sidebar sur mobile + if (!isMobile && this.sidebarOpen) { + document.getElementById('sidebar')?.classList.remove('open'); + this.sidebarOpen = false; + } + } + + /** + * Gérer la navigation au clavier + */ + handleKeyboardNavigation(e) { + // Marquer la navigation clavier pour le CSS + if (e.key === 'Tab') { + document.body.classList.add('keyboard-navigation'); + } + + // Échap pour fermer les modales + if (e.key === 'Escape') { + if (this.contextPanelOpen) { + this.modules.context.togglePanel(); + } + + // Fermer toutes les modales Bootstrap ouvertes + const openModals = document.querySelectorAll('.modal.show'); + openModals.forEach(modal => { + const bsModal = bootstrap.Modal.getInstance(modal); + if (bsModal) bsModal.hide(); + }); + } + + // Navigation dans les menus avec flèches + if (e.target.closest('.sidebar-nav')) { + this.modules.navigation.handleKeyboardMenuNavigation(e); + } + } + + /** + * Démarrer les pollings automatiques + */ + startPollings() { + // Polling des notifications + setInterval(() => { + this.modules.notifications.pollNotifications(); + }, UXConfig.notificationPollInterval); + + // Premier poll immédiat + setTimeout(() => { + this.modules.notifications.pollNotifications(); + }, 2000); + } + + /** + * Basculer le panneau contexte + */ + toggleContextPanel() { + return this.modules.context.togglePanel(); + } + + /** + * Afficher la modale photo + */ + openPhotoModal() { + return this.modules.context.openPhotoModal(); + } + + /** + * Basculer le sidebar mobile + */ + toggleSidebar() { + const sidebar = document.getElementById('sidebar'); + if (!sidebar) return; + + this.sidebarOpen = !this.sidebarOpen; + sidebar.classList.toggle('open'); + + const toggleBtn = document.querySelector('.sidebar-toggle'); + if (toggleBtn) { + toggleBtn.setAttribute('aria-expanded', this.sidebarOpen); + } + + // Fermer le sidebar si on clique à l'extérieur (mobile uniquement) + if (this.sidebarOpen && window.innerWidth < 768) { + setTimeout(() => { + document.addEventListener('click', this.closeSidebarOnClickOutside.bind(this), { once: true }); + }, 100); + } + } + + /** + * Fermer le sidebar en cliquant à l'extérieur + */ + closeSidebarOnClickOutside(e) { + const sidebar = document.getElementById('sidebar'); + const toggleBtn = document.querySelector('.sidebar-toggle'); + + if (!sidebar?.contains(e.target) && !toggleBtn?.contains(e.target)) { + sidebar.classList.remove('open'); + this.sidebarOpen = false; + if (toggleBtn) toggleBtn.setAttribute('aria-expanded', 'false'); + } + } +} + +// ============================================ +// MODULE: Gestionnaire de Navigation +// ============================================ + +class NavigationManager { + constructor() { + this.expandedMenus = new Set(); + this.activeMenuId = window.appConfig?.activeParentId || null; + this.init(); + } + + init() { + this.setupMenuAutoClose(); + this.highlightActiveMenu(); + this.setupMenuInteractions(); + } + + /** + * Fermer automatiquement les menus inactifs + */ + setupMenuAutoClose() { + // Fermer tous les sous-menus sauf celui actif + document.querySelectorAll('.nav-submenu.show').forEach(submenu => { + const menuId = submenu.id; + const parentLink = document.querySelector(`[href="#${menuId}"]`); + + if (!parentLink?.classList.contains('active')) { + this.collapseMenu(submenu.id); + } + }); + + // Fermer les menus quand on clique ailleurs + document.addEventListener('click', (e) => { + if (!e.target.closest('.nav-item')) { + this.collapseAllExceptActive(); + } + }); + } + + /** + * Mettre en évidence le menu actif + */ + highlightActiveMenu() { + if (!this.activeMenuId) return; + + const activeLink = document.querySelector(`[href="#submenu${this.activeMenuId}"]`); + if (activeLink) { + activeLink.classList.add('active'); + this.expandMenu(`submenu${this.activeMenuId}`); + } + + // Mettre en évidence les enfants actifs + document.querySelectorAll('.nav-submenu .nav-link').forEach(link => { + const href = link.getAttribute('href'); + if (href && href.includes(window.appConfig.activeLink)) { + link.classList.add('active'); + link.setAttribute('aria-current', 'page'); + } + }); + } + + /** + * Configurer les interactions des menus + */ + setupMenuInteractions() { + document.querySelectorAll('.nav-link[data-bs-toggle="collapse"]').forEach(link => { + link.addEventListener('click', (e) => { + const menuId = link.getAttribute('href').substring(1); + + // Si on clique sur un menu déjà actif, ne rien faire + if (link.classList.contains('active')) return; + + // Fermer tous les autres menus + this.collapseAllExcept(menuId); + + // Sauvegarder l'état + this.saveMenuState(); + }); + }); + } + + /** + * Développer un menu + */ + expandMenu(menuId) { + const submenu = document.getElementById(menuId); + const parentLink = document.querySelector(`[href="#${menuId}"]`); + + if (submenu && parentLink) { + submenu.classList.add('show'); + parentLink.setAttribute('aria-expanded', 'true'); + parentLink.classList.add('active'); + + const arrow = parentLink.querySelector('.nav-arrow'); + if (arrow) arrow.style.transform = 'rotate(90deg)'; + + this.expandedMenus.add(menuId); + } + } + + /** + * Réduire un menu + */ + collapseMenu(menuId) { + const submenu = document.getElementById(menuId); + const parentLink = document.querySelector(`[href="#${menuId}"]`); + + if (submenu && parentLink) { + submenu.classList.remove('show'); + parentLink.setAttribute('aria-expanded', 'false'); + parentLink.classList.remove('active'); + + const arrow = parentLink.querySelector('.nav-arrow'); + if (arrow) arrow.style.transform = 'rotate(0deg)'; + + this.expandedMenus.delete(menuId); + } + } + + /** + * Réduire tous les menus sauf un + */ + collapseAllExcept(exceptMenuId) { + document.querySelectorAll('.nav-submenu.show').forEach(submenu => { + if (submenu.id !== exceptMenuId) { + this.collapseMenu(submenu.id); + } + }); + } + + /** + * Réduire tous les menus sauf l'actif + */ + collapseAllExceptActive() { + const activeMenuId = `submenu${this.activeMenuId}`; + this.collapseAllExcept(activeMenuId); + } + + /** + * Gérer la navigation clavier dans les menus + */ + handleKeyboardMenuNavigation(e) { + const currentItem = e.target.closest('.nav-link'); + if (!currentItem) return; + + const items = Array.from(document.querySelectorAll('.nav-link')); + const currentIndex = items.indexOf(currentItem); + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + if (currentIndex < items.length - 1) { + items[currentIndex + 1].focus(); + } + break; + + case 'ArrowUp': + e.preventDefault(); + if (currentIndex > 0) { + items[currentIndex - 1].focus(); + } + break; + + case 'ArrowRight': + e.preventDefault(); + if (currentItem.hasAttribute('data-bs-toggle')) { + const menuId = currentItem.getAttribute('href').substring(1); + this.expandMenu(menuId); + } + break; + + case 'ArrowLeft': + e.preventDefault(); + if (currentItem.hasAttribute('data-bs-toggle')) { + const menuId = currentItem.getAttribute('href').substring(1); + this.collapseMenu(menuId); + } + break; + + case 'Enter': + case ' ': + e.preventDefault(); + if (currentItem.hasAttribute('data-bs-toggle')) { + const menuId = currentItem.getAttribute('href').substring(1); + if (this.expandedMenus.has(menuId)) { + this.collapseMenu(menuId); + } else { + this.expandMenu(menuId); + } + } else if (currentItem.href) { + window.location.href = currentItem.href; + } + break; + } + } + + /** + * Sauvegarder l'état des menus + */ + saveMenuState() { + if (!UXConfig.saveState) return; + + try { + const state = Array.from(this.expandedMenus); + localStorage.setItem('expandedMenus', JSON.stringify(state)); + } catch (e) { + if (UXConfig.debug) console.warn('Impossible de sauvegarder l\'état des menus:', e); + } + } + + /** + * Restaurer l'état des menus + */ + restoreMenuState(state) { + if (!Array.isArray(state)) return; + + state.forEach(menuId => { + this.expandMenu(menuId); + }); + } +} + +// ============================================ +// MODULE: Gestionnaire du Panneau Contexte +// ============================================ + +class ContextPanelManager { + constructor() { + this.panel = document.getElementById('contextPanel'); + this.toggleButton = document.querySelector('.context-toggle'); + this.proximityArea = document.querySelector('.proximity-hover-area'); + this.isOpen = false; + + this.init(); + } + + init() { + if (!this.panel || !this.toggleButton) { + console.warn('❌ Éléments du panneau contexte non trouvés'); + return; + } + + this.setupProximityDetection(); + this.setupAnimations(); + this.setupCloseBehavior(); + } + + /** + * Détection de proximité (style QuillBot) + */ + setupProximityDetection() { + if (!this.proximityArea) return; + + let proximityTimeout; + + // Détection de la souris + document.addEventListener('mousemove', (e) => { + if (this.isOpen) return; + + const rect = this.proximityArea.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + const distance = Math.sqrt( + Math.pow(e.clientX - centerX, 2) + + Math.pow(e.clientY - centerY, 2) + ); + + clearTimeout(proximityTimeout); + + if (distance < UXConfig.proximityRadius) { + // Souris proche - montrer le bouton + this.showButton(); + } else { + // Souris éloignée - cacher progressivement + proximityTimeout = setTimeout(() => { + this.hideButton(); + }, 1000); + } + }); + + // Toujours montrer le bouton quand le panel est ouvert + this.panel.addEventListener('transitionend', () => { + if (this.isOpen) { + this.showButton(); + } + }); + } + + /** + * Afficher le bouton contexte + */ + showButton() { + if (this.toggleButton) { + this.toggleButton.style.opacity = '0.8'; + this.toggleButton.classList.add('active'); + } + } + + /** + * Cacher le bouton contexte + */ + hideButton() { + if (this.toggleButton && !this.isOpen) { + this.toggleButton.style.opacity = '0.2'; + this.toggleButton.classList.remove('active'); + } + } + + /** + * Configurer les animations + */ + setupAnimations() { + // Observer pour animations d'entrée + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate__animated', 'animate__fadeInUp'); + } + }); + }, { threshold: 0.1 }); + + // Observer les sections du panneau + document.querySelectorAll('.context-section').forEach(section => { + observer.observe(section); + }); + } + + /** + * Configurer le comportement de fermeture + */ + setupCloseBehavior() { + // Fermer avec Échap + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.isOpen) { + this.togglePanel(); + } + }); + + // Fermer en cliquant à l'extérieur + document.addEventListener('click', (e) => { + if (this.isOpen && + !this.panel.contains(e.target) && + !this.toggleButton.contains(e.target)) { + this.togglePanel(); + } + }); + } + + /** + * Basculer l'état du panneau + */ + togglePanel() { + this.isOpen = !this.isOpen; + + if (this.isOpen) { + this.openPanel(); + } else { + this.closePanel(); + } + + // Mettre à jour les attributs ARIA + this.toggleButton.setAttribute('aria-expanded', this.isOpen); + + // Sauvegarder l'état + if (UXConfig.saveState) { + localStorage.setItem('contextPanelOpen', this.isOpen.toString()); + } + + return this.isOpen; + } + + /** + * Ouvrir le panneau + */ + openPanel() { + this.panel.classList.add('open'); + this.toggleButton.classList.add('panel-open'); + + // Focus sur le premier élément focusable + setTimeout(() => { + const firstFocusable = this.panel.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (firstFocusable) firstFocusable.focus(); + }, UXConfig.transitionSpeed); + + // Émettre un événement personnalisé + this.dispatchEvent('contextpanel:open'); + } + + /** + * Fermer le panneau + */ + closePanel() { + this.panel.classList.remove('open'); + this.toggleButton.classList.remove('panel-open'); + + // Retourner le focus au bouton toggle + this.toggleButton.focus(); + + // Cacher progressivement le bouton + setTimeout(() => { + if (!this.isOpen) this.hideButton(); + }, 1000); + + // Émettre un événement personnalisé + this.dispatchEvent('contextpanel:close'); + } + + /** + * Ouvrir la modale photo + */ + openPhotoModal() { + const modalElement = document.getElementById('photoModal'); + if (modalElement) { + const modal = new bootstrap.Modal(modalElement); + modal.show(); + } + } + + /** + * Émettre un événement personnalisé + */ + dispatchEvent(eventName, detail = {}) { + const event = new CustomEvent(eventName, { + detail: { ...detail, timestamp: Date.now() } + }); + document.dispatchEvent(event); + } +} + +// ============================================ +// MODULE: Gestionnaire de Notifications +// ============================================ + +class NotificationManager { + constructor() { + this.badge = document.getElementById('notificationCount'); + this.lastCount = 0; + this.unreadMessages = []; + + this.init(); + } + + init() { + this.setupBadgeAnimation(); + this.setupMessagePolling(); + } + + /** + * Configurer l'animation du badge + */ + setupBadgeAnimation() { + if (!this.badge) return; + + // Animation de pulse pour nouveaux messages + this.badge.addEventListener('animationend', () => { + this.badge.classList.remove('pulse'); + }); + } + + /** + * Configurer le polling des messages + */ + setupMessagePolling() { + // Poll initial + this.pollNotifications(); + + // Écouter les événements de nouvelle notification + document.addEventListener('newnotification', (e) => { + this.handleNewNotification(e.detail); + }); + } + + /** + * Poller les notifications + */ + async pollNotifications() { + try { + // Remplacer par votre appel API réel + const response = await this.fetchNotifications(); + const data = await response.json(); + + this.updateBadge(data.count); + + if (data.newMessages > 0) { + this.showNewNotificationAlert(data.newMessages); + this.unreadMessages = data.messages || []; + } + + } catch (error) { + if (UXConfig.debug) console.warn('Erreur polling notifications:', error); + } + } + + /** + * Récupérer les notifications + */ + async fetchNotifications() { + // Simuler une API - remplacer par votre endpoint réel + return { + ok: true, + json: async () => ({ + count: Math.floor(Math.random() * 10), + newMessages: Math.random() > 0.7 ? 1 : 0, + messages: [] + }) + }; + + // En production, utiliser : + // return fetch('api/notifications/count', { + // headers: { 'X-Requested-With': 'XMLHttpRequest' } + // }); + } + + /** + * Mettre à jour le badge + */ + updateBadge(count) { + if (!this.badge) return; + + const hasNew = count > this.lastCount; + this.lastCount = count; + + this.badge.textContent = count; + this.badge.style.display = count > 0 ? 'flex' : 'none'; + + if (hasNew && count > 0) { + this.badge.classList.add('pulse'); + + // Notification système si autorisée + if (Notification.permission === 'granted' && document.hidden) { + this.showSystemNotification(count); + } + } + } + + /** + * Afficher une alerte pour nouvelles notifications + */ + showNewNotificationAlert(count) { + // Créer un toast Bootstrap + const toastHTML = ` +