Refactor stores for modularity and API type updates
- Split energy store into sensor, room, analytics, and websocket stores - Add new analytics, room, sensor, and websocket stores - Update API types for sensors (SensorDevice, SensorAction) - Update sensorsApi to use new SensorDevice type - Add central index for store exports and types - Refactor energy store to delegate to modular stores - Remove legacy code and consolidate API logic
This commit is contained in:
298
src/stores/room.ts
Normal file
298
src/stores/room.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { roomsApi, type RoomInfo as ApiRoomInfo } 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
|
||||
}
|
||||
|
||||
interface SensorReading {
|
||||
sensorId: string
|
||||
room: string
|
||||
timestamp: number
|
||||
energy: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
co2: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
temperature?: {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
}
|
||||
|
||||
export const useRoomStore = defineStore('room', () => {
|
||||
// State
|
||||
const roomsData = reactive<Map<string, RoomMetrics>>(new Map())
|
||||
const availableRooms = ref<string[]>([])
|
||||
const apiRooms = ref<ApiRoomInfo[]>([])
|
||||
const roomsLoading = ref<boolean>(false)
|
||||
const roomsLoaded = ref<boolean>(false)
|
||||
const apiLoading = ref(false)
|
||||
const apiError = ref<string | null>(null)
|
||||
|
||||
// Actions
|
||||
function updateRoomData(data: SensorReading) {
|
||||
const sensorStore = useSensorStore()
|
||||
|
||||
// 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.sensorId],
|
||||
energy: { current: 0, total: 0, average: 0, unit: data.energy.unit },
|
||||
co2: { current: 0, average: 0, max: 0, status: 'good', unit: data.co2.unit },
|
||||
occupancyEstimate: 'low',
|
||||
lastUpdated: data.timestamp,
|
||||
}
|
||||
roomsData.set(data.room, roomMetrics)
|
||||
}
|
||||
|
||||
// Update room sensors list
|
||||
if (!roomMetrics.sensors.includes(data.sensorId)) {
|
||||
roomMetrics.sensors.push(data.sensorId)
|
||||
}
|
||||
|
||||
// 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<void> => {
|
||||
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.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<T>(apiCall: () => Promise<T>): Promise<T | null> {
|
||||
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) {
|
||||
apiRooms.value = result
|
||||
// Update available rooms from API data
|
||||
const roomNames = result.map((room) => 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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user