sensor management page
This commit is contained in:
220
src/views/SensorManagementView.vue
Normal file
220
src/views/SensorManagementView.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900">Sensor & IoT Management</h1>
|
||||
<p class="text-gray-600">Manage sensors, assign rooms, and control device actions</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0">
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||
<div
|
||||
class="w-3 h-3 rounded-full"
|
||||
:class="energyStore.isConnected ? 'bg-green-500' : 'bg-red-500'"
|
||||
></div>
|
||||
<span>{{ energyStore.isConnected ? 'Connected' : 'Disconnected' }}</span>
|
||||
<span class="mx-2">•</span>
|
||||
<span>{{ sensorList.length }} sensors</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and View Toggle -->
|
||||
<div class="flex flex-col lg:flex-row gap-4">
|
||||
<!-- Filters -->
|
||||
<div class="flex flex-col sm:flex-row gap-4 flex-1">
|
||||
<select
|
||||
v-model="selectedRoom"
|
||||
class="px-4 py-2 border border-gray-200 rounded-lg bg-white"
|
||||
>
|
||||
<option value="">All Rooms</option>
|
||||
<option v-for="room in energyStore.availableRooms" :key="room" :value="room">
|
||||
{{ room }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="selectedType"
|
||||
class="px-4 py-2 border border-gray-200 rounded-lg bg-white"
|
||||
>
|
||||
<option value="">All Types</option>
|
||||
<option value="energy">Energy</option>
|
||||
<option value="co2">CO2</option>
|
||||
<option value="temperature">Temperature</option>
|
||||
<option value="hvac">HVAC</option>
|
||||
<option value="lighting">Lighting</option>
|
||||
<option value="security">Security</option>
|
||||
</select>
|
||||
|
||||
<select
|
||||
v-model="selectedStatus"
|
||||
class="px-4 py-2 border border-gray-200 rounded-lg bg-white"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
<option value="online">Online</option>
|
||||
<option value="offline">Offline</option>
|
||||
<option value="error">Error</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="flex items-center gap-2 bg-gray-100 rounded-lg p-1">
|
||||
<button
|
||||
@click="viewMode = 'simple'"
|
||||
class="px-3 py-1.5 rounded text-sm font-medium transition-colors"
|
||||
:class="viewMode === 'simple'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
|
||||
</svg>
|
||||
Simple
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@click="viewMode = 'detailed'"
|
||||
class="px-3 py-1.5 rounded text-sm font-medium transition-colors"
|
||||
:class="viewMode === 'detailed'
|
||||
? 'bg-white text-gray-900 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
Detailed
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sensors Grid -->
|
||||
<div
|
||||
class="grid gap-4"
|
||||
:class="viewMode === 'simple'
|
||||
? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'
|
||||
: 'grid-cols-1 lg:grid-cols-2 xl:grid-cols-3'"
|
||||
>
|
||||
<!-- Simple Cards -->
|
||||
<SimpleSensorCard
|
||||
v-if="viewMode === 'simple'"
|
||||
v-for="sensor in filteredSensors"
|
||||
:key="sensor.id"
|
||||
:sensor="sensor"
|
||||
:is-executing-action="isExecutingAction"
|
||||
@execute-action="executeAction"
|
||||
@show-more="showSensorDetails"
|
||||
/>
|
||||
|
||||
<!-- Detailed Cards -->
|
||||
<DetailedSensorCard
|
||||
v-if="viewMode === 'detailed'"
|
||||
v-for="sensor in filteredSensors"
|
||||
:key="sensor.id"
|
||||
:sensor="sensor"
|
||||
:available-rooms="energyStore.availableRooms"
|
||||
:is-executing-action="isExecutingAction"
|
||||
@update-room="updateRoom"
|
||||
@execute-action="executeAction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-if="filteredSensors.length === 0" class="text-center py-12">
|
||||
<div class="text-gray-400 text-6xl mb-4">🔍</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No sensors found</h3>
|
||||
<p class="text-gray-600">Try adjusting your filters or check if sensors are connected.</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Modal -->
|
||||
<ActionModal
|
||||
v-if="showActionModal"
|
||||
:sensor="selectedSensor"
|
||||
:action="selectedAction"
|
||||
@execute="handleActionExecute"
|
||||
@close="closeActionModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useEnergyStore } from '@/stores/energy'
|
||||
import ActionModal from '@/components/modals/ActionModal.vue'
|
||||
import SimpleSensorCard from '@/components/cards/SimpleSensorCard.vue'
|
||||
import DetailedSensorCard from '@/components/cards/DetailedSensorCard.vue'
|
||||
|
||||
const energyStore = useEnergyStore()
|
||||
|
||||
// View mode
|
||||
const viewMode = ref<'simple' | 'detailed'>('simple')
|
||||
|
||||
// Filters
|
||||
const selectedRoom = ref('')
|
||||
const selectedType = ref('')
|
||||
const selectedStatus = ref('')
|
||||
|
||||
// Action modal
|
||||
const showActionModal = ref(false)
|
||||
const selectedSensor = ref<any>(null)
|
||||
const selectedAction = ref<any>(null)
|
||||
const isExecutingAction = ref(false)
|
||||
|
||||
const sensorList = computed(() => {
|
||||
return Array.from(energyStore.sensorDevices.values()).sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
)
|
||||
})
|
||||
|
||||
const filteredSensors = computed(() => {
|
||||
return sensorList.value.filter(sensor => {
|
||||
const matchesRoom = !selectedRoom.value || sensor.room === selectedRoom.value
|
||||
const matchesType = !selectedType.value || sensor.type === selectedType.value
|
||||
const matchesStatus = !selectedStatus.value || sensor.status === selectedStatus.value
|
||||
|
||||
return matchesRoom && matchesType && matchesStatus
|
||||
})
|
||||
})
|
||||
|
||||
const updateRoom = (sensorId: string, newRoom: string) => {
|
||||
energyStore.updateSensorRoom(sensorId, newRoom)
|
||||
}
|
||||
|
||||
const executeAction = (sensor: any, action: any) => {
|
||||
if (action.parameters) {
|
||||
// Show modal for actions with parameters
|
||||
selectedSensor.value = sensor
|
||||
selectedAction.value = action
|
||||
showActionModal.value = true
|
||||
} else {
|
||||
// Execute simple actions directly
|
||||
handleActionExecute(sensor.id, action.id, {})
|
||||
}
|
||||
}
|
||||
|
||||
const handleActionExecute = async (sensorId: string, actionId: string, parameters: any) => {
|
||||
isExecutingAction.value = true
|
||||
try {
|
||||
await energyStore.executeSensorAction(sensorId, actionId, parameters)
|
||||
} catch (error) {
|
||||
console.error('Action execution failed:', error)
|
||||
} finally {
|
||||
isExecutingAction.value = false
|
||||
closeActionModal()
|
||||
}
|
||||
}
|
||||
|
||||
const closeActionModal = () => {
|
||||
showActionModal.value = false
|
||||
selectedSensor.value = null
|
||||
selectedAction.value = null
|
||||
}
|
||||
|
||||
const showSensorDetails = (sensor: any) => {
|
||||
// Switch to detailed view when user wants to see more actions
|
||||
viewMode.value = 'detailed'
|
||||
// Optionally scroll to the sensor or highlight it
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user