Files
sa4cps-frontend/src/components/cards/SimpleSensorCard.vue
rafaeldpsilva 55a2d6d097 Show real-time room metrics and improve sensor cards
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.
2025-09-03 16:34:04 +01:00

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>