Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import AdminRoles from './pages/AdminRoles'
import AdminLogs from './pages/AdminLogs'
import Tempo from './pages/Tempo'
import EcoWatt from './pages/EcoWatt'
import Consumption from './pages/Consumption'
import ConsumptionKwh from './pages/ConsumptionKwh'
import ConsumptionEuro from './pages/ConsumptionEuro'
import Production from './pages/Production'
import FAQ from './pages/FAQ'
import ApiDocs from './pages/ApiDocs'
Expand Down Expand Up @@ -191,12 +192,27 @@ function App() {
</ProtectedRoute>
}
/>
{/* Consumption routes with submenu */}
<Route
path="/consumption"
element={<Navigate to="/consumption_kwh" replace />}
/>
<Route
path="/consumption_kwh"
element={
<ProtectedRoute>
<Layout>
<ConsumptionKwh />
</Layout>
</ProtectedRoute>
}
/>
<Route
path="/consumption_euro"
element={
<ProtectedRoute>
<Layout>
<Consumption />
<ConsumptionEuro />
</Layout>
</ProtectedRoute>
}
Expand Down
42 changes: 42 additions & 0 deletions apps/web/src/components/ConsumptionTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Link, useLocation } from 'react-router-dom'
import { Zap, Euro } from 'lucide-react'

interface Tab {
name: string
path: string
icon: typeof Zap
}

const tabs: Tab[] = [
{ name: 'kWh', path: '/consumption_kwh', icon: Zap },
{ name: 'Euro', path: '/consumption_euro', icon: Euro },
]

export default function ConsumptionTabs() {
const location = useLocation()

return (
<div className="w-full border-b border-gray-200 dark:border-gray-700 overflow-x-auto bg-white dark:bg-gray-800">
<nav className="flex justify-center gap-1 min-w-max px-3 sm:px-4 lg:px-6" aria-label="Tabs">
{tabs.map((tab) => {
const isActive = location.pathname === tab.path
const Icon = tab.icon
return (
<Link
key={tab.path}
to={tab.path}
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
isActive
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
<Icon size={16} />
{tab.name}
</Link>
)
})}
</nav>
</div>
)
}
91 changes: 54 additions & 37 deletions apps/web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useState, useEffect } from 'react'
import toast, { Toaster } from 'react-hot-toast'
import AdminTabs from './AdminTabs'
import ApiDocsTabs from './ApiDocsTabs'
import ConsumptionTabs from './ConsumptionTabs'
import PageHeader from './PageHeader'
import { PageTransition } from './PageTransition'
import { useQueryClient } from '@tanstack/react-query'
Expand Down Expand Up @@ -34,14 +35,17 @@ export default function Layout({ children }: { children: React.ReactNode }) {
// Menu items
const menuItems = [
{ to: '/dashboard', icon: Home, label: 'Tableau de bord' },
{ to: '/consumption', icon: TrendingUp, label: 'Consommation' },
{ to: '/consumption_kwh', icon: TrendingUp, label: 'Consommation' },
{ to: '/production', icon: Sun, label: 'Production' },
{ to: '/simulator', icon: Calculator, label: 'Simulateur' },
{ to: '/contribute', icon: Users, label: 'Contribuer' },
{ to: '/tempo', icon: Calendar, label: 'Tempo' },
{ to: '/ecowatt', icon: Zap, label: 'EcoWatt' },
]

// Check if we're on a consumption page (for active state and tabs)
const isConsumptionPage = location.pathname.startsWith('/consumption')

// Clear cache function (admin only)
const handleClearCache = async () => {
if (!user?.is_admin) {
Expand Down Expand Up @@ -154,26 +158,32 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-4">
<div className="space-y-1 px-2">
{menuItems.map((item) => (
<Link
key={item.to}
to={item.to}
className={`flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors ${
location.pathname === item.to
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400'
: 'hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
title={sidebarCollapsed ? item.label : ''}
data-tour={item.to === '/consumption' ? 'nav-consumption' :
item.to === '/simulator' ? 'nav-simulator' :
item.to === '/contribute' ? 'nav-contribute' : undefined}
>
<item.icon size={20} className="flex-shrink-0" />
{!sidebarCollapsed && (
<span className="font-medium">{item.label}</span>
)}
</Link>
))}
{menuItems.map((item) => {
// Special handling for consumption - active if any consumption page
const isActive = item.to === '/consumption_kwh'
? isConsumptionPage
: location.pathname === item.to
return (
<Link
key={item.to}
to={item.to}
className={`flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors ${
isActive
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400'
: 'hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
title={sidebarCollapsed ? item.label : ''}
data-tour={item.to === '/consumption_kwh' ? 'nav-consumption' :
item.to === '/simulator' ? 'nav-simulator' :
item.to === '/contribute' ? 'nav-contribute' : undefined}
>
<item.icon size={20} className="flex-shrink-0" />
{!sidebarCollapsed && (
<span className="font-medium">{item.label}</span>
)}
</Link>
)
})}

{/* Admin Link */}
{canAccessAdmin() && (
Expand Down Expand Up @@ -326,21 +336,27 @@ export default function Layout({ children }: { children: React.ReactNode }) {
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-4">
<div className="space-y-1 px-2">
{menuItems.map((item) => (
<Link
key={item.to}
to={item.to}
onClick={() => setMobileMenuOpen(false)}
className={`flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors ${
location.pathname === item.to
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400'
: 'hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
<item.icon size={20} />
<span className="font-medium">{item.label}</span>
</Link>
))}
{menuItems.map((item) => {
// Special handling for consumption - active if any consumption page
const isActive = item.to === '/consumption_kwh'
? isConsumptionPage
: location.pathname === item.to
return (
<Link
key={item.to}
to={item.to}
onClick={() => setMobileMenuOpen(false)}
className={`flex items-center gap-3 px-3 py-2.5 rounded-md transition-colors ${
isActive
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400'
: 'hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
>
<item.icon size={20} />
<span className="font-medium">{item.label}</span>
</Link>
)
})}

{canAccessAdmin() && (
<>
Expand Down Expand Up @@ -459,11 +475,12 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<PageHeader />
{location.pathname.startsWith('/admin') && <AdminTabs />}
{location.pathname.startsWith('/api-docs') && <ApiDocsTabs />}
{isConsumptionPage && <ConsumptionTabs />}
</div>

{/* Main Content */}
<main className={`flex-1 bg-gray-50 dark:bg-gray-900 ${isAdminLogsPage ? 'overflow-hidden' : 'overflow-y-auto'}`}>
<div className={`container mx-auto px-3 sm:px-4 lg:px-6 max-w-[1920px] ${isAdminLogsPage ? 'h-full pb-0' : 'pb-6'} ${(location.pathname.startsWith('/admin') || location.pathname.startsWith('/api-docs')) ? 'pt-4' : ''}`}>
<div className={`container mx-auto px-3 sm:px-4 lg:px-6 max-w-[1920px] ${isAdminLogsPage ? 'h-full pb-0' : 'pb-6'} ${(location.pathname.startsWith('/admin') || location.pathname.startsWith('/api-docs') || isConsumptionPage) ? 'pt-4' : ''}`}>
<PageTransition key={location.pathname}>
{children}
</PageTransition>
Expand Down
14 changes: 9 additions & 5 deletions apps/web/src/components/PageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { TrendingUp, Sun, Calculator, Download, Lock, LayoutDashboard, Calendar, Zap, Users, AlertCircle, BookOpen, Settings as SettingsIcon, Key, Shield, FileText, Activity } from 'lucide-react'
import { TrendingUp, Sun, Calculator, Download, Lock, LayoutDashboard, Calendar, Zap, Users, AlertCircle, BookOpen, Settings as SettingsIcon, Key, Shield, FileText, Activity, Euro } from 'lucide-react'
import { useQuery } from '@tanstack/react-query'
import { pdlApi } from '@/api/pdl'
import { usePdlStore } from '@/stores/pdlStore'
Expand All @@ -12,15 +12,16 @@ import type { PDL } from '@/types/api'

// Pages qui affichent le sélecteur de PDL avec bouton "Récupérer"
const PDL_SELECTOR_PAGES = [
'/consumption', '/production', '/simulator', '/dashboard', '/tempo', '/ecowatt', '/contribute',
'/consumption_kwh', '/consumption_euro', '/production', '/simulator', '/dashboard', '/tempo', '/ecowatt', '/contribute',
'/faq', '/api-docs', '/api-docs/auth', '/settings',
'/admin', '/admin/users', '/admin/tempo', '/admin/ecowatt', '/admin/contributions', '/admin/offers', '/admin/roles', '/admin/logs', '/admin/add-pdl'
]

// Configuration des titres et icônes par page
const PAGE_CONFIG: Record<string, { title: string; icon: typeof TrendingUp; subtitle?: string }> = {
'/dashboard': { title: 'Tableau de bord', icon: LayoutDashboard, subtitle: 'Gérez vos points de livraison' },
'/consumption': { title: 'Consommation', icon: TrendingUp, subtitle: 'Visualisez et analysez votre consommation électrique' },
'/consumption_kwh': { title: 'Consommation', icon: TrendingUp, subtitle: 'Visualisez et analysez votre consommation électrique en kWh' },
'/consumption_euro': { title: 'Consommation', icon: Euro, subtitle: 'Visualisez et analysez le coût de votre consommation en euros' },
'/production': { title: 'Production', icon: Sun, subtitle: 'Visualisez et analysez votre production d\'énergie solaire' },
'/simulator': { title: 'Comparateur des abonnements', icon: Calculator, subtitle: 'Comparez automatiquement le coût de toutes les offres disponibles' },
'/tempo': { title: 'Calendrier Tempo', icon: Calendar, subtitle: 'Historique des jours Tempo bleus, blancs et rouges fourni par RTE' },
Expand Down Expand Up @@ -107,6 +108,9 @@ export default function PageHeader() {
const Icon = config.icon
const activePdls = pdls.filter((p: PDL) => p.is_active)

// Check if on a consumption page
const isConsumptionPage = location.pathname.startsWith('/consumption')

// Filter PDLs based on page
const displayedPdls = location.pathname === '/production'
? (() => {
Expand All @@ -130,7 +134,7 @@ export default function PageHeader() {

return [...consumptionWithProduction, ...standaloneProduction]
})()
: location.pathname === '/consumption'
: isConsumptionPage
? activePdls.filter((pdl: PDL) => pdl.has_consumption)
: activePdls

Expand Down Expand Up @@ -159,7 +163,7 @@ export default function PageHeader() {
<div className="text-sm text-gray-600 dark:text-gray-400 italic">
{location.pathname === '/production'
? 'Aucun PDL de production non lié trouvé'
: location.pathname === '/consumption'
: isConsumptionPage
? 'Aucun PDL avec l\'option consommation activée'
: 'Aucun point de livraison actif trouvé'}
</div>
Expand Down
67 changes: 67 additions & 0 deletions apps/web/src/pages/ConsumptionEuro/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState, useEffect } from 'react'
import { Euro, Database, ArrowRight, AlertCircle } from 'lucide-react'
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import Database.

Suggested change
import { Euro, Database, ArrowRight, AlertCircle } from 'lucide-react'
import { Euro, ArrowRight, AlertCircle } from 'lucide-react'

Copilot uses AI. Check for mistakes.
import { usePdlStore } from '@/stores/pdlStore'

export default function ConsumptionEuro() {
const { selectedPdl: selectedPDL } = usePdlStore()
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable selectedPDL.

Suggested change
const { selectedPdl: selectedPDL } = usePdlStore()

Copilot uses AI. Check for mistakes.
const [isDarkMode, setIsDarkMode] = useState(false)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable isDarkMode.

Suggested change
const [isDarkMode, setIsDarkMode] = useState(false)
const [, setIsDarkMode] = useState(false)

Copilot uses AI. Check for mistakes.

// Detect dark mode
useEffect(() => {
const checkDarkMode = () => {
setIsDarkMode(document.documentElement.classList.contains('dark'))
}
checkDarkMode()
const observer = new MutationObserver(checkDarkMode)
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
})
return () => observer.disconnect()
}, [])

return (
<div className="w-full">
{/* Coming Soon / Work in Progress State */}
<div className="rounded-xl shadow-md border bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-700 transition-colors duration-200">
<div className="flex flex-col items-center justify-center py-16 px-6 text-center">
<div className="w-20 h-20 rounded-full bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center mb-6">
<Euro className="w-10 h-10 text-amber-600 dark:text-amber-400" />
</div>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Consommation en Euros
</h3>
<p className="text-gray-600 dark:text-gray-400 max-w-md mb-6">
Cette fonctionnalité est en cours de développement. Elle vous permettra de visualiser
votre consommation électrique convertie en euros selon les tarifs de votre fournisseur.
</p>

<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 max-w-lg">
<div className="flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div className="text-left">
<p className="text-sm text-blue-800 dark:text-blue-200 font-medium mb-1">
Fonctionnalités à venir :
</p>
<ul className="text-sm text-blue-700 dark:text-blue-300 list-disc list-inside space-y-1">
<li>Conversion de la consommation kWh en euros</li>
<li>Application des tarifs HC/HP selon votre abonnement</li>
<li>Comparaison des coûts par période</li>
<li>Estimation de la facture mensuelle</li>
</ul>
</div>
</div>
</div>

<div className="mt-6 flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
<span>Récupérez vos données kWh</span>
<ArrowRight className="w-4 h-4" />
<span>Sélectionnez une offre tarifaire</span>
<ArrowRight className="w-4 h-4" />
<span>Visualisez vos coûts</span>
</div>
</div>
</div>
</div>
)
}
Loading
Loading