@@ -535,7 +642,7 @@ export default function Production() {
}}
>
-
+
Courbe de production détaillée
@@ -584,7 +691,7 @@ export default function Production() {
onClick={() => setIsInfoSectionExpanded(!isInfoSectionExpanded)}
>
-
+
Informations importantes
diff --git a/apps/web/src/pages/Simulator.tsx b/apps/web/src/pages/Simulator.tsx
index 979a5b7..36f5062 100644
--- a/apps/web/src/pages/Simulator.tsx
+++ b/apps/web/src/pages/Simulator.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo, useCallback } from 'react'
+import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { Calculator, AlertCircle, Loader2, ChevronDown, ChevronUp, FileDown, ArrowUpDown, ArrowUp, ArrowDown, Filter, Info, ArrowRight } from 'lucide-react'
import { useQuery, useQueryClient, useIsRestoring } from '@tanstack/react-query'
import { LoadingOverlay } from '@/components/LoadingOverlay'
@@ -13,6 +13,8 @@ import { logger } from '@/utils/logger'
import { ModernButton } from './Simulator/components/ModernButton'
import { useIsDemo } from '@/hooks/useIsDemo'
import { usePdlStore } from '@/stores/pdlStore'
+import { useDataFetchStore } from '@/stores/dataFetchStore'
+import { useUnifiedDataFetch } from '@/hooks/useUnifiedDataFetch'
// Helper function to check if a date is weekend (Saturday or Sunday)
function isWeekend(dateString: string): boolean {
@@ -125,8 +127,10 @@ export default function Simulator() {
const queryClient = useQueryClient()
const isRestoring = useIsRestoring()
const isDemo = useIsDemo()
+ const { setIsLoading } = useDataFetchStore()
+ const demoAutoFetchDone = useRef(false)
- // Fetch user's PDLs
+ // Fetch user's PDLs - with short staleTime to ensure provider changes are reflected
const { data: pdlsData, isLoading: pdlsLoading, error: pdlsError } = useQuery({
queryKey: ['pdls'],
queryFn: async () => {
@@ -139,6 +143,7 @@ export default function Simulator() {
logger.warn('[Simulator] Returning empty array, response was:', response)
return []
},
+ staleTime: 30 * 1000, // 30 seconds - same as Dashboard for consistency
})
// Fetch energy providers and offers
@@ -255,6 +260,25 @@ export default function Simulator() {
const [sortBy, setSortBy] = useState<'total' | 'subscription' | 'energy'>('total')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
+ // Get PDL details for demo auto-fetch
+ const allPDLs: PDL[] = Array.isArray(pdlsData) ? pdlsData : []
+ const selectedPDLDetails = allPDLs.find(p => p.usage_point_id === selectedPdl)
+
+ // Hook for demo auto-fetch
+ const { fetchAllData } = useUnifiedDataFetch({
+ selectedPDL: selectedPdl,
+ selectedPDLDetails,
+ allPDLs,
+ pageContext: 'consumption',
+ })
+
+ // Check if data is in cache for auto-fetch logic
+ const hasDataInCache = useMemo(() => {
+ if (!selectedPdl) return false
+ const cachedData = queryClient.getQueryData(['consumptionDetail', selectedPdl]) as any
+ return cachedData?.data?.meter_reading?.interval_reading?.length > 0
+ }, [selectedPdl, queryClient, cachedConsumptionData])
+
// Set first active PDL as selected by default
useEffect(() => {
logger.log('[Simulator] pdlsData in useEffect:', pdlsData, 'isArray:', Array.isArray(pdlsData))
@@ -291,6 +315,22 @@ export default function Simulator() {
}
}, [selectedPdl, offersLoading, providersLoading, isConsumptionCacheLoading])
+ // Auto-fetch data for demo account
+ useEffect(() => {
+ if (isDemo && selectedPdl && selectedPDLDetails && !demoAutoFetchDone.current && !hasDataInCache && !isInitializing) {
+ logger.log('[DEMO] Auto-fetching data for demo account on Simulator')
+ demoAutoFetchDone.current = true
+ setTimeout(async () => {
+ setIsLoading(true)
+ try {
+ await fetchAllData()
+ } finally {
+ setIsLoading(false)
+ }
+ }, 300)
+ }
+ }, [isDemo, selectedPdl, selectedPDLDetails, hasDataInCache, isInitializing, setIsLoading, fetchAllData])
+
// Auto-collapse info section when simulation results are available
useEffect(() => {
if (simulationResult && Array.isArray(simulationResult) && simulationResult.length > 0) {
diff --git a/docs/pages/consumption.md b/docs/pages/consumption.md
index 5e92593..423e803 100644
--- a/docs/pages/consumption.md
+++ b/docs/pages/consumption.md
@@ -202,6 +202,78 @@ Page permettant aux utilisateurs de **visualiser et analyser leur consommation
- lucide-react pour les icônes
- react-hot-toast pour les notifications
+## Design visuel
+
+### Couleurs des sections
+
+Chaque section utilise une icône colorée distinctive :
+
+| Section | Icône | Couleur |
+|---------|-------|---------|
+| Statistiques de consommation | ⚡ Zap | Ambre (`amber-500`) |
+| Graphiques de consommation | 📊 BarChart3 | Émeraude (`emerald-500`) |
+| Courbe de charge détaillée | 📈 LineChart | Indigo (`indigo-500`) |
+| Pics de puissance maximale | 📉 TrendingUp | Rouge (`red-500`) |
+
+### Cartes statistiques annuelles
+
+Les cartes `YearlyStatCards` utilisent des gradients colorés rotatifs :
+
+1. **Bleu → Indigo** : `from-blue-50 to-indigo-100`
+2. **Émeraude → Teal** : `from-emerald-50 to-teal-100`
+3. **Violet → Violet** : `from-purple-50 to-violet-100`
+4. **Ambre → Orange** : `from-amber-50 to-orange-100`
+
+Chaque carte affiche :
+- Icône Zap colorée
+- Année et période (dates)
+- Consommation en kWh
+- Comparaison année précédente (tendance haut/bas)
+
+### Sélecteurs d'années (tabs)
+
+Les sélecteurs d'années utilisent les mêmes couleurs que les graphiques associés :
+
+**Style actif :**
+```css
+background-color: rgba(couleur, 0.125);
+border-color: couleur;
+color: couleur;
+```
+
+**Style inactif :**
+```css
+text-gray-400 border-gray-700
+hover:text-gray-200 hover:border-gray-600
+```
+
+**Couleurs par composant :**
+
+| Composant | Couleurs des tabs |
+|-----------|-------------------|
+| AnnualCurve | `#3B82F6` (bleu), `#10B981` (émeraude), `#F59E0B` (ambre), `#8B5CF6` (violet) |
+| PowerPeaks | `#EF4444` (rouge), `#F59E0B` (ambre), `#10B981` (émeraude), `#8B5CF6` (violet) |
+| MonthlyHcHp | Nuances de bleu (`#3B82F6`, `#93C5FD`, `#60A5FA`, `#2563EB`) |
+| HcHpDistribution | Nuances de bleu (`#60A5FA`, `#3B82F6`, `#93C5FD`, `#2563EB`) |
+
+### Camembert HC/HP
+
+Le camembert de répartition HC/HP utilise des couleurs semi-transparentes :
+
+- **Heures Creuses (HC)** : `rgba(96, 165, 250, 0.6)` (bleu-400 avec 60% opacité)
+- **Heures Pleines (HP)** : `rgba(251, 146, 60, 0.6)` (orange-400 avec 60% opacité)
+
+### Graphiques avec gradients
+
+Les conteneurs de graphiques utilisent des gradients subtils :
+
+| Composant | Gradient |
+|-----------|----------|
+| YearlyConsumption | `from-sky-50 to-blue-100` |
+| AnnualCurve | `from-teal-50 to-emerald-100` |
+| MonthlyHcHp | `from-indigo-50 to-cyan-100` |
+| PowerPeaks | `from-red-50 to-orange-100` |
+
## Fichiers lies
- **Frontend** : `apps/web/src/pages/ConsumptionKwh/` (dossier avec composants)
@@ -259,8 +331,7 @@ Page permettant aux utilisateurs de **visualiser et analyser leur consommation
apps/web/src/pages/ConsumptionKwh/
├── index.tsx # Page principale
├── components/
-│ ├── AnnualCurve.tsx # Courbe annuelle
-│ ├── ConfirmModal.tsx # Modal de confirmation
+│ ├── AnnualCurve.tsx # Courbe annuelle avec sélecteurs colorés
│ ├── DataFetchSection.tsx # Section recuperation donnees
│ ├── HcHpDistribution.tsx # Repartition HC/HP (camemberts)
│ ├── InfoBlock.tsx # Bloc d'information
@@ -268,9 +339,9 @@ apps/web/src/pages/ConsumptionKwh/
│ ├── ModernButton.tsx # Bouton moderne
│ ├── MonthlyHcHp.tsx # HC/HP mensuel (barres)
│ ├── PDLSelector.tsx # Selecteur PDL
-│ ├── PowerPeaks.tsx # Pics de puissance
-│ ├── YearlyConsumption.tsx # Comparaison mensuelle
-│ └── YearlyStatCards.tsx # Cartes annuelles
+│ ├── PowerPeaks.tsx # Pics de puissance avec sélecteurs colorés
+│ ├── YearlyConsumption.tsx # Comparaison mensuelle avec gradient
+│ └── YearlyStatCards.tsx # Cartes annuelles avec gradients colorés
├── hooks/
│ ├── useConsumptionCalcs.ts # Calculs consommation
│ ├── useConsumptionData.ts # Gestion donnees
diff --git a/docs/pages/dashboard.md b/docs/pages/dashboard.md
index 9fe2dbc..2175f5c 100644
--- a/docs/pages/dashboard.md
+++ b/docs/pages/dashboard.md
@@ -406,3 +406,107 @@ Cette fonctionnalité pose les bases pour :
- Un PDL de production peut être lié à plusieurs PDL de consommation
- Compatible SQLite et PostgreSQL
- Aucune donnée n'est copiée, seul le lien (UUID) est stocké
+
+---
+
+## 💰 Fonctionnalité : Sélection de l'offre tarifaire
+
+### Description
+
+Cette fonctionnalité permet aux utilisateurs de **sélectionner leur offre tarifaire actuelle** pour chaque PDL. L'offre sélectionnée est utilisée dans le simulateur pour comparer avec d'autres offres disponibles.
+
+### Interface utilisateur
+
+#### 1. Sélecteur d'offre dans PDLCard
+
+Le composant `OfferSelector` affiche 3 sélecteurs sur une seule ligne :
+
+| Sélecteur | Description |
+|-----------|-------------|
+| **Fournisseur** | Liste des fournisseurs d'énergie (EDF, Enercoop, TotalEnergies, etc.) |
+| **Type** | Type d'offre (Base, Heures Creuses, Tempo, EJP, Weekend, Saisonnier) |
+| **Offre** | Nom de l'offre spécifique |
+
+#### 2. Affichage des prix détaillés
+
+Une fois l'offre sélectionnée, un bloc récapitulatif s'affiche avec :
+
+- **En-tête** : Fournisseur - Nom de l'offre + badge du type
+- **Abonnement** : Prix mensuel en €/mois
+- **Puissance** : Si spécifiée dans l'offre (kVA)
+- **Prix détaillés** : Tous les prix selon le type d'offre (en €/kWh)
+- **Date de mise à jour** : Dernière actualisation des tarifs
+
+#### 3. Types d'offres et prix affichés
+
+| Type | Prix affichés |
+|------|---------------|
+| **BASE** | Prix kWh unique |
+| **HC_HP** | Heures Pleines, Heures Creuses |
+| **TEMPO** | Bleu HP/HC, Blanc HP/HC, Rouge HP/HC (6 prix) |
+| **EJP** | Jours normaux, Jours de pointe |
+| **WEEKEND** | Semaine HP/HC, Week-end HP/HC |
+| **SEASONAL** | Hiver HP/HC, Été HP/HC, Jours de pointe |
+
+#### 4. Codes couleur des prix
+
+- 🔵 **Bleu** : Jours Tempo Bleus
+- ⚪ **Gris** : Jours Tempo Blancs
+- 🔴 **Rouge** : Jours Tempo Rouges / Jours de pointe
+- 🟣 **Violet** : Week-end
+- 🔷 **Cyan** : Hiver (offres saisonnières)
+- 🟠 **Ambre** : Été (offres saisonnières)
+
+### API Backend
+
+**Endpoint :**
+
+```http
+PATCH /api/pdl/{pdl_id}/offer
+Content-Type: application/json
+
+{
+ "selected_offer_id": "uuid-de-l-offre" | null
+}
+```
+
+### Fichiers impactés
+
+**Frontend :**
+- [apps/web/src/components/OfferSelector.tsx](../../apps/web/src/components/OfferSelector.tsx) : Composant de sélection
+- [apps/web/src/components/PDLCard.tsx](../../apps/web/src/components/PDLCard.tsx) : Intégration du sélecteur
+- [apps/web/src/api/energy.ts](../../apps/web/src/api/energy.ts) : Types EnergyOffer, EnergyProvider
+- [apps/web/src/api/pdl.ts](../../apps/web/src/api/pdl.ts) : Méthode `updateSelectedOffer`
+
+**Backend :**
+- [apps/api/src/models/pdl.py](../../apps/api/src/models/pdl.py) : Champ `selected_offer_id`
+- [apps/api/src/routers/pdl.py](../../apps/api/src/routers/pdl.py) : Endpoint de mise à jour
+
+### Utilisation
+
+**Pour l'utilisateur :**
+
+1. Dans la carte PDL, section "Offre tarifaire"
+2. Sélectionner le fournisseur
+3. Sélectionner le type d'offre
+4. Sélectionner l'offre spécifique
+5. Le récapitulatif des prix s'affiche automatiquement
+6. Cliquer sur ✕ pour effacer la sélection
+
+### Design
+
+- 3 sélecteurs alignés sur une ligne (`grid-cols-3`)
+- Labels compacts avec icônes (Building2, Zap, Tag)
+- Bloc récapitulatif en fond bleu clair
+- Prix en €/kWh avec 4 décimales
+- Abonnement en €/mois avec 2 décimales
+- Support du mode sombre
+- Responsive (s'adapte aux petits écrans)
+
+### Notes techniques
+
+- Les offres sont filtrées par puissance souscrite du PDL
+- Seules les offres actives (`is_active = true`) sont affichées
+- Les sélecteurs sont en cascade : Type dépend du Fournisseur, Offre dépend du Type
+- La sélection est persistée immédiatement via mutation React Query
+- Le cache des offres est conservé 5 minutes (`staleTime`)