RELEASE V.1.2.0 - Voici les fonctionnalités :
-> Mode sombre -> Updater
This commit is contained in:
1 parent
383354d854
commit
5fac0d4032
9 files changed
+1011
-2
No files matched your search
@@ -21,6 +21,15 @@ function App() {
|
||||
.then(() => defineCustomElements(window))
|
||||
.catch(() => {});
|
||||
|
||||
// Init dark mode
|
||||
if (localStorage.getItem('theme') === 'dark') {
|
||||
document.body.classList.add('dark-mode');
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
}
|
||||
|
||||
// écouter un événement custom pour synchroniser l'état d'authentification
|
||||
const onAuthChanged = () => {
|
||||
try {
|
||||
|
||||
@@ -36,7 +36,8 @@ const MENU_GROUPS = [
|
||||
label: 'Configuration',
|
||||
items: [
|
||||
{ label: 'Gestion des parametres', href: '/parametres', match: ['/parametres'] },
|
||||
{ label: 'Gestion des menus regionaux', href: '/menu', match: ['/menu'] }
|
||||
{ label: 'Gestion des menus regionaux', href: '/menu', match: ['/menu'] },
|
||||
{ label: 'Mise à jour systeme', href: '/systeme-maj', match: ['/systeme-maj'] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -119,6 +120,22 @@ export default function AppHeader({ isLoggedIn, userName }) {
|
||||
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [notificationCount, setNotificationCount] = useState(0);
|
||||
const [isDarkMode, setIsDarkMode] = useState(() => localStorage.getItem('theme') === 'dark');
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(prev => {
|
||||
const next = !prev;
|
||||
localStorage.setItem('theme', next ? 'dark' : 'light');
|
||||
if (next) {
|
||||
document.body.classList.add('dark-mode');
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
@@ -313,6 +330,7 @@ export default function AppHeader({ isLoggedIn, userName }) {
|
||||
else if (section === 'gares') permSection = 'gares';
|
||||
else if (section === 'quais') permSection = 'quais';
|
||||
else if (section === 'annonces-sonores') permSection = 'annonces_sonores';
|
||||
else if (section === 'systeme-maj') permSection = 'systeme_maj';
|
||||
|
||||
return hasAccessTo(user, permSection);
|
||||
}
|
||||
@@ -407,6 +425,16 @@ export default function AppHeader({ isLoggedIn, userName }) {
|
||||
)}
|
||||
|
||||
<div className="app-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="app-header-notification-btn"
|
||||
aria-label="Basculer le mode sombre"
|
||||
onClick={toggleDarkMode}
|
||||
title={isDarkMode ? "Passer en mode clair" : "Passer en mode sombre"}
|
||||
>
|
||||
<wcs-mat-icon icon={isDarkMode ? 'light_mode' : 'dark_mode'}></wcs-mat-icon>
|
||||
</button>
|
||||
|
||||
{isLoggedIn && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
/* Dark Mode Styles */
|
||||
/* ==========================================================================
|
||||
WCS CORE SEMANTIC OVERRIDES (Applies to all wcs-components, modals, etc.)
|
||||
========================================================================== */
|
||||
body.dark-mode {
|
||||
/* Surface and Backgrounds */
|
||||
--wcs-semantic-color-background-surface-body: #121212;
|
||||
--wcs-semantic-color-background-surface-primary: #1e1e1e;
|
||||
--wcs-semantic-color-background-surface-secondary: #2a2a2a;
|
||||
--wcs-semantic-color-background-surface-tertiary: #333333;
|
||||
--wcs-semantic-color-background-control-default: #2a2a2a;
|
||||
--wcs-semantic-color-background-backdrop: rgba(0, 0, 0, 0.7);
|
||||
|
||||
--wcs-semantic-color-background-action-primary-default: #0d47a1;
|
||||
--wcs-semantic-color-background-action-primary-hover: #1565c0;
|
||||
--wcs-semantic-color-background-action-primary-press: #0a3a82;
|
||||
--wcs-semantic-color-background-action-primary-disabled: #1e3a5f;
|
||||
|
||||
--wcs-semantic-color-background-action-secondary-default: transparent;
|
||||
--wcs-semantic-color-background-action-secondary-hover: rgba(255, 255, 255, 0.05);
|
||||
--wcs-semantic-color-background-action-secondary-press: rgba(255, 255, 255, 0.1);
|
||||
--wcs-semantic-color-background-action-secondary-disabled: transparent;
|
||||
|
||||
--wcs-semantic-color-background-action-neutral-default: #333333;
|
||||
--wcs-semantic-color-background-action-neutral-hover: #444444;
|
||||
|
||||
/* Borders */
|
||||
--wcs-semantic-color-border-primary: #444444;
|
||||
--wcs-semantic-color-border-secondary: #333333;
|
||||
--wcs-semantic-color-border-action-secondary-default: #64b5f6;
|
||||
--wcs-semantic-color-border-action-secondary-hover: #90caf9;
|
||||
--wcs-semantic-color-border-action-secondary-disabled: #444444;
|
||||
|
||||
/* Texts & Foregrounds */
|
||||
--wcs-semantic-color-text-primary: #ffffff;
|
||||
--wcs-semantic-color-text-secondary: #cccccc;
|
||||
--wcs-semantic-color-text-tertiary: #aaaaaa;
|
||||
--wcs-semantic-color-text-disabled: #666666;
|
||||
|
||||
--wcs-semantic-color-foreground-primary: #ffffff;
|
||||
--wcs-semantic-color-foreground-brand: #64b5f6;
|
||||
|
||||
--wcs-semantic-color-foreground-action-primary-default: #ffffff;
|
||||
--wcs-semantic-color-foreground-action-secondary-default: #64b5f6;
|
||||
--wcs-semantic-color-foreground-action-secondary-hover: #90caf9;
|
||||
--wcs-semantic-color-foreground-action-secondary-disabled: #666666;
|
||||
|
||||
--wcs-semantic-color-foreground-action-neutral-default: #ffffff;
|
||||
|
||||
/* Links */
|
||||
--wcs-semantic-color-text-link-default: #64b5f6;
|
||||
--wcs-semantic-color-text-link-hover: #90caf9;
|
||||
}
|
||||
|
||||
/* Base overrides for root elements to ensure inheritance */
|
||||
body.dark-mode * {
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode {
|
||||
background-color: #121212 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .app-header-shell,
|
||||
body.dark-mode .app-header-main {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-bottom-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .nav-panel wcs-nav,
|
||||
body.dark-mode .wcs-nav {
|
||||
--wcs-nav-background: #1e1e1e !important;
|
||||
--wcs-nav-text-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .widget-card,
|
||||
body.dark-mode .login-card,
|
||||
body.dark-mode .table-container,
|
||||
body.dark-mode .modal-content {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
|
||||
body.dark-mode .wcs-table th {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-bottom: 1px solid #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .wcs-table td {
|
||||
border-bottom: 1px solid #444 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .wcs-table tr:hover {
|
||||
background-color: #2a2a2a !important;
|
||||
}
|
||||
|
||||
body.dark-mode .app-top-menu,
|
||||
body.dark-mode .app-header-user-accordion,
|
||||
body.dark-mode .app-submenu,
|
||||
body.dark-mode .app-notifications-drawer,
|
||||
body.dark-mode .app-notifications-overlay {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode a {
|
||||
color: #61dafb !important;
|
||||
}
|
||||
|
||||
body.dark-mode .app-menu-group > a,
|
||||
body.dark-mode .app-menu-group > button,
|
||||
body.dark-mode .app-header-user-pill,
|
||||
body.dark-mode .app-header-title h1,
|
||||
body.dark-mode .app-header-title span {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .app-menu-group > a:hover,
|
||||
body.dark-mode .app-menu-group > button:hover,
|
||||
body.dark-mode .app-menu-group > a.active,
|
||||
body.dark-mode .app-menu-group > button.active {
|
||||
background-color: #333 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .app-submenu a:hover,
|
||||
body.dark-mode .app-submenu a.active {
|
||||
background-color: #444 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
body.dark-mode .page-header h2,
|
||||
body.dark-mode h1,
|
||||
body.dark-mode h2,
|
||||
body.dark-mode h3,
|
||||
body.dark-mode h4,
|
||||
body.dark-mode h5,
|
||||
body.dark-mode h6,
|
||||
body.dark-mode p,
|
||||
body.dark-mode span {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
body.dark-mode input,
|
||||
body.dark-mode select,
|
||||
body.dark-mode textarea,
|
||||
body.dark-mode .travaux-input,
|
||||
body.dark-mode .travaux-textarea,
|
||||
body.dark-mode .premium-input,
|
||||
body.dark-mode .gares-text-input,
|
||||
body.dark-mode .gares-toolbar-input,
|
||||
body.dark-mode .gares-toolbar-select,
|
||||
body.dark-mode .horaires-search-input,
|
||||
body.dark-mode .horaires-quai-select {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #fff !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .wcs-tabs {
|
||||
border-bottom-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode wcs-tab {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .station-type-badge.ville {
|
||||
background: #1e3a8a;
|
||||
color: #bfdbfe;
|
||||
}
|
||||
body.dark-mode .station-type-badge.interurbaine {
|
||||
background: #78350f;
|
||||
color: #fde68a;
|
||||
}
|
||||
body.dark-mode .service-badge {
|
||||
background: #3730a3;
|
||||
color: #e0e7ff;
|
||||
}
|
||||
|
||||
/* Dashboard Overrides */
|
||||
body.dark-mode .dashboard-page {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
body.dark-mode .dashboard-hero {
|
||||
background: linear-gradient(140deg, #1e1e1e 0%, #2a2a2a 70%, #333 100%) !important;
|
||||
color: #e0e0e0 !important;
|
||||
box-shadow: 0 14px 35px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
body.dark-mode .dashboard-hero h1,
|
||||
body.dark-mode .dashboard-hero p,
|
||||
body.dark-mode .dashboard-hero strong,
|
||||
body.dark-mode .dashboard-subtitle,
|
||||
body.dark-mode .dashboard-kicker {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .stat-card,
|
||||
body.dark-mode .dashboard-widget,
|
||||
body.dark-mode .widget-header-panel,
|
||||
body.dark-mode .widget-content-panel {
|
||||
background-color: #1e1e1e !important;
|
||||
border-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .stat-card-title,
|
||||
body.dark-mode .stat-card-value,
|
||||
body.dark-mode .stat-card-subtitle,
|
||||
body.dark-mode .widget-item-text strong,
|
||||
body.dark-mode .perturbation-item-route,
|
||||
body.dark-mode .horaire-item-route,
|
||||
body.dark-mode .perturbation-item-details,
|
||||
body.dark-mode .quick-action-row-left {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .stat-card-icon-wrap {
|
||||
background-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .quick-action-row {
|
||||
background-color: #2a2a2a !important;
|
||||
border-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .quick-action-row:hover {
|
||||
background-color: #333 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
body.dark-mode .quick-action-icon {
|
||||
background-color: #444 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
body.dark-mode .perturbation-item:hover,
|
||||
body.dark-mode .horaire-item:hover {
|
||||
background-color: #2a2a2a !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
body.dark-mode .widget-header {
|
||||
background-color: #2a2a2a !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
body.dark-mode .widget-title-icon {
|
||||
background-color: #444 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
/* Login Page Overrides */
|
||||
body.dark-mode .admin-login-page {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
body.dark-mode .admin-login-panel {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
body.dark-mode .admin-login-panel-header p,
|
||||
body.dark-mode .admin-login-form label,
|
||||
body.dark-mode .admin-login-branding p,
|
||||
body.dark-mode .admin-login-meta span {
|
||||
color: #ccc !important;
|
||||
}
|
||||
body.dark-mode .admin-login-branding {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Common overrides for other pages */
|
||||
body.dark-mode .panel-layout {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
body.dark-mode .page-header {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
body.dark-mode .white-box,
|
||||
body.dark-mode .card,
|
||||
body.dark-mode section.content,
|
||||
body.dark-mode .wcs-table-container,
|
||||
body.dark-mode .panel-content-container,
|
||||
body.dark-mode .form-container {
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
/* Global wildcard overrides for all pages (BEM-like classes) */
|
||||
body.dark-mode [class*="-page"] {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-card"],
|
||||
body.dark-mode [class*="-panel"],
|
||||
body.dark-mode [class*="-widget"],
|
||||
body.dark-mode [class*="-toolbar"],
|
||||
body.dark-mode [class*="-state"]:not(wcs-progress-bar):not(wcs-spinner),
|
||||
body.dark-mode [class*="-empty"],
|
||||
body.dark-mode [class*="-content"]:not(.panel-content),
|
||||
body.dark-mode [class*="resume-"],
|
||||
body.dark-mode [class*="step-container"] {
|
||||
background-color: #1e1e1e !important;
|
||||
border-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-header"]:not(.app-header-shell):not(.app-header-main):not(.app-header-brand) {
|
||||
/* Avoid overriding the hero gradients which are already targeted, but fix normal headers */
|
||||
background-color: transparent !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
/* Specific background for hero sections */
|
||||
body.dark-mode [class*="-hero"],
|
||||
body.dark-mode [class*="-page-header"] {
|
||||
background: linear-gradient(140deg, #1e1e1e 0%, #2a2a2a 70%, #333 100%) !important;
|
||||
box-shadow: 0 14px 35px rgba(0,0,0,0.5) !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-title"],
|
||||
body.dark-mode [class*="-subtitle"],
|
||||
body.dark-mode [class*="-kicker"],
|
||||
body.dark-mode [class*="-text"],
|
||||
body.dark-mode [class*="-description"],
|
||||
body.dark-mode [class*="-label"],
|
||||
body.dark-mode [class*="-value"],
|
||||
body.dark-mode [class*="-region"],
|
||||
body.dark-mode [class*="-count-pill"] {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-search-box"],
|
||||
body.dark-mode [class*="-search-input"] {
|
||||
background-color: #333 !important;
|
||||
border-color: #444 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-count-pill"] {
|
||||
background-color: #333 !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="-item"]:hover {
|
||||
background-color: #2a2a2a !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode [class*="form-group"] label {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
/* Fix navbar user pill and icons which were left white */
|
||||
body.dark-mode .app-header-notification-btn,
|
||||
body.dark-mode .app-header-user-pill,
|
||||
body.dark-mode .app-header-menu-toggle {
|
||||
background-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
/* Force WCS components variables for dark mode when inside body.dark-mode */
|
||||
body.dark-mode {
|
||||
--wcs-semantic-color-background-surface-primary: #1e1e1e;
|
||||
--wcs-semantic-color-text-primary: #e0e0e0;
|
||||
--wcs-semantic-color-border-primary: #444;
|
||||
}
|
||||
body.dark-mode wcs-input,
|
||||
body.dark-mode wcs-select,
|
||||
body.dark-mode wcs-textarea {
|
||||
--wcs-input-background-color: #333;
|
||||
--wcs-input-value-color: #ffffff;
|
||||
--wcs-input-placeholder-color: #aaa;
|
||||
--wcs-input-border-color-default: #555;
|
||||
|
||||
--wcs-select-control-background-color: #333;
|
||||
--wcs-select-value-color: #ffffff;
|
||||
--wcs-select-placeholder-color: #aaa;
|
||||
--wcs-select-control-border-color-default: #555;
|
||||
|
||||
--wcs-textarea-background-color: #333;
|
||||
--wcs-textarea-value-color: #ffffff;
|
||||
--wcs-textarea-placeholder-color: #aaa;
|
||||
--wcs-textarea-border-color-default: #555;
|
||||
}
|
||||
|
||||
/* Fixes for specific components missed by wildcards */
|
||||
body.dark-mode .modal-footer,
|
||||
body.dark-mode .modal-header,
|
||||
body.dark-mode .modal-body,
|
||||
body.dark-mode .quais-sidebar,
|
||||
body.dark-mode .quais-main,
|
||||
body.dark-mode .quais-main-header,
|
||||
body.dark-mode .quais-day-selector,
|
||||
body.dark-mode .quai-arret-row,
|
||||
body.dark-mode .quais-view-toggle,
|
||||
body.dark-mode .toolbar-container,
|
||||
body.dark-mode .parcours-preview,
|
||||
body.dark-mode .arret-accordion-card,
|
||||
body.dark-mode .horaire-card,
|
||||
body.dark-mode .horaire-card-header,
|
||||
body.dark-mode .horaire-card-body,
|
||||
body.dark-mode .horaire-card-actions,
|
||||
body.dark-mode .empty-state-modern,
|
||||
body.dark-mode .loading-state-full,
|
||||
body.dark-mode .quais-layout
|
||||
{
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .quai-arret-row.current-gare {
|
||||
background-color: #08345f !important;
|
||||
border-color: #094074 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .horaire-group-card,
|
||||
body.dark-mode .perturbation-item,
|
||||
body.dark-mode .horaire-display,
|
||||
body.dark-mode .horaire-results {
|
||||
background-color: #1e1e1e !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .quais-day-btn,
|
||||
body.dark-mode .quais-btn:not(.quais-btn-primary),
|
||||
body.dark-mode .jour-type-option {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .quais-day-btn.active,
|
||||
body.dark-mode .jour-type-option.active {
|
||||
background-color: #0d47a1 !important;
|
||||
color: #fff !important;
|
||||
border-color: #0d47a1 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .quais-sidebar-header {
|
||||
background-color: #121212 !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .horaire-result-item {
|
||||
border-color: #333 !important;
|
||||
}
|
||||
body.dark-mode .horaire-result-item:hover {
|
||||
background-color: #2a2a2a !important;
|
||||
}
|
||||
|
||||
/* Fixes for Horaires & Materiel Roulant wrappers */
|
||||
body.dark-mode .horaires-main-content,
|
||||
body.dark-mode .horaires-main-tabs,
|
||||
body.dark-mode .horaires-toolbar,
|
||||
body.dark-mode .horaires-workspace,
|
||||
body.dark-mode .horaires-page .table-container,
|
||||
body.dark-mode .horaires-empty-state,
|
||||
body.dark-mode .horaires-selection-panel,
|
||||
body.dark-mode .attribution-mr-toolbar,
|
||||
body.dark-mode .attribution-mr-timeline-wrap,
|
||||
body.dark-mode .attribution-mr-gantt-shell,
|
||||
body.dark-mode .attribution-mr-gantt-header,
|
||||
body.dark-mode .attribution-mr-gantt-label-head,
|
||||
body.dark-mode .attribution-mr-gantt-row,
|
||||
body.dark-mode .attribution-mr-gantt-label,
|
||||
body.dark-mode .attribution-mr-materiel-card,
|
||||
body.dark-mode .attribution-mr-materiels-group {
|
||||
background: #1e1e1e !important;
|
||||
background-color: #1e1e1e !important;
|
||||
border-color: #333 !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .horaires-page input,
|
||||
body.dark-mode .horaires-page select,
|
||||
body.dark-mode .horaires-page textarea,
|
||||
body.dark-mode .attribution-mr-toolbar select,
|
||||
body.dark-mode .attribution-mr-direction-btn {
|
||||
background-color: #2a2a2a !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #444 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .attribution-mr-direction-btn.is-active {
|
||||
background-color: #0d47a1 !important;
|
||||
color: #fff !important;
|
||||
border-color: #0d47a1 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .attribution-mr-gantt-track {
|
||||
background-image: repeating-linear-gradient(to right, #1e1e1e 0, #1e1e1e calc(230px - 1px), #333 calc(230px - 1px), #333 230px) !important;
|
||||
}
|
||||
|
||||
body.dark-mode .horaires-page .wcs-table td {
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
/* Fixes for Travaux, Dashboard, and other remaining components */
|
||||
body.dark-mode [class*="modal-content"],
|
||||
body.dark-mode [class*="modal-header"],
|
||||
body.dark-mode [class*="modal-body"],
|
||||
body.dark-mode [class*="modal-footer"],
|
||||
body.dark-mode [class$="-modal"],
|
||||
body.dark-mode .travaux-toolbar,
|
||||
body.dark-mode .travaux-search-box,
|
||||
body.dark-mode .travaux-count-pill,
|
||||
body.dark-mode .travaux-card,
|
||||
body.dark-mode .travaux-card-header,
|
||||
body.dark-mode .travaux-card-footer,
|
||||
body.dark-mode .travaux-empty-state,
|
||||
body.dark-mode .travaux-state,
|
||||
body.dark-mode .step-container,
|
||||
body.dark-mode .steps-indicator,
|
||||
body.dark-mode .jour-checkbox,
|
||||
body.dark-mode .substitutions-selectionnees,
|
||||
body.dark-mode .horaire-selected-item,
|
||||
body.dark-mode .horaires-results-list,
|
||||
body.dark-mode .resume-card,
|
||||
body.dark-mode .substitution-resume-item,
|
||||
body.dark-mode .travaux-glass-card,
|
||||
body.dark-mode .premium-checkbox-label,
|
||||
body.dark-mode .dashboard-hero,
|
||||
body.dark-mode .stat-card,
|
||||
body.dark-mode .dashboard-widget,
|
||||
body.dark-mode .widget-header-panel,
|
||||
body.dark-mode .widget-content-panel,
|
||||
body.dark-mode .widget-list-panel,
|
||||
body.dark-mode .quick-action-row,
|
||||
body.dark-mode .dashboard-state,
|
||||
body.dark-mode .annonces-sonores-panel,
|
||||
body.dark-mode .parametres-card,
|
||||
body.dark-mode .tarifs-card,
|
||||
body.dark-mode .messagerie-panel,
|
||||
body.dark-mode .materiel-roulant-card
|
||||
{
|
||||
background-color: #1e1e1e !important;
|
||||
color: #e0e0e0 !important;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .dashboard-page,
|
||||
body.dark-mode .travaux-page,
|
||||
body.dark-mode .parametres-page,
|
||||
body.dark-mode .tarifs-page,
|
||||
body.dark-mode .materiel-roulant-page,
|
||||
body.dark-mode .mon-compte-page,
|
||||
body.dark-mode .messagerie-page,
|
||||
body.dark-mode .annonces-sonores-page,
|
||||
body.dark-mode .informations-page,
|
||||
body.dark-mode .pages-gestion-page,
|
||||
body.dark-mode .systeme-maj-page,
|
||||
body.dark-mode .activer-compte-page,
|
||||
body.dark-mode .acces-non-autorise-page,
|
||||
body.dark-mode .menu-page {
|
||||
background-color: #121212 !important;
|
||||
}
|
||||
|
||||
body.dark-mode .premium-checkbox-label.selected {
|
||||
background-color: #0d47a1 !important;
|
||||
color: #fff !important;
|
||||
border-color: #0d47a1 !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import 'wcs-core/dist/wcs/wcs.css';
|
||||
@import './dark-mode.css';
|
||||
|
||||
|
||||
/* styles.scss des différents labels SNCF */
|
||||
|
||||
@@ -22,6 +22,7 @@ import MonCompte from '../mon-compte/MonCompte';
|
||||
import Messagerie from '../messagerie/Messagerie';
|
||||
import AccesNonAutorise from '../acces-non-autorise/AccesNonAutorise';
|
||||
import AttributionMaterielRoulant from '../attribution-materiel-roulant/AttributionMaterielRoulant';
|
||||
import SystemeMaj from '../systeme-maj/SystemeMaj';
|
||||
import { canPerform, getCurrentUser, hasAccessTo } from '../../utils/userUtils';
|
||||
|
||||
export default function PanelRouter() {
|
||||
@@ -60,6 +61,7 @@ export default function PanelRouter() {
|
||||
if (path.startsWith('/menu/creation')) return denyIfNoAccess('menu', 'create') || <CreerMenu />;
|
||||
if (path.startsWith('/menu/edition/')) return denyIfNoAccess('menu', 'update') || <CreerMenu />;
|
||||
if (path.startsWith('/menu')) return denyIfNoAccess('menu') || <MenuRegional />;
|
||||
if (path.startsWith('/systeme-maj')) return denyIfNoAccess('systeme_maj') || <SystemeMaj />;
|
||||
|
||||
// default -> dashboard
|
||||
return <Dashboard />;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
.maj-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.maj-card {
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.maj-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.maj-header wcs-mat-icon {
|
||||
font-size: 48px;
|
||||
color: #0088ce;
|
||||
}
|
||||
|
||||
.maj-title h3 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 18px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.maj-body {
|
||||
min-height: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.maj-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.maj-actions p {
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.alert-new-version {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
background: #fef3c7;
|
||||
border-left: 4px solid #d97706;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
text-align: left;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alert-new-version strong {
|
||||
display: block;
|
||||
color: #92400e;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.alert-new-version p {
|
||||
color: #b45309;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: 16px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.progress-container wcs-progress-bar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-weight: bold;
|
||||
color: #0088ce;
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
font-size: 13px !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.maj-actions.success {
|
||||
color: #16a34a;
|
||||
}
|
||||
.maj-actions.success p {
|
||||
color: #15803d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Dark mode adjustments */
|
||||
body.dark-mode .maj-header {
|
||||
border-bottom-color: #333;
|
||||
}
|
||||
|
||||
body.dark-mode .maj-title h3 {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
body.dark-mode .alert-new-version {
|
||||
background: #451a03;
|
||||
border-color: #f59e0b;
|
||||
}
|
||||
|
||||
body.dark-mode .alert-new-version strong {
|
||||
color: #fde68a;
|
||||
}
|
||||
|
||||
body.dark-mode .alert-new-version p {
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
body.dark-mode .progress-text {
|
||||
color: #38bdf8;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './SystemeMaj.css';
|
||||
|
||||
export default function SystemeMaj() {
|
||||
const [status, setStatus] = useState('idle'); // idle, checking, available, updating, success
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [version, setVersion] = useState('v0.1.0 (Bêta)');
|
||||
const [newVersion, setNewVersion] = useState(null);
|
||||
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [stepName, setStepName] = useState('');
|
||||
|
||||
const checkForUpdates = () => {
|
||||
setStatus('checking');
|
||||
setTimeout(() => {
|
||||
// Pour une vérification réelle, on pourrait interroger un endpoint /api/check-update
|
||||
setNewVersion('Dernière version (Git)');
|
||||
setStatus('available');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const applyUpdate = async () => {
|
||||
setStatus('updating');
|
||||
setProgress(0);
|
||||
|
||||
try {
|
||||
// Démarre la mise à jour sur le backend
|
||||
await fetch('http://localhost:3001/api/update', { method: 'POST' });
|
||||
|
||||
// Polling de l'état
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const res = await fetch('http://localhost:3001/api/update/status');
|
||||
const data = await res.json();
|
||||
|
||||
setProgress(data.progress);
|
||||
setStepName(data.step);
|
||||
setLogs(data.logs || []);
|
||||
|
||||
if (data.status === 'success') {
|
||||
clearInterval(interval);
|
||||
setStatus('success');
|
||||
setVersion('À jour');
|
||||
setNewVersion(null);
|
||||
|
||||
// Reset le backend après succès
|
||||
setTimeout(() => {
|
||||
fetch('http://localhost:3001/api/update/reset', { method: 'POST' });
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
} else if (data.status === 'error') {
|
||||
clearInterval(interval);
|
||||
setStatus('error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur de connexion avec l'updater:", err);
|
||||
}
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
console.error("Impossible de lancer la mise à jour:", error);
|
||||
setStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="panel-layout">
|
||||
<div className="nav-panel">
|
||||
<wcs-nav>
|
||||
<wcs-nav-item>
|
||||
<wcs-mat-icon icon="update"></wcs-mat-icon>
|
||||
<span>Mises à jour</span>
|
||||
</wcs-nav-item>
|
||||
</wcs-nav>
|
||||
</div>
|
||||
|
||||
<div className="panel-content maj-content">
|
||||
<div className="page-header">
|
||||
<h2>Système de mises à jour</h2>
|
||||
<span className="station-type-badge ville" style={{ background: '#0088ce', color: '#fff' }}>Espace Administrateur</span>
|
||||
</div>
|
||||
|
||||
<div className="widget-card maj-card">
|
||||
<div className="maj-header">
|
||||
<wcs-mat-icon icon="system_update_alt" size="l"></wcs-mat-icon>
|
||||
<div className="maj-title">
|
||||
<h3>Version Actuelle</h3>
|
||||
<p className="version-text">{version}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="maj-body">
|
||||
{status === 'idle' && (
|
||||
<div className="maj-actions">
|
||||
<p>Votre système semble à jour. Vous pouvez forcer une vérification pour synchroniser avec le dépôt.</p>
|
||||
<wcs-button onClick={checkForUpdates} className="wcs-primary">Vérifier les mises à jour</wcs-button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'checking' && (
|
||||
<div className="maj-actions">
|
||||
<wcs-spinner></wcs-spinner>
|
||||
<p>Recherche de mises à jour sur le serveur Git distant...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'available' && (
|
||||
<div className="maj-actions">
|
||||
<div className="alert-new-version">
|
||||
<wcs-mat-icon icon="new_releases" style={{ color: '#d97706' }}></wcs-mat-icon>
|
||||
<div>
|
||||
<strong>Mise à jour prête : {newVersion}</strong>
|
||||
<p>Cette action va exécuter `git pull`, puis recompiler l'application. Le panel sera temporairement indisponible.</p>
|
||||
</div>
|
||||
</div>
|
||||
<wcs-button onClick={applyUpdate} className="wcs-primary">Lancer la mise à jour (PM2)</wcs-button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'updating' && (
|
||||
<div className="maj-actions" style={{ maxWidth: '600px', width: '100%' }}>
|
||||
<p>Mise à jour en cours sur le serveur...</p>
|
||||
<div className="progress-container">
|
||||
<wcs-progress-bar value={progress}></wcs-progress-bar>
|
||||
<span className="progress-text">{progress}%</span>
|
||||
</div>
|
||||
<p className="progress-details" style={{ fontWeight: 'bold', margin: '10px 0' }}>
|
||||
Étape : {stepName || "Initialisation..."}
|
||||
</p>
|
||||
<div className="terminal-logs" style={{ background: '#1e1e1e', color: '#e0e0e0', padding: '10px', borderRadius: '6px', height: '150px', overflowY: 'auto', fontFamily: 'monospace', fontSize: '12px', textAlign: 'left' }}>
|
||||
{logs.length === 0 ? "En attente des logs..." : logs.map((log, i) => (
|
||||
<div key={i}>{log}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<div className="maj-actions error">
|
||||
<wcs-mat-icon icon="error" style={{ color: '#dc2626', fontSize: '48px' }}></wcs-mat-icon>
|
||||
<p style={{ color: '#dc2626', fontWeight: 'bold' }}>Une erreur critique est survenue durant la mise à jour.</p>
|
||||
<p>Consultez les logs du serveur (PM2) ou vérifiez les permissions Git.</p>
|
||||
<wcs-button onClick={() => setStatus('idle')} className="wcs-secondary">Retour</wcs-button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'success' && (
|
||||
<div className="maj-actions success">
|
||||
<wcs-mat-icon icon="check_circle" style={{ color: '#16a34a', fontSize: '48px' }}></wcs-mat-icon>
|
||||
<p>Mise à jour compilée et déployée avec succès !</p>
|
||||
<p>L'application va se recharger automatiquement pour appliquer les changements...</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,8 @@ export const RBAC_MODULES = [
|
||||
'messagerie',
|
||||
'parametres',
|
||||
'utilisateurs_global',
|
||||
'annonces_sonores'
|
||||
'annonces_sonores',
|
||||
'systeme_maj'
|
||||
];
|
||||
|
||||
const denyAll = Object.fromEntries(CRUD_ACTIONS.map((a) => [a, false]));
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
const PORT = process.env.UPDATER_PORT || 3001;
|
||||
|
||||
// State to keep track of the current update process
|
||||
let updateState = {
|
||||
status: 'idle', // idle, updating, success, error
|
||||
progress: 0,
|
||||
step: '',
|
||||
logs: []
|
||||
};
|
||||
|
||||
function addLog(msg) {
|
||||
console.log(msg);
|
||||
updateState.logs.push(msg);
|
||||
if (updateState.logs.length > 100) {
|
||||
updateState.logs.shift(); // Keep last 100 logs
|
||||
}
|
||||
}
|
||||
|
||||
function runCommand(command, args, stepName, progressStart, progressEnd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateState.step = stepName;
|
||||
updateState.progress = progressStart;
|
||||
addLog(`>>> Exécution: ${command} ${args.join(' ')}`);
|
||||
|
||||
const proc = spawn(command, args, { cwd: path.join(__dirname), shell: true });
|
||||
|
||||
proc.stdout.on('data', (data) => {
|
||||
addLog(data.toString().trim());
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (data) => {
|
||||
addLog(`[ERREUR] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
addLog(`>>> Échec avec le code ${code}`);
|
||||
reject(new Error(`La commande ${command} a échoué.`));
|
||||
} else {
|
||||
updateState.progress = progressEnd;
|
||||
addLog(`>>> Succès: ${stepName}`);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.get('/api/update/status', (req, res) => {
|
||||
res.json(updateState);
|
||||
});
|
||||
|
||||
app.post('/api/update', async (req, res) => {
|
||||
if (updateState.status === 'updating') {
|
||||
return res.status(400).json({ error: 'Une mise à jour est déjà en cours.' });
|
||||
}
|
||||
|
||||
updateState = {
|
||||
status: 'updating',
|
||||
progress: 10,
|
||||
step: 'Initialisation',
|
||||
logs: []
|
||||
};
|
||||
|
||||
res.json({ message: 'Mise à jour démarrée' });
|
||||
|
||||
try {
|
||||
// Step 1: Git Pull (10% to 30%)
|
||||
await runCommand('git', ['pull'], 'Téléchargement des sources (git pull)', 10, 30);
|
||||
|
||||
// Step 2: NPM Install (30% to 60%)
|
||||
await runCommand('npm', ['install'], 'Installation des dépendances (npm install)', 30, 60);
|
||||
|
||||
// Step 3: Build (60% to 100%)
|
||||
await runCommand('npm', ['run', 'build'], 'Compilation de l\'application (npm run build)', 60, 100);
|
||||
|
||||
updateState.status = 'success';
|
||||
updateState.step = 'Terminé';
|
||||
updateState.progress = 100;
|
||||
addLog('>>> Mise à jour terminée avec succès.');
|
||||
|
||||
} catch (error) {
|
||||
updateState.status = 'error';
|
||||
updateState.step = 'Erreur';
|
||||
addLog(`>>> ERREUR CRITIQUE: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Acknowledge update success and reset
|
||||
app.post('/api/update/reset', (req, res) => {
|
||||
updateState = { status: 'idle', progress: 0, step: '', logs: [] };
|
||||
res.json({ message: 'Reset OK' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[Ferrovia Updater] Écoute sur le port ${PORT}`);
|
||||
});
|
||||
Reference in new issue
Block a user