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>
);
}