Checkpoint: Menu latéral : nom utilisateur déplacé tout en bas avec espace libre avant, ajout du menu Tableau de bord Finance sous OPEX (Charges), création de la page dédiée avec accès au dossier OneDrive Windows.

This commit is contained in:
Manus
2026-06-12 10:28:47 +00:00
parent 795869fab4
commit 31cdb257fb
4 changed files with 227 additions and 26 deletions

View File

@@ -18,6 +18,7 @@ import DsiOpex from "./pages/DsiOpex";
import Santinova from "./pages/Santinova";
import SoinsSante from "./pages/SoinsSante";
import StExupery from "./pages/StExupery";
import TableauBordFinance from "./pages/TableauBordFinance";
import { useAuth } from "./_core/hooks/useAuth";
import { DashboardLayoutSkeleton } from "./components/DashboardLayoutSkeleton";
@@ -70,6 +71,9 @@ function Router() {
<Route path="/parametres">
<ProtectedRoute component={Parametres} />
</Route>
<Route path="/tableau-bord-finance">
<ProtectedRoute component={TableauBordFinance} />
</Route>
<Route path="/404" component={NotFound} />
<Route component={NotFound} />

View File

@@ -32,6 +32,7 @@ import {
PanelLeftOpen,
TableProperties,
Upload,
LayoutDashboard,
} from 'lucide-react';
// Palette de couleurs par section principale
@@ -123,6 +124,7 @@ const NAV_SECTIONS: NavSection[] = [
{ id: 'budget2027', label: 'CAPEX - Construction', icon: FileText, path: '/budget2027' },
{ id: 'dsi-capex', label: 'CAPEX - Synthèse', icon: TableProperties, path: '/dsi-capex' },
{ id: 'dsi-opex', label: 'OPEX (Charges)', icon: TrendingDown, path: '/dsi-opex' },
{ id: 'tableau-bord-finance', label: 'Tableau de bord Finance', icon: LayoutDashboard, path: '/tableau-bord-finance' },
],
},
{
@@ -339,32 +341,6 @@ export function AppSidebar({ collapsed = false, onToggle }: AppSidebarProps) {
)}
</button>
{/* Utilisateur connecté + déconnexion */}
{user && (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-white/5 border border-white/10">
<div className="w-7 h-7 rounded-full bg-blue-500/40 flex items-center justify-center flex-shrink-0">
<User className="w-3.5 h-3.5 text-blue-200" />
</div>
{!collapsed && (
<>
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-white/90 truncate">
{user.firstName ? `${user.firstName} ${user.lastName || ''}`.trim() : user.login}
</p>
<p className="text-[10px] text-white/40 truncate capitalize">{user.role}</p>
</div>
<button
onClick={() => logout()}
title="Se déconnecter"
className="text-white/30 hover:text-white/80 transition-colors p-1 rounded"
>
<LogOut className="w-3.5 h-3.5" />
</button>
</>
)}
</div>
)}
<button
onClick={() => navigate(SETTINGS_ITEM.path)}
className={`w-full flex items-center gap-2.5 px-3 py-2.5 rounded-lg transition-all duration-150 text-left group ${
@@ -389,6 +365,35 @@ export function AppSidebar({ collapsed = false, onToggle }: AppSidebarProps) {
</>
)}
</button>
{/* Espace libre avant le nom de connexion */}
<div className="h-3" />
{/* Utilisateur connecté + déconnexion — toujours en dernier */}
{user && (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-white/5 border border-white/10">
<div className="w-7 h-7 rounded-full bg-blue-500/40 flex items-center justify-center flex-shrink-0">
<User className="w-3.5 h-3.5 text-blue-200" />
</div>
{!collapsed && (
<>
<div className="flex-1 min-w-0">
<p className="text-xs font-medium text-white/90 truncate">
{user.firstName ? `${user.firstName} ${user.lastName || ''}`.trim() : user.login}
</p>
<p className="text-[10px] text-white/40 truncate capitalize">{user.role}</p>
</div>
<button
onClick={() => logout()}
title="Se déconnecter"
className="text-white/30 hover:text-white/80 transition-colors p-1 rounded"
>
<LogOut className="w-3.5 h-3.5" />
</button>
</>
)}
</div>
)}
</div>
</aside>
);

View File

@@ -0,0 +1,180 @@
// TableauBordFinance.tsx — Tableau de bord Finance
// Permet d'accéder au dossier d'analyse budget sur le poste de l'utilisateur
import { useState } from 'react';
import { AppSidebar } from '../components/AppSidebar';
import {
LayoutDashboard,
FolderOpen,
Copy,
CheckCheck,
ExternalLink,
Info,
FolderSearch,
} from 'lucide-react';
const DEFAULT_PATH = 'C:\\Users\\o.pareige\\OneDrive - ITINOVA\\@ITINOVA\\Pilotage\\Budget\\Analyse budget de mon poste';
export default function TableauBordFinance() {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [copied, setCopied] = useState(false);
const [customPath, setCustomPath] = useState(DEFAULT_PATH);
const [editingPath, setEditingPath] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(customPath);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// fallback
const el = document.createElement('textarea');
el.value = customPath;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
// Construire l'URL file:// pour tenter l'ouverture directe
// Remplace les backslashes par des slashes et encode les espaces
const fileUrl = 'file:///' + customPath.replace(/\\/g, '/');
return (
<div className="min-h-screen flex bg-background">
<AppSidebar collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(c => !c)} />
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
{/* En-tête */}
<header className="bg-card border-b border-border px-6 py-4 flex-shrink-0">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-lg bg-blue-500/20 flex items-center justify-center">
<LayoutDashboard className="w-5 h-5 text-blue-500" />
</div>
<div>
<h1 className="text-xl font-bold text-foreground" style={{ fontFamily: 'Sora, sans-serif' }}>
Tableau de bord Finance
</h1>
<p className="text-sm text-muted-foreground mt-0.5">
Accès aux dossiers d'analyse budgétaire
</p>
</div>
</div>
</header>
{/* Contenu */}
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="max-w-2xl mx-auto space-y-6">
{/* Carte principale — dossier OneDrive */}
<div className="bg-card border border-border rounded-xl shadow-sm overflow-hidden">
<div className="px-5 py-4 border-b border-border flex items-center gap-3">
<FolderSearch className="w-5 h-5 text-blue-500 flex-shrink-0" />
<div>
<h2 className="text-sm font-semibold text-foreground">Analyse budget de mon poste</h2>
<p className="text-xs text-muted-foreground mt-0.5">Dossier OneDrive — Pilotage Budget</p>
</div>
</div>
<div className="px-5 py-4 space-y-4">
{/* Chemin du dossier */}
<div>
<label className="text-xs font-medium text-muted-foreground uppercase tracking-wide block mb-1.5">
Chemin du dossier
</label>
{editingPath ? (
<div className="flex gap-2">
<input
type="text"
value={customPath}
onChange={e => setCustomPath(e.target.value)}
className="flex-1 text-sm font-mono bg-muted/50 border border-border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary"
autoFocus
/>
<button
onClick={() => setEditingPath(false)}
className="px-3 py-2 text-xs font-medium bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
>
OK
</button>
</div>
) : (
<div
className="flex items-center gap-2 bg-muted/40 border border-border rounded-lg px-3 py-2.5 cursor-pointer group hover:bg-muted/70 transition-colors"
onClick={() => setEditingPath(true)}
title="Cliquer pour modifier le chemin"
>
<FolderOpen className="w-4 h-4 text-amber-500 flex-shrink-0" />
<span className="flex-1 text-xs font-mono text-foreground/80 truncate">{customPath}</span>
<span className="text-[10px] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">modifier</span>
</div>
)}
</div>
{/* Actions */}
<div className="flex flex-col sm:flex-row gap-3">
{/* Bouton copier le chemin */}
<button
onClick={handleCopy}
className={`flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all duration-150 flex-1 ${
copied
? 'bg-emerald-500/20 text-emerald-600 border border-emerald-500/30'
: 'bg-muted/50 border border-border text-foreground hover:bg-muted hover:border-border/80'
}`}
>
{copied ? (
<>
<CheckCheck className="w-4 h-4" />
Chemin copié !
</>
) : (
<>
<Copy className="w-4 h-4" />
Copier le chemin
</>
)}
</button>
{/* Bouton ouvrir dans l'explorateur */}
<a
href={fileUrl}
className="flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium bg-blue-500/15 border border-blue-500/30 text-blue-600 hover:bg-blue-500/25 transition-all duration-150 flex-1"
title="Tenter d'ouvrir le dossier (selon le navigateur)"
>
<ExternalLink className="w-4 h-4" />
Ouvrir le dossier
</a>
</div>
{/* Instructions */}
<div className="bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800/50 rounded-lg px-4 py-3 flex gap-3">
<Info className="w-4 h-4 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
<div className="text-xs text-amber-800 dark:text-amber-300 space-y-1">
<p className="font-semibold">Comment accéder au dossier</p>
<p>
<strong>Option 1 :</strong> Cliquez sur <em>"Ouvrir le dossier"</em> fonctionne sur certains navigateurs (Edge, Firefox) si le dossier est synchronisé localement.
</p>
<p>
<strong>Option 2 :</strong> Copiez le chemin puis collez-le dans la barre d'adresse de l'Explorateur Windows (<kbd className="bg-amber-100 dark:bg-amber-900 px-1 rounded">Win + E</kbd>, puis collez dans la barre d'adresse).
</p>
<p>
<strong>Option 3 :</strong> Ouvrez directement OneDrive et naviguez vers <em>@ITINOVA Pilotage Budget Analyse budget de mon poste</em>.
</p>
</div>
</div>
</div>
</div>
{/* Note de personnalisation */}
<p className="text-xs text-muted-foreground text-center">
Le chemin peut être modifié en cliquant dessus il sera conservé pour cette session.
</p>
</div>
</div>
</main>
</div>
);
}

12
todo.md
View File

@@ -65,3 +65,15 @@
## Corrections (post-checkpoint eb1684f0)
- [x] Bug : lignes parasites (catégories Infogérance, Sécurité, TOTAL, etc.) dans la vue Établissements OPEX — suppression en BDD + filtre anti-parasites dans l'import
## Améliorations (post-checkpoint 60fd2b89)
- [x] Mode tout manuel : cellules des postes automatiques rendues éditables pour les établissements en mode manuel
- [x] Pré-remplissage des montants précalculés lors du passage en mode tout manuel (batchSetMontantsEtab)
- [x] Tri par code établissement dans la vue Établissements OPEX (colonne Code cliquable, tri par défaut)
## Évolutions menu et navigation (12/06/2026)
- [x] Menu latéral : afficher le nom de l'utilisateur connecté en dernier (tout en bas)
- [x] Menu latéral : ajouter un espace libre entre "Paramètres" et le nom de connexion
- [x] Menu latéral : ajouter "Tableau de bord Finance" sous "OPEX (charges)"
- [x] Créer la page Tableau de bord Finance avec accès au dossier Windows OneDrive