import { defineStore } from 'pinia' import { ref, reactive } from 'vue' import { roomsApi, type RoomInfo as ApiRoomInfo, type SensorReading } from '@/services' import { useSensorStore } from './sensor' interface RoomMetrics { room: string sensors: string[] energy: { current: number total: number average: number unit: string } co2: { current: number average: number max: number status: 'good' | 'moderate' | 'poor' | 'critical' unit: string } occupancyEstimate: 'low' | 'medium' | 'high' lastUpdated: number } export const useRoomStore = defineStore('room', () => { // State const roomsData = reactive>(new Map()) const availableRooms = ref([]) const apiRooms = ref([]) const roomsLoading = ref(false) const roomsLoaded = ref(false) const apiLoading = ref(false) const apiError = ref(null) // Actions function updateRoomData(data: SensorReading) { const sensorStore = useSensorStore() // Validate data structure and provide fallbacks if (!data.energy || !data.co2) { console.warn('Invalid sensor reading data, missing energy or co2 properties:', data) return } // Store latest reading in sensor store sensorStore.updateLatestReading(data) // Get or create room metrics let roomMetrics = roomsData.get(data.room) if (!roomMetrics) { roomMetrics = { room: data.room, sensors: [data.sensor_id], energy: { current: 0, total: 0, average: 0, unit: data.energy?.unit || 'kWh' }, co2: { current: 0, average: 0, max: 0, status: 'good', unit: data.co2?.unit || 'ppm' }, occupancyEstimate: 'low', lastUpdated: data.timestamp, } roomsData.set(data.room, roomMetrics) } // Update room sensors list if (!roomMetrics.sensors.includes(data.sensor_id)) { roomMetrics.sensors.push(data.sensor_id) } // Recalculate room metrics from all sensors in the room const roomSensors = Array.from(sensorStore.latestReadings.values()).filter( (reading) => reading.room === data.room, ) // Energy calculations roomMetrics.energy.current = roomSensors.reduce((sum, sensor) => sum + sensor.energy.value, 0) roomMetrics.energy.total += data.energy.value // Accumulate total roomMetrics.energy.average = roomMetrics.energy.total / roomSensors.length // CO2 calculations const co2Values = roomSensors.map((sensor) => sensor.co2.value) roomMetrics.co2.current = co2Values.reduce((sum, val) => sum + val, 0) / co2Values.length roomMetrics.co2.max = Math.max(roomMetrics.co2.max, ...co2Values) roomMetrics.co2.average = (roomMetrics.co2.average + roomMetrics.co2.current) / 2 // CO2 status classification if (roomMetrics.co2.current < 400) roomMetrics.co2.status = 'good' else if (roomMetrics.co2.current < 1000) roomMetrics.co2.status = 'moderate' else if (roomMetrics.co2.current < 5000) roomMetrics.co2.status = 'poor' else roomMetrics.co2.status = 'critical' // Occupancy estimate based on CO2 levels if (roomMetrics.co2.current < 600) roomMetrics.occupancyEstimate = 'low' else if (roomMetrics.co2.current < 1200) roomMetrics.occupancyEstimate = 'medium' else roomMetrics.occupancyEstimate = 'high' roomMetrics.lastUpdated = data.timestamp } function getCO2Status(ppm: number): 'good' | 'moderate' | 'poor' | 'critical' { if (ppm < 400) return 'good' if (ppm < 1000) return 'moderate' if (ppm < 5000) return 'poor' return 'critical' } const loadRoomsFromAPI = async (): Promise => { if (roomsLoading.value || roomsLoaded.value) { return } roomsLoading.value = true try { const data = await roomsApi.getRoomNames() if (data.rooms && Array.isArray(data.rooms)) { availableRooms.value = data.rooms.sort() roomsLoaded.value = true return } console.warn('No rooms found in API response, starting with empty list') availableRooms.value = [] roomsLoaded.value = true } catch (error) { console.error('Error loading rooms:', error) availableRooms.value = [] roomsLoaded.value = true } finally { roomsLoading.value = false } } function addRoom(roomName: string): boolean { if (!roomName.trim()) return false if (availableRooms.value.includes(roomName.trim())) { return false } availableRooms.value.push(roomName.trim()) availableRooms.value.sort() console.log(`Added new room: ${roomName}`) return true } function removeRoom(roomName: string): boolean { const sensorStore = useSensorStore() const index = availableRooms.value.indexOf(roomName) if (index === -1) return false // Check if any sensors are assigned to this room const sensorsInRoom = sensorStore.getSensorsByRoom(roomName) if (sensorsInRoom.length > 0) { // Reassign sensors to empty room sensorsInRoom.forEach((sensor) => { sensor.room = '' sensorStore.sensorDevices.set(sensor.sensor_id, { ...sensor }) }) } // Remove room data roomsData.delete(roomName) // Remove from available rooms availableRooms.value.splice(index, 1) console.log(`Removed room: ${roomName}`) return true } function getRoomStats(roomName: string) { const sensorStore = useSensorStore() const sensorsInRoom = sensorStore.getSensorsByRoom(roomName) const roomMetrics = roomsData.get(roomName) return { sensorCount: sensorsInRoom.length, sensorTypes: [...new Set(sensorsInRoom.map((s) => s.type))], hasMetrics: !!roomMetrics, energyConsumption: roomMetrics?.energy.current || 0, co2Level: roomMetrics?.co2.current || 0, lastUpdated: roomMetrics?.lastUpdated || null, } } function getAllRoomsWithStats() { return availableRooms.value.map((room) => ({ name: room, ...getRoomStats(room), })) } // API Integration Functions async function handleApiCall(apiCall: () => Promise): Promise { apiLoading.value = true apiError.value = null try { const result = await apiCall() return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' if (errorMessage.includes('401') || errorMessage.includes('Authorization')) { console.warn('Authentication error detected, attempting to re-authenticate...') try { const authStore = (window as any).__AUTH_STORE__ if (authStore && typeof authStore.ensureAuthenticated === 'function') { const authSuccess = await authStore.ensureAuthenticated() if (authSuccess) { console.log('Re-authentication successful, retrying API call...') try { const retryResult = await apiCall() return retryResult } catch (retryError) { const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Retry failed' apiError.value = retryErrorMessage console.error('API retry failed:', retryErrorMessage) return null } } } } catch (authError) { console.error('Re-authentication failed:', authError) } } apiError.value = errorMessage console.error('API call failed:', errorMessage) return null } finally { apiLoading.value = false } } // Rooms API functions async function fetchApiRooms() { const result = await handleApiCall(() => roomsApi.getRooms()) if (result) { // Handle both response formats: {rooms: [...]} or direct array [...] const roomsArray = Array.isArray(result) ? result : result.rooms || [] apiRooms.value = roomsArray // Update available rooms from API data const roomNames = roomsArray.map((room) => room.name || room.room).filter((name) => name) if (roomNames.length > 0) { availableRooms.value = [...new Set([...availableRooms.value, ...roomNames])].sort() } } return result } async function fetchApiRoomData( roomName: string, params?: { start_time?: number; end_time?: number; limit?: number }, ) { return handleApiCall(() => roomsApi.getRoomData(roomName, params)) } // Initialize rooms on store creation loadRoomsFromAPI() return { // State roomsData, availableRooms, apiRooms, roomsLoading, roomsLoaded, apiLoading, apiError, // Actions updateRoomData, getCO2Status, loadRoomsFromAPI, addRoom, removeRoom, getRoomStats, getAllRoomsWithStats, // API functions fetchApiRooms, fetchApiRoomData, } })