Checkpoint: Ajout de la vue liste (tableau) en complément de la vue vignettes — toggle Vignettes/Liste dans la barre de filtres, tableau avec tri par colonne, barres de vétusté colorées, budget en orange/vert.

This commit is contained in:
Manus
2026-04-01 05:45:51 -04:00
parent 908cf74046
commit c4dce77ecc
2 changed files with 254 additions and 26 deletions

View File

@@ -1,5 +1,5 @@
// Page principale — Budget Renouvellement SI 2027 - Itinova
// Design: Corporate Modernism — Sidebar bleu marine + grille de cards
// Design: Corporate Modernism — Sidebar bleu marine + grille de cards + vue liste
import { useState } from 'react';
import {
@@ -15,14 +15,19 @@ import {
AlertTriangle,
CheckCircle,
ChevronDown,
LayoutGrid,
List,
} from 'lucide-react';
import { useBudgetData } from '../hooks/useBudgetData';
import { formatEuros, formatNumber } from '../lib/format';
import { EtablissementCard } from '../components/EtablissementCard';
import { EtablissementDetail } from '../components/EtablissementDetail';
import { EtablissementListView } from '../components/EtablissementListView';
import { KpiCard } from '../components/KpiCard';
import type { Etablissement, SortField, FilterType } from '../types/budget';
type ViewMode = 'grid' | 'list';
export default function Home() {
const {
meta,
@@ -39,6 +44,7 @@ export default function Home() {
const [selectedEtab, setSelectedEtab] = useState<Etablissement | null>(null);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [viewMode, setViewMode] = useState<ViewMode>('grid');
const sortOptions: { value: SortField; label: string }[] = [
{ value: 'budget_total', label: 'Budget total' },
@@ -194,7 +200,7 @@ export default function Home() {
</div>
</div>
{/* Barre de filtres */}
{/* Barre de filtres + toggle vue */}
<div className="px-6 py-3 border-b border-border bg-card flex-shrink-0">
<div className="flex flex-wrap items-center gap-3">
{/* Recherche */}
@@ -225,38 +231,71 @@ export default function Home() {
</select>
</div>
{/* Tri */}
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Trier par :</span>
<div className="relative">
<select
value={sortField}
onChange={(e) => toggleSort(e.target.value as SortField)}
className="pl-3 pr-8 py-2 text-sm bg-muted/50 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 appearance-none cursor-pointer"
{/* Tri (masqué en vue liste car tri par colonne) */}
{viewMode === 'grid' && (
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">Trier par :</span>
<div className="relative">
<select
value={sortField}
onChange={(e) => toggleSort(e.target.value as SortField)}
className="pl-3 pr-8 py-2 text-sm bg-muted/50 border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/30 appearance-none cursor-pointer"
>
{sortOptions.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<button
onClick={() => toggleSort(sortField)}
className="p-2 rounded-lg border border-border bg-muted/50 hover:bg-muted transition-colors"
title={sortOrder === 'asc' ? 'Croissant' : 'Décroissant'}
>
{sortOptions.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
{sortOrder === 'asc' ? (
<SortAsc className="w-4 h-4 text-muted-foreground" />
) : (
<SortDesc className="w-4 h-4 text-muted-foreground" />
)}
</button>
</div>
)}
{/* Spacer */}
<div className="flex-1" />
{/* Toggle vue vignettes / liste */}
<div className="flex items-center gap-1 bg-muted/50 border border-border rounded-lg p-1">
<button
onClick={() => toggleSort(sortField)}
className="p-2 rounded-lg border border-border bg-muted/50 hover:bg-muted transition-colors"
title={sortOrder === 'asc' ? 'Croissant' : 'Décroissant'}
onClick={() => setViewMode('grid')}
title="Vue vignettes"
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
viewMode === 'grid'
? 'bg-white shadow-sm text-primary border border-border'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{sortOrder === 'asc' ? (
<SortAsc className="w-4 h-4 text-muted-foreground" />
) : (
<SortDesc className="w-4 h-4 text-muted-foreground" />
)}
<LayoutGrid className="w-4 h-4" />
<span className="hidden sm:inline">Vignettes</span>
</button>
<button
onClick={() => setViewMode('list')}
title="Vue liste"
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all ${
viewMode === 'list'
? 'bg-white shadow-sm text-primary border border-border'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<List className="w-4 h-4" />
<span className="hidden sm:inline">Liste</span>
</button>
</div>
</div>
</div>
{/* Grille des établissements */}
{/* Contenu — vue vignettes ou liste */}
<div className="flex-1 overflow-y-auto px-6 py-5">
{etablissements.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-center">
@@ -266,7 +305,7 @@ export default function Home() {
Modifiez vos critères de recherche ou de filtre.
</p>
</div>
) : (
) : viewMode === 'grid' ? (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{etablissements.map((etab, index) => (
<EtablissementCard
@@ -277,6 +316,14 @@ export default function Home() {
/>
))}
</div>
) : (
<EtablissementListView
etablissements={etablissements}
onSelect={(etab) => setSelectedEtab(etab)}
sortField={sortField}
sortOrder={sortOrder}
onSort={toggleSort}
/>
)}
</div>
</main>