@@ -166,6 +166,56 @@ export default function Simulator() {
166166 // Selected PDL from global store
167167 const { selectedPdl, setSelectedPdl } = usePdlStore ( )
168168
169+ // HYBRID APPROACH for consumptionDetail: useQuery creates the cache entry for persistence,
170+ // but we read data via subscription to avoid race conditions with IndexedDB hydration
171+ const { isLoading : isConsumptionCacheLoading } = useQuery ( {
172+ queryKey : [ 'consumptionDetail' , selectedPdl ] ,
173+ queryFn : async ( ) => {
174+ // Read from cache - this makes the query succeed with status: 'success'
175+ // Data is written to cache via setQueryData in useUnifiedDataFetch
176+ const cachedData = queryClient . getQueryData ( [ 'consumptionDetail' , selectedPdl ] )
177+ return cachedData || null
178+ } ,
179+ enabled : ! ! selectedPdl ,
180+ staleTime : Infinity , // Never refetch - data only comes from setQueryData
181+ gcTime : 1000 * 60 * 60 * 24 * 7 , // 7 days
182+ refetchOnMount : false ,
183+ refetchOnWindowFocus : false ,
184+ refetchOnReconnect : false ,
185+ } )
186+
187+ // Read consumptionDetail data via direct cache access + subscription
188+ const [ cachedConsumptionData , setCachedConsumptionData ] = useState < any > ( null )
189+
190+ useEffect ( ( ) => {
191+ if ( ! selectedPdl ) {
192+ setCachedConsumptionData ( null )
193+ return
194+ }
195+
196+ // Read current data from cache (includes persisted data after hydration)
197+ const initialData = queryClient . getQueryData ( [ 'consumptionDetail' , selectedPdl ] )
198+ if ( initialData ) {
199+ logger . log ( '[Simulator] Initial cache data found:' , ! ! initialData )
200+ setCachedConsumptionData ( initialData )
201+ }
202+
203+ // Subscribe to future changes (when setQueryData is called or cache hydrates)
204+ const unsubscribe = queryClient . getQueryCache ( ) . subscribe ( ( event ) => {
205+ if (
206+ event ?. type === 'updated' &&
207+ event ?. query ?. queryKey ?. [ 0 ] === 'consumptionDetail' &&
208+ event ?. query ?. queryKey ?. [ 1 ] === selectedPdl
209+ ) {
210+ const updatedData = queryClient . getQueryData ( [ 'consumptionDetail' , selectedPdl ] )
211+ logger . log ( '[Simulator] Cache updated:' , ! ! updatedData )
212+ setCachedConsumptionData ( updatedData )
213+ }
214+ } )
215+
216+ return ( ) => unsubscribe ( )
217+ } , [ selectedPdl , queryClient ] )
218+
169219 // Simulation state
170220 const [ isSimulating , setIsSimulating ] = useState ( false )
171221 const [ simulationResult , setSimulationResult ] = useState < any > ( null )
@@ -213,18 +263,18 @@ export default function Simulator() {
213263 setIsInitializing ( true )
214264 } , [ selectedPdl ] )
215265
216- // End initialization when required data is loaded (offers and providers )
266+ // End initialization when required data is loaded (offers, providers, and cache hydration )
217267 // This ensures auto-launch can check cache properly before showing empty state
218268 useEffect ( ( ) => {
219- // Wait for offers and providers to finish loading
220- if ( ! offersLoading && ! providersLoading ) {
221- // Small delay to allow cache hydration to complete
269+ // Wait for all data sources to finish loading
270+ if ( ! offersLoading && ! providersLoading && ! isConsumptionCacheLoading ) {
271+ // Small delay to allow any final state updates
222272 const timer = setTimeout ( ( ) => {
223273 setIsInitializing ( false )
224274 } , 50 )
225275 return ( ) => clearTimeout ( timer )
226276 }
227- } , [ selectedPdl , offersLoading , providersLoading ] )
277+ } , [ selectedPdl , offersLoading , providersLoading , isConsumptionCacheLoading ] )
228278
229279 // Auto-collapse info section when simulation results are available
230280 useEffect ( ( ) => {
@@ -271,12 +321,11 @@ export default function Simulator() {
271321
272322 logger . log ( `Loading consumption data from cache: ${ startDate } to ${ endDate } ` )
273323
274- // Get all data from the single cache key (new format)
275- const cachedData = queryClient . getQueryData ( [ 'consumptionDetail' , selectedPdl ] ) as any
324+ // Use cachedConsumptionData from state (already hydrated from IndexedDB via subscription)
276325 let allPoints : any [ ] = [ ]
277326
278- if ( cachedData ?. data ?. meter_reading ?. interval_reading ) {
279- const readings = cachedData . data . meter_reading . interval_reading
327+ if ( cachedConsumptionData ?. data ?. meter_reading ?. interval_reading ) {
328+ const readings = cachedConsumptionData . data . meter_reading . interval_reading
280329
281330 // Filter readings to the desired date range (rolling year)
282331 allPoints = readings . filter ( ( point : any ) => {
@@ -364,7 +413,7 @@ export default function Simulator() {
364413 } finally {
365414 setIsSimulating ( false )
366415 }
367- } , [ selectedPdl , pdlsData , offersData , providersData , queryClient ] )
416+ } , [ selectedPdl , pdlsData , offersData , providersData , cachedConsumptionData ] )
368417
369418 const calculateSimulationsForAllOffers = ( consumptionData : any [ ] , offers : EnergyOffer [ ] , providers : EnergyProvider [ ] , tempoColors : TempoDay [ ] , pdl ?: PDL ) => {
370419 // Create a map of date -> TEMPO color for fast lookup
@@ -781,6 +830,8 @@ export default function Simulator() {
781830 providersDataLoaded : ! ! providersData ,
782831 offersLoading,
783832 providersLoading,
833+ isConsumptionCacheLoading,
834+ hasCachedData : ! ! cachedConsumptionData ,
784835 } )
785836
786837 // Don't auto-launch if already launched, simulating, or have results
@@ -789,8 +840,8 @@ export default function Simulator() {
789840 return
790841 }
791842
792- // Don't auto-launch while data is still loading
793- if ( offersLoading || providersLoading ) {
843+ // Don't auto-launch while data is still loading (including cache hydration)
844+ if ( offersLoading || providersLoading || isConsumptionCacheLoading ) {
794845 logger . log ( '[Auto-launch] Skipping auto-launch - still loading data' )
795846 return
796847 }
@@ -805,15 +856,13 @@ export default function Simulator() {
805856 return
806857 }
807858
808- // Check if we have cached data for this PDL (uses new single cache key format)
809- const cachedData = queryClient . getQueryData ( [ 'consumptionDetail' , selectedPdl ] ) as any
810-
811- if ( ! cachedData ?. data ?. meter_reading ?. interval_reading ?. length ) {
859+ // Use cachedConsumptionData from state (populated via subscription, handles IndexedDB hydration)
860+ if ( ! cachedConsumptionData ?. data ?. meter_reading ?. interval_reading ?. length ) {
812861 logger . log ( '[Auto-launch] ❌ No cached data found for PDL:' , selectedPdl )
813862 return
814863 }
815864
816- const readings = cachedData . data . meter_reading . interval_reading
865+ const readings = cachedConsumptionData . data . meter_reading . interval_reading
817866 const totalPoints = readings . length
818867
819868 // Check if we have enough data (at least 30 days worth = ~1440 points at 30min intervals)
@@ -835,7 +884,7 @@ export default function Simulator() {
835884 } else {
836885 logger . log ( `❌ Not enough cached data (${ totalPoints } points), skipping auto-launch` )
837886 }
838- } , [ selectedPdl , isSimulating , simulationResult , hasAutoLaunched , isDemo , pdlsData , offersData , providersData , offersLoading , providersLoading , queryClient , handleSimulation ] )
887+ } , [ selectedPdl , isSimulating , simulationResult , hasAutoLaunched , isDemo , pdlsData , offersData , providersData , offersLoading , providersLoading , isConsumptionCacheLoading , cachedConsumptionData , handleSimulation ] )
839888
840889 // Filter and sort simulation results
841890 // IMPORTANT: This hook must be before any early returns to respect React's rules of hooks
0 commit comments