diff --git a/app/app_optimized.py b/app/app_optimized.py index 77a7270..7d18071 100644 --- a/app/app_optimized.py +++ b/app/app_optimized.py @@ -681,9 +681,23 @@ class TaskLogService: """Retourne le chemin du répertoire pour une date donnée (YYYY/MM/JJ)""" if dt is None: dt = datetime.now(timezone.utc) - year = dt.strftime("%Y") - month = dt.strftime("%m") - day = dt.strftime("%d") + + # IMPORTANT : on utilise le fuseau horaire local (America/Montreal) + # pour déterminer la date du dossier de log, afin que les tâches + # exécutées en soirée ne basculent pas au jour suivant à cause de l'UTC. + import pytz + local_tz = pytz.timezone("America/Montreal") + + if dt.tzinfo is None: + # Si la datetime est naïve, on considère qu'elle est déjà en heure locale + dt_local = local_tz.localize(dt) + else: + # Sinon on la convertit dans le fuseau local + dt_local = dt.astimezone(local_tz) + + year = dt_local.strftime("%Y") + month = dt_local.strftime("%m") + day = dt_local.strftime("%d") return self.base_dir / year / month / day def _generate_task_id(self) -> str: diff --git a/app/index.html b/app/index.html index 9103e36..126a866 100644 --- a/app/index.html +++ b/app/index.html @@ -1524,20 +1524,1120 @@ body.light-theme .schedule-calendar-day:hover { background: rgba(255, 255, 255, 0.9); } + + /* ======================================== + MOBILE-FIRST RESPONSIVE STYLES + ======================================== */ + + /* === MOBILE NAVIGATION === */ + .mobile-menu-btn { + display: none; + width: 44px; + height: 44px; + align-items: center; + justify-content: center; + border-radius: 8px; + background: rgba(55, 65, 81, 0.5); + border: none; + cursor: pointer; + transition: all 0.2s ease; + -webkit-tap-highlight-color: transparent; + } + + .mobile-menu-btn:hover, + .mobile-menu-btn:active { + background: rgba(124, 58, 237, 0.3); + } + + .mobile-menu-btn .hamburger-icon { + width: 20px; + height: 14px; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .mobile-menu-btn .hamburger-icon span { + display: block; + height: 2px; + background: white; + border-radius: 2px; + transition: all 0.3s ease; + } + + .mobile-menu-btn.active .hamburger-icon span:nth-child(1) { + transform: rotate(45deg) translate(4px, 4px); + } + + .mobile-menu-btn.active .hamburger-icon span:nth-child(2) { + opacity: 0; + } + + .mobile-menu-btn.active .hamburger-icon span:nth-child(3) { + transform: rotate(-45deg) translate(4px, -4px); + } + + /* Mobile Menu Overlay */ + .mobile-nav-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + z-index: 45; + opacity: 0; + transition: opacity 0.3s ease; + } + + .mobile-nav-overlay.active { + opacity: 1; + } + + /* Mobile Sidebar Navigation */ + .mobile-nav-sidebar { + display: none; + position: fixed; + top: 0; + left: 0; + width: 280px; + max-width: 85vw; + height: 100vh; + background: var(--secondary-bg); + border-right: 1px solid var(--border-color); + z-index: 55; + transform: translateX(-100%); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + .mobile-nav-sidebar.active { + transform: translateX(0); + } + + .mobile-nav-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border-color); + } + + .mobile-nav-links { + padding: 16px 0; + } + + .mobile-nav-links button, + .mobile-nav-links a { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 24px; + color: var(--secondary-text); + text-decoration: none; + transition: all 0.2s ease; + min-height: 48px; + width: 100%; + background: none; + border: none; + border-left: 3px solid transparent; + font-size: 1rem; + cursor: pointer; + text-align: left; + } + + .mobile-nav-links button:hover, + .mobile-nav-links button:active, + .mobile-nav-links a:hover, + .mobile-nav-links a:active { + background: rgba(124, 58, 237, 0.1); + color: var(--primary-text); + } + + .mobile-nav-links button.active, + .mobile-nav-links a.active { + background: rgba(124, 58, 237, 0.15); + color: var(--accent-color); + border-left: 3px solid var(--accent-color); + } + + .mobile-nav-links button i, + .mobile-nav-links a i { + width: 24px; + text-align: center; + } + + /* ===== MOBILE DROPDOWN FIX ===== */ + /* Force visibility when dropdown-open class is applied (overrides Tailwind) */ + .group.dropdown-open > .absolute, + .group.dropdown-open > div.absolute, + .group.dropdown-open > div[class*="absolute"] { + visibility: visible !important; + opacity: 1 !important; + pointer-events: auto !important; + display: block !important; + } + + /* On touch devices, disable hover-based dropdowns */ + @media (hover: none), (pointer: coarse) { + /* Hide dropdown by default on touch */ + .group > .absolute, + .group > div.absolute { + visibility: hidden !important; + opacity: 0 !important; + } + + /* But show when dropdown-open is applied */ + .group.dropdown-open > .absolute, + .group.dropdown-open > div.absolute { + visibility: visible !important; + opacity: 1 !important; + pointer-events: auto !important; + } + } + + /* On small screens, always use click-based dropdowns */ + @media (max-width: 768px) { + .group > .absolute, + .group > div.absolute { + visibility: hidden !important; + opacity: 0 !important; + } + + .group.dropdown-open > .absolute, + .group.dropdown-open > div.absolute { + visibility: visible !important; + opacity: 1 !important; + pointer-events: auto !important; + } + } + + /* Touch feedback - subtle opacity change only */ + .touch-active { + opacity: 0.8; + } + + /* ===== ENSURE ALL INTERACTIVE ELEMENTS ARE CLICKABLE ===== */ + button, .btn-primary, select, input, a, [onclick], [role="button"] { + cursor: pointer; + -webkit-tap-highlight-color: rgba(124, 58, 237, 0.3); + /* Ensure element is clickable */ + position: relative; + z-index: 1; + } + + /* Touch optimization for all interactive elements */ + button, select, input, a, .touch-target, .mobile-menu-btn, .mobile-nav-link, [onclick] { + touch-action: manipulation; + -webkit-touch-callout: none; + user-select: none; + } + + /* Prevent any pseudo-elements from blocking clicks */ + button::before, button::after, + .btn-primary::before, .btn-primary::after { + pointer-events: none; + } + + /* Desktop Navigation Links */ + .desktop-nav-links { + display: flex; + align-items: center; + gap: 1.5rem; + } + + /* === TOUCH TARGETS - Min 44x44px === */ + .touch-target { + min-width: 44px; + min-height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + } + + /* === MOBILE SELECT & BUTTON STYLING === */ + @media (max-width: 768px) { + /* Ensure select dropdowns are touch-friendly */ + select { + min-height: 44px; + font-size: 16px; /* Prevents iOS zoom on focus */ + -webkit-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%239ca3af'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 16px; + padding-right: 32px; + } + + /* Touch-friendly buttons */ + button { + min-height: 40px; + } + + /* Prevent double-tap zoom on buttons */ + button, select, input, a { + touch-action: manipulation; + } + } + + /* === MOBILE CARDS === */ + .mobile-card-stack { + display: flex; + flex-direction: column; + gap: 12px; + } + + /* Table to Cards Transformation */ + .responsive-table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + /* === MOBILE MODALS === */ + @media (max-width: 640px) { + #modal .glass-card { + margin: 0; + border-radius: 0; + min-height: 100vh; + max-height: 100vh; + width: 100%; + max-width: 100%; + } + + #modal { + padding: 0; + } + + /* Modal close button fixed */ + .modal-close-fixed { + position: fixed; + top: 12px; + right: 12px; + z-index: 60; + width: 44px; + height: 44px; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(8px); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + } + + /* === MOBILE HERO === */ + @media (max-width: 640px) { + .hero-section h1 { + font-size: 2rem; + line-height: 1.2; + } + + .hero-section p { + font-size: 1rem; + } + + .animate-float { + animation: none; + } + } + + /* === MOBILE METRIC CARDS === */ + @media (max-width: 640px) { + .metric-card { + padding: 16px; + } + + .metric-card .text-3xl { + font-size: 1.5rem; + } + } + + /* === MOBILE BUTTONS === */ + @media (max-width: 640px) { + .btn-primary { + padding: 14px 20px; + width: 100%; + justify-content: center; + } + + /* Button group stacking */ + .button-group-mobile { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + } + + .button-group-mobile > button, + .button-group-mobile > a { + width: 100%; + } + } + + /* === MOBILE FILTERS === */ + @media (max-width: 768px) { + .filter-bar-mobile { + display: flex; + flex-direction: column; + gap: 12px; + } + + .filter-bar-mobile .flex-wrap { + flex-wrap: nowrap; + overflow-x: auto; + padding-bottom: 8px; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + + .filter-bar-mobile .flex-wrap::-webkit-scrollbar { + display: none; + } + + /* Filter buttons as pills */ + .task-filter-btn, + .schedule-filter-btn, + .playbook-filter-btn { + flex-shrink: 0; + white-space: nowrap; + padding: 10px 16px; + min-height: 44px; + } + } + + /* === MOBILE HOST CARDS === */ + @media (max-width: 768px) { + .host-card { + padding: 16px; + } + + .host-card-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border-color); + } + + .host-card-actions button { + flex: 1; + min-width: calc(50% - 4px); + min-height: 44px; + font-size: 0.75rem; + } + + /* Host info condensed */ + .host-info-condensed { + display: none; + } + + .host-info-expanded { + display: block; + } + } + + @media (min-width: 769px) { + .host-info-condensed { + display: block; + } + + .host-info-expanded { + display: none; + } + } + + /* === MOBILE SCHEDULE STEPPER === */ + @media (max-width: 640px) { + .schedule-step-indicator { + flex-direction: column; + align-items: flex-start; + gap: 0; + } + + .schedule-step-container { + display: flex; + align-items: center; + width: 100%; + padding: 8px 0; + } + + .schedule-step-dot { + flex-shrink: 0; + } + + .schedule-step-label { + margin-left: 12px; + font-size: 0.875rem; + color: var(--secondary-text); + } + + .schedule-step-connector { + width: 2px; + height: 24px; + margin-left: 15px; + } + + /* Form fields full width */ + .schedule-modal-step input, + .schedule-modal-step select, + .schedule-modal-step textarea { + width: 100%; + min-height: 44px; + } + } + + /* === MOBILE TABLES AS CARDS === */ + @media (max-width: 768px) { + .table-card-mobile { + display: block; + } + + .table-card-mobile thead { + display: none; + } + + .table-card-mobile tbody { + display: flex; + flex-direction: column; + gap: 12px; + } + + .table-card-mobile tr { + display: flex; + flex-direction: column; + background: rgba(42, 42, 42, 0.4); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 16px; + } + + .table-card-mobile td { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .table-card-mobile td:last-child { + border-bottom: none; + } + + .table-card-mobile td::before { + content: attr(data-label); + font-weight: 600; + color: var(--secondary-text); + font-size: 0.75rem; + text-transform: uppercase; + } + } + + /* === MOBILE TASK CARDS === */ + @media (max-width: 640px) { + .task-log-card { + padding: 14px; + } + + .task-log-card .flex.items-center.gap-3 { + flex-wrap: wrap; + } + + .task-log-card-header { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + } + + .task-log-card-meta { + display: flex; + flex-wrap: wrap; + gap: 8px; + font-size: 0.75rem; + } + + .task-log-card-actions { + display: flex; + gap: 8px; + margin-top: 12px; + width: 100%; + } + + .task-log-card-actions button { + flex: 1; + min-height: 40px; + } + } + + /* === MOBILE PLAYBOOK CARDS === */ + @media (max-width: 640px) { + .playbook-card { + padding: 14px; + } + + .playbook-card-header { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .playbook-card-actions { + display: flex; + gap: 8px; + width: 100%; + margin-top: 12px; + } + + .playbook-action-btn { + flex: 1; + min-height: 44px; + justify-content: center; + } + } + + /* === MOBILE CALENDAR === */ + @media (max-width: 640px) { + .schedule-calendar-day { + min-height: 60px; + padding: 4px; + font-size: 0.7rem; + } + + .schedule-calendar-event { + font-size: 0.6rem; + padding: 1px 3px; + } + + #schedule-calendar-grid { + gap: 2px; + } + } + + /* === MOBILE HELP PAGE === */ + @media (max-width: 1024px) { + .help-toc { + display: none; + } + + .help-main-content { + width: 100%; + } + } + + @media (max-width: 640px) { + .help-card { + padding: 16px; + } + + .help-section-title { + font-size: 1.1rem; + } + } + + /* === MOBILE FOOTER === */ + @media (max-width: 640px) { + footer { + padding: 16px; + } + + footer p { + font-size: 0.75rem; + } + } + + /* === MOBILE LOADING OVERLAY === */ + @media (max-width: 640px) { + #loading-overlay .loading-spinner { + width: 32px; + height: 32px; + } + + #loading-overlay p { + font-size: 0.875rem; + } + } + + /* === REDUCED MOTION === */ + @media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .animate-float, + .animate-pulse, + .animate-pulse-slow { + animation: none !important; + } + } + + /* === MOBILE ANIMATIONS OPTIMIZATION === */ + @media (max-width: 768px) { + .glass-card:hover { + transform: none; + } + + .host-card:hover { + transform: none; + } + + .playbook-card:hover { + transform: none; + } + + .schedule-card:hover { + transform: none; + } + + /* Reduce animation complexity */ + .fade-in { + transform: none; + transition: opacity 0.3s ease; + } + } + + /* === MOBILE TOAST NOTIFICATIONS === */ + @media (max-width: 640px) { + .toast-container { + bottom: 70px; + left: 16px; + right: 16px; + max-width: none; + } + + .toast { + width: 100%; + border-radius: 8px; + } + } + + /* === MOBILE DATE PICKER === */ + @media (max-width: 640px) { + #task-date-calendar { + left: 0; + right: 0; + width: auto; + margin: 0 16px; + } + + .task-calendar { + width: 100%; + } + } + + /* === KEBAB MENU FOR MOBILE ACTIONS === */ + .kebab-menu { + display: none; + position: relative; + } + + .kebab-menu-btn { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(55, 65, 81, 0.5); + border: none; + border-radius: 8px; + cursor: pointer; + transition: background 0.2s ease; + } + + .kebab-menu-btn:hover, + .kebab-menu-btn:active { + background: rgba(124, 58, 237, 0.3); + } + + .kebab-menu-dropdown { + position: absolute; + top: 100%; + right: 0; + min-width: 160px; + background: var(--secondary-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + z-index: 50; + opacity: 0; + visibility: hidden; + transform: translateY(-8px); + transition: all 0.2s ease; + } + + .kebab-menu.open .kebab-menu-dropdown { + opacity: 1; + visibility: visible; + transform: translateY(4px); + } + + .kebab-menu-dropdown button { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 12px 16px; + border: none; + background: none; + color: var(--primary-text); + font-size: 0.875rem; + cursor: pointer; + transition: background 0.2s ease; + } + + .kebab-menu-dropdown button:hover { + background: rgba(124, 58, 237, 0.1); + } + + .kebab-menu-dropdown button:first-child { + border-radius: 8px 8px 0 0; + } + + .kebab-menu-dropdown button:last-child { + border-radius: 0 0 8px 8px; + } + + @media (max-width: 640px) { + .kebab-menu { + display: block; + } + + .desktop-actions { + display: none; + } + } + + @media (min-width: 641px) { + .kebab-menu { + display: none; + } + + .desktop-actions { + display: flex; + } + } + + /* === MOBILE DROPDOWN CLICK SUPPORT === */ + .group.dropdown-open > div[class*="absolute"] { + opacity: 1 !important; + visibility: visible !important; + } + + /* Disable hover dropdowns on touch devices */ + @media (hover: none) { + .group:hover > div[class*="absolute"] { + opacity: 0; + visibility: hidden; + } + .group.dropdown-open > div[class*="absolute"] { + opacity: 1 !important; + visibility: visible !important; + } + } + + /* === MOBILE NAV SHOW/HIDE === */ + @media (max-width: 768px) { + .mobile-menu-btn { + display: flex; + } + + .desktop-nav-links { + display: none; + } + + .mobile-nav-overlay, + .mobile-nav-sidebar { + display: block; + } + + /* Adjust nav padding for mobile */ + nav .max-w-7xl { + padding-left: 16px; + padding-right: 16px; + } + } + + /* === MOBILE-FIRST GRID ADJUSTMENTS === */ + @media (max-width: 640px) { + .max-w-7xl { + padding-left: 16px; + padding-right: 16px; + } + + /* Single column on mobile */ + .grid-cols-1.md\\:grid-cols-2.lg\\:grid-cols-4 { + grid-template-columns: 1fr; + } + + .grid-cols-1.lg\\:grid-cols-3 { + grid-template-columns: 1fr; + } + } + + /* === SWIPE HINT === */ + .swipe-hint { + display: none; + } + + @media (max-width: 768px) { + .swipe-hint { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px; + background: rgba(124, 58, 237, 0.1); + border-radius: 8px; + margin-bottom: 12px; + font-size: 0.75rem; + color: var(--secondary-text); + } + + .swipe-hint i { + animation: swipeHint 1.5s ease-in-out infinite; + } + + @keyframes swipeHint { + 0%, 100% { transform: translateX(0); } + 50% { transform: translateX(8px); } + } + } + + /* === BOTTOM ACTION BAR FOR MOBILE === */ + .mobile-action-bar { + display: none; + } + + @media (max-width: 640px) { + .mobile-action-bar { + display: flex; + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--secondary-bg); + border-top: 1px solid var(--border-color); + padding: 8px 16px; + padding-bottom: calc(8px + env(safe-area-inset-bottom)); + z-index: 40; + gap: 8px; + } + + .mobile-action-bar button { + flex: 1; + min-height: 44px; + border-radius: 8px; + font-size: 0.75rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + } + + /* Add bottom padding to body for action bar */ + body { + padding-bottom: 70px; + } + } + + /* === LIGHT THEME MOBILE ADJUSTMENTS === */ + body.light-theme .mobile-nav-sidebar { + background: #ffffff; + border-color: #e5e5e5; + } + + body.light-theme .mobile-nav-links button, + body.light-theme .mobile-nav-links a { + color: #4b5563; + } + + body.light-theme .mobile-nav-links button:hover, + body.light-theme .mobile-nav-links button:active, + body.light-theme .mobile-nav-links a:hover, + body.light-theme .mobile-nav-links a:active { + color: #1f2937; + } + + body.light-theme .mobile-nav-links button.active, + body.light-theme .mobile-nav-links a.active { + color: #7c3aed; + } + + body.light-theme .mobile-action-bar { + background: #ffffff; + border-color: #e5e5e5; + } + + body.light-theme .kebab-menu-dropdown { + background: #ffffff; + border-color: #e5e5e5; + } + + body.light-theme .kebab-menu-dropdown button { + color: #1f2937; + } + + body.light-theme .kebab-menu-dropdown button:hover { + background: rgba(124, 58, 237, 0.1); + } + + /* === SCROLLBAR HIDE UTILITY === */ + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .scrollbar-hide::-webkit-scrollbar { + display: none; + } + + /* === SAFE AREA INSETS FOR NOTCHED PHONES === */ + @supports (padding: max(0px)) { + .safe-area-bottom { + padding-bottom: max(16px, env(safe-area-inset-bottom)); + } + + .safe-area-top { + padding-top: max(16px, env(safe-area-inset-top)); + } + } + + /* === PULL TO REFRESH INDICATOR (visual only) === */ + .ptr-indicator { + display: none; + position: fixed; + top: 60px; + left: 50%; + transform: translateX(-50%); + background: var(--accent-color); + padding: 8px 16px; + border-radius: 20px; + font-size: 0.75rem; + z-index: 100; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + /* === LANDSCAPE ORIENTATION ADJUSTMENTS === */ + @media (max-height: 500px) and (orientation: landscape) { + .hero-section { + padding-top: 60px; + padding-bottom: 20px; + } + + .hero-section h1 { + font-size: 1.5rem; + margin-bottom: 8px; + } + + .hero-section p { + font-size: 0.875rem; + margin-bottom: 12px; + } + + .metric-card { + padding: 12px; + } + }
+ +Chargement du Dashboard...
+ +