import { createContext, useContext, useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useNomenclatures } from './NomenclatureContext'; import api from '../services/api'; const ScaleProtocolContext = createContext(null); // Which protocol this station uses. Deployment is flexible (one central Django // at the port, single-machine elsewhere), so the station is identified by the // bridge it talks to on localhost - this only records the operator's choice. const SELECTED_KEY = 'scalesapp.selectedProtocolId'; export const useScaleProtocols = () => { const ctx = useContext(ScaleProtocolContext); if (!ctx) throw new Error('useScaleProtocols must be used within ScaleProtocolProvider'); return ctx; }; export function ScaleProtocolProvider({ children }) { const { subscribeToUpdates } = useNomenclatures(); const [protocols, setProtocols] = useState([]); const [selectedId, setSelectedId] = useState(() => { try { const stored = localStorage.getItem(SELECTED_KEY); return stored ? parseInt(stored, 10) : null; } catch { return null; } }); const [isLoaded, setIsLoaded] = useState(false); const [error, setError] = useState(null); // subscribeToUpdates is not memoized in NomenclatureContext - hold it in a // ref so the SSE effect runs once instead of on every provider render. const subscribeRef = useRef(subscribeToUpdates); subscribeRef.current = subscribeToUpdates; const load = useCallback(async () => { try { const res = await api.get('/api/scale-protocols/'); setProtocols(res.data.results || res.data); setError(null); } catch { setError('Failed to load scale protocols'); } finally { setIsLoaded(true); } }, []); useEffect(() => { load(); }, [load]); useEffect(() => { const unsubscribe = subscribeRef.current((message) => { if (message.object !== 'scale_protocol') return; const { operation, data } = message; setProtocols(prev => { if (operation === 'delete') return prev.filter(p => p.id !== data.id); const without = prev.filter(p => p.id !== data.id); return [...without, data].sort((a, b) => a.name.localeCompare(b.name)); }); }); return unsubscribe; }, []); const selectProtocol = useCallback((id) => { setSelectedId(id); try { if (id === null) localStorage.removeItem(SELECTED_KEY); else localStorage.setItem(SELECTED_KEY, String(id)); } catch { // Storage unavailable (private window, blocked site data) - the // selection still applies for this session. } }, []); // Fall back to the simulator so a fresh install shows a weight rather than // an error before anyone has chosen a protocol. const selectedProtocol = useMemo(() => { if (!protocols.length) return null; return protocols.find(p => p.id === selectedId) || protocols.find(p => p.kind === 'simulator') || protocols[0]; }, [protocols, selectedId]); const value = { protocols, selectedProtocol, selectedId, selectProtocol, isLoaded, error, refresh: load, }; return ( {children} ); }