HeatTransPlan / frontend /src /store /analysisStore.ts
drzg15's picture
reparing app build
d19c379
/** Zustand store for Potential Analysis page state. */
import { create } from 'zustand';
import type {
PinchResult,
HPIResult,
StatusQuoResult,
EnergyDemandLocal,
} from '../types/analysis';
interface AnalysisStore {
// Stream selection: "stream_{procIdx}_{streamIdx}" → boolean
selectedStreams: Record<string, boolean>;
// Energy demands (user-entered)
energyDemands: EnergyDemandLocal[];
// Pinch controls
tMin: number;
showShifted: boolean;
// Results from backend
pinchResult: PinchResult | null;
hpiResult: HPIResult | null;
statusQuoResult: StatusQuoResult | null;
// Heat pump selection for chart
selectedHPTypes: string[];
// Loading states
pinchLoading: boolean;
hpiLoading: boolean;
// Scenarios: Array of saved states
scenarios: Array<{
id: string;
name: string;
selectedStreams: Record<string, boolean>;
energyDemands: EnergyDemandLocal[];
tMin: number;
// Results for comparison
pinchResult?: PinchResult | null;
hpiResult?: HPIResult | null;
statusQuoResult?: StatusQuoResult | null;
}>;
activeScenarioId: string | null;
// Actions
setSelectedStream: (key: string, val: boolean) => void;
setSelectedStreams: (streams: Record<string, boolean>) => void;
setStreamsForProcess: (pIdx: number, val: boolean, processes: any[]) => void;
toggleStreamSelection: (pIdx: number, sIdx: number) => void;
initSelectedStreams: (keys: string[]) => void;
setEnergyDemands: (demands: EnergyDemandLocal[]) => void;
addEnergyDemand: (demand: EnergyDemandLocal) => void;
removeEnergyDemand: (index: number) => void;
updateEnergyDemand: (index: number, demand: Partial<EnergyDemandLocal>) => void;
setTMin: (val: number) => void;
setShowShifted: (val: boolean) => void;
setPinchResult: (result: PinchResult | null) => void;
setHPIResult: (result: HPIResult | null) => void;
setStatusQuoResult: (result: StatusQuoResult | null) => void;
setSelectedHPTypes: (types: string[]) => void;
setPinchLoading: (val: boolean) => void;
setHPILoading: (val: boolean) => void;
saveScenario: (name?: string) => void;
loadScenario: (index: number) => void;
deleteScenario: (index: number) => void;
renameScenario: (index: number, newName: string) => void;
clearActiveScenario: () => void;
}
// Helper to update the scenario list if an active scenario is set
const syncScenario = (state: AnalysisStore) => {
if (!state.activeScenarioId) return {};
return {
scenarios: state.scenarios.map(sc =>
sc.id === state.activeScenarioId
? {
...sc,
selectedStreams: { ...state.selectedStreams },
energyDemands: JSON.parse(JSON.stringify(state.energyDemands)),
tMin: state.tMin,
pinchResult: state.pinchResult ? JSON.parse(JSON.stringify(state.pinchResult)) : null,
hpiResult: state.hpiResult ? JSON.parse(JSON.stringify(state.hpiResult)) : null,
statusQuoResult: state.statusQuoResult ? JSON.parse(JSON.stringify(state.statusQuoResult)) : null,
}
: sc
)
};
};
export const useAnalysisStore = create<AnalysisStore>((set) => ({
selectedStreams: {},
energyDemands: [
{
heat_demand: 0,
cooling_demand: 0,
selected_heat_streams: [],
selected_cooling_streams: [],
},
],
tMin: 10,
showShifted: false,
pinchResult: null,
hpiResult: null,
statusQuoResult: null,
selectedHPTypes: [],
pinchLoading: false,
hpiLoading: false,
scenarios: [],
activeScenarioId: null,
setSelectedStream: (key, val) => {
set((s) => ({ selectedStreams: { ...s.selectedStreams, [key]: val } }));
set((s) => syncScenario(s));
},
setSelectedStreams: (streams) => {
set({ selectedStreams: streams });
set((s) => syncScenario(s));
},
setStreamsForProcess: (pIdx, val, processes) => {
set((s) => {
const next = { ...s.selectedStreams };
const proc = processes[pIdx];
if (proc) {
(proc.streams || []).forEach((_: any, si: number) => {
next[`stream_${pIdx}_${si}`] = val;
});
}
return { selectedStreams: next };
});
set((s) => syncScenario(s));
},
toggleStreamSelection: (pIdx, sIdx) => {
set((s) => {
const key = `stream_${pIdx}_${sIdx}`;
const current = s.selectedStreams[key] !== false;
return { selectedStreams: { ...s.selectedStreams, [key]: !current } };
});
set((s) => syncScenario(s));
},
initSelectedStreams: (keys) =>
set(() => {
const streams: Record<string, boolean> = {};
keys.forEach((k) => (streams[k] = true));
return { selectedStreams: streams };
}),
setEnergyDemands: (demands) => {
set({ energyDemands: demands });
set((s) => syncScenario(s));
},
addEnergyDemand: (demand) => {
set((s) => ({ energyDemands: [...s.energyDemands, demand] }));
set((s) => syncScenario(s));
},
removeEnergyDemand: (index) => {
set((s) => ({
energyDemands: s.energyDemands.filter((_, i) => i !== index),
}));
set((s) => syncScenario(s));
},
updateEnergyDemand: (index, demand) => {
set((s) => ({
energyDemands: s.energyDemands.map((d, i) =>
i === index ? { ...d, ...demand } : d
),
}));
set((s) => syncScenario(s));
},
setTMin: (val) => {
set({ tMin: val });
set((s) => syncScenario(s));
},
setShowShifted: (val) => set({ showShifted: val }),
setPinchResult: (result) => {
set((s) => {
const newState: any = { pinchResult: result };
// Auto-save "All streams" if it's the first time we get a result and no scenarios exist
if (result && s.scenarios.length === 0 && !s.activeScenarioId) {
const newScen = {
id: crypto.randomUUID(),
name: 'All streams',
selectedStreams: { ...s.selectedStreams },
energyDemands: JSON.parse(JSON.stringify(s.energyDemands)),
tMin: s.tMin,
pinchResult: JSON.parse(JSON.stringify(result)),
hpiResult: null,
statusQuoResult: null,
};
newState.scenarios = [newScen];
newState.activeScenarioId = newScen.id;
}
return newState;
});
set((s) => syncScenario(s));
},
setHPIResult: (result) => {
set({ hpiResult: result });
set((s) => syncScenario(s));
},
setStatusQuoResult: (result) => {
set({ statusQuoResult: result });
set((s) => syncScenario(s));
},
setSelectedHPTypes: (types) => set({ selectedHPTypes: types }),
setPinchLoading: (val) => set({ pinchLoading: val }),
setHPILoading: (val) => set({ hpiLoading: val }),
saveScenario: (name) => set((s) => {
const nextIndex = s.scenarios.length + 1;
const finalName = name?.trim() || `Scenario ${nextIndex}`;
const newScen = {
id: crypto.randomUUID(),
name: finalName,
selectedStreams: { ...s.selectedStreams },
energyDemands: JSON.parse(JSON.stringify(s.energyDemands)),
tMin: s.tMin,
pinchResult: s.pinchResult ? JSON.parse(JSON.stringify(s.pinchResult)) : null,
hpiResult: s.hpiResult ? JSON.parse(JSON.stringify(s.hpiResult)) : null,
statusQuoResult: s.statusQuoResult ? JSON.parse(JSON.stringify(s.statusQuoResult)) : null,
};
return {
scenarios: [...s.scenarios, newScen],
activeScenarioId: newScen.id
};
}),
loadScenario: (index) => set((s) => {
const sc = s.scenarios[index];
if (!sc) return {};
return {
activeScenarioId: sc.id,
selectedStreams: { ...sc.selectedStreams },
energyDemands: JSON.parse(JSON.stringify(sc.energyDemands)),
tMin: sc.tMin,
pinchResult: sc.pinchResult ? JSON.parse(JSON.stringify(sc.pinchResult)) : null,
hpiResult: sc.hpiResult ? JSON.parse(JSON.stringify(sc.hpiResult)) : null,
statusQuoResult: sc.statusQuoResult ? JSON.parse(JSON.stringify(sc.statusQuoResult)) : null,
};
}),
deleteScenario: (index) => set((s) => {
const sc = s.scenarios[index];
const isActive = sc && sc.id === s.activeScenarioId;
return {
scenarios: s.scenarios.filter((_, i) => i !== index),
activeScenarioId: isActive ? null : s.activeScenarioId
};
}),
renameScenario: (index, newName) => set((s) => ({
scenarios: s.scenarios.map((sc, i) => i === index ? { ...sc, name: newName } : sc)
})),
clearActiveScenario: () => set({ activeScenarioId: null })
}));