Add a summary of real-time metrics per room, including energy, CO2, sensor count, and occupancy. Sensor cards now display live readings from the store instead of mock data. Refactor card logic for reactivity and update navigation colors for clarity.
196 lines
6.1 KiB
Vue
196 lines
6.1 KiB
Vue
<template>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-4">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="p-1.5 rounded-lg" :class="getSensorTypeStyle(sensor.type).bg">
|
|
<span class="text-sm">{{ getSensorTypeIcon(sensor.type) }}</span>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-medium text-gray-900 text-sm">{{ sensor.name }}</h3>
|
|
<p class="text-xs text-gray-500">{{ sensor.room || 'Unassigned' }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status Indicator -->
|
|
<div class="flex items-center gap-1">
|
|
<div
|
|
class="w-2 h-2 rounded-full"
|
|
:class="getSensorStatusColor(sensor.status)"
|
|
></div>
|
|
<span class="text-xs text-gray-500 capitalize">{{ sensor.status }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sensor Values -->
|
|
<div class="mb-3">
|
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
|
<div v-for="metric in sensorValues" :key="metric.type"
|
|
class="bg-gray-50 rounded p-2">
|
|
<div class="text-gray-600 mb-1">{{ metric.label }}</div>
|
|
<div class="font-medium text-gray-900">
|
|
{{ metric.value }} <span class="text-gray-500">{{ metric.unit }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div v-if="sensor.capabilities.actions.length > 0" class="space-y-2">
|
|
<div class="text-xs font-medium text-gray-600 mb-2">Quick Actions</div>
|
|
<div class="flex gap-1 flex-wrap">
|
|
<button
|
|
v-for="action in sensor.capabilities.actions.slice(0, 3)"
|
|
:key="action.id"
|
|
@click="$emit('executeAction', sensor, action)"
|
|
:disabled="isExecutingAction"
|
|
class="flex items-center gap-1 px-2 py-1 bg-gray-100 hover:bg-gray-200 disabled:bg-gray-100 disabled:cursor-not-allowed text-gray-700 rounded text-xs font-medium transition-colors"
|
|
:class="{ 'opacity-50': isExecutingAction }"
|
|
>
|
|
<span class="text-xs">{{ action.icon }}</span>
|
|
<span class="truncate">{{ action.name }}</span>
|
|
</button>
|
|
|
|
<!-- Show more actions if there are more than 3 -->
|
|
<button
|
|
v-if="sensor.capabilities.actions.length > 3"
|
|
@click="$emit('showMore', sensor)"
|
|
class="px-2 py-1 bg-blue-100 hover:bg-blue-200 text-blue-700 rounded text-xs font-medium transition-colors"
|
|
>
|
|
+{{ sensor.capabilities.actions.length - 3 }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No Actions State -->
|
|
<div v-else class="text-xs text-gray-500 text-center py-2 bg-gray-50 rounded">
|
|
Monitor Only
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useEnergyStore } from '@/stores/energy'
|
|
|
|
const props = defineProps<{
|
|
sensor: any
|
|
isExecutingAction?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
executeAction: [sensor: any, action: any]
|
|
showMore: [sensor: any]
|
|
}>()
|
|
|
|
const energyStore = useEnergyStore()
|
|
|
|
const getSensorValues = (sensor: any) => {
|
|
const values = []
|
|
|
|
// Get real-time sensor reading from store
|
|
const latestReading = energyStore.latestReadings.get(sensor.id)
|
|
|
|
if (sensor.capabilities.monitoring.includes('energy')) {
|
|
const energyValue = latestReading?.energy?.value?.toFixed(2) ||
|
|
energyStore.latestMessage?.value?.toFixed(2) ||
|
|
'0.00'
|
|
values.push({
|
|
type: 'energy',
|
|
label: 'Energy',
|
|
value: energyValue,
|
|
unit: latestReading?.energy?.unit || energyStore.latestMessage?.unit || 'kWh'
|
|
})
|
|
}
|
|
|
|
if (sensor.capabilities.monitoring.includes('co2')) {
|
|
const co2Value = latestReading?.co2?.value || Math.floor(Math.random() * 800 + 350)
|
|
values.push({
|
|
type: 'co2',
|
|
label: 'CO2',
|
|
value: co2Value,
|
|
unit: latestReading?.co2?.unit || 'ppm'
|
|
})
|
|
}
|
|
|
|
if (sensor.capabilities.monitoring.includes('temperature')) {
|
|
const tempValue = latestReading?.temperature?.value?.toFixed(1) ||
|
|
(Math.random() * 8 + 18).toFixed(1)
|
|
values.push({
|
|
type: 'temperature',
|
|
label: 'Temperature',
|
|
value: tempValue,
|
|
unit: latestReading?.temperature?.unit || '°C'
|
|
})
|
|
}
|
|
|
|
if (sensor.capabilities.monitoring.includes('humidity')) {
|
|
// Fallback to mock data for humidity as it's not in current data model
|
|
values.push({
|
|
type: 'humidity',
|
|
label: 'Humidity',
|
|
value: Math.floor(Math.random() * 40 + 30),
|
|
unit: '%'
|
|
})
|
|
}
|
|
|
|
if (sensor.capabilities.monitoring.includes('motion')) {
|
|
values.push({
|
|
type: 'motion',
|
|
label: 'Motion',
|
|
value: Math.random() > 0.7 ? 'Detected' : 'Clear',
|
|
unit: ''
|
|
})
|
|
}
|
|
|
|
// If no monitoring capabilities, show generic status
|
|
if (values.length === 0) {
|
|
values.push({
|
|
type: 'status',
|
|
label: 'Status',
|
|
value: sensor.status === 'online' ? 'Active' : 'Inactive',
|
|
unit: ''
|
|
})
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
// Reactive sensor values that update automatically
|
|
const sensorValues = computed(() => getSensorValues(props.sensor))
|
|
|
|
const getSensorTypeIcon = (type: string) => {
|
|
const icons = {
|
|
energy: '⚡',
|
|
co2: '💨',
|
|
temperature: '🌡️',
|
|
humidity: '💧',
|
|
hvac: '❄️',
|
|
lighting: '💡',
|
|
security: '🔒'
|
|
}
|
|
return icons[type as keyof typeof icons] || '📱'
|
|
}
|
|
|
|
const getSensorTypeStyle = (type: string) => {
|
|
const styles = {
|
|
energy: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
co2: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
temperature: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
humidity: { bg: 'bg-blue-100', text: 'text-blue-700' },
|
|
hvac: { bg: 'bg-cyan-100', text: 'text-cyan-700' },
|
|
lighting: { bg: 'bg-amber-100', text: 'text-amber-700' },
|
|
security: { bg: 'bg-purple-100', text: 'text-purple-700' }
|
|
}
|
|
return styles[type as keyof typeof styles] || { bg: 'bg-gray-100', text: 'text-gray-700' }
|
|
}
|
|
|
|
const getSensorStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'online': return 'bg-green-500'
|
|
case 'offline': return 'bg-gray-400'
|
|
case 'error': return 'bg-red-500'
|
|
default: return 'bg-gray-400'
|
|
}
|
|
}
|
|
</script> |