<script setup>
import { computed } from "vue";
import { GnLoader, GnBadge } from "gnexus-ui-kit/vue";
const props = defineProps({
deviceType: { type: String, default: "" },
response: { type: Object, default: null },
loading: { type: Boolean, default: false },
error: { type: [String, Object], default: null },
connectionStatus: { type: String, default: "unknown" },
});
const isOffline = computed(() => props.connectionStatus === "lost");
const channels = computed(() => props.response?.channels || []);
const sensors = computed(() => props.response?.sensors || {});
const hatchState = computed(() => props.response?.hatch?.state || "—");
const hatchPosition = computed(() => props.response?.hatch?.position_pct ?? "—");
const hatchVariant = computed(() =>
String(hatchState.value).includes("open") ? "warning" : "primary"
);
const hatchShowPosition = computed(() =>
String(hatchState.value).includes("open")
);
const isOk = computed(() => props.response?.status === "ok");
const jsonPreview = computed(() => {
if (!props.response) return "";
try {
return JSON.stringify(props.response).slice(0, 200);
} catch {
return "";
}
});
const hasContent = computed(() => {
if (!isOk.value) return false;
if (props.deviceType === "relay" || props.deviceType === "button") {
return channels.value.length > 0;
}
if (props.deviceType === "sensor") {
return Object.keys(sensors.value).length > 0;
}
if (props.deviceType === "hatch") {
return !!props.response?.hatch;
}
return false;
});
function channelId(ch) {
return ch.id ?? ch.channel;
}
function buttonVariant(indicator) {
const map = {
enabled: "success",
disabled: "secondary",
mute: "primary",
waiting: "warning",
error: "danger",
};
return map[indicator] || "secondary";
}
</script>
<template>
<div class="device-channels-state">
<GnLoader v-if="loading" circle size="sm" label="Loading state" />
<GnBadge v-else-if="isOffline" variant="danger" size="sm">
<i class="ph ph-wifi-slash"></i> Offline
</GnBadge>
<GnBadge v-else-if="error || !isOk" variant="danger" size="sm">
<i class="ph ph-warning-octagon"></i>
<template v-if="typeof error === 'string'">{{ error }}</template>
<template v-else>Error</template>
</GnBadge>
<div v-else-if="hasContent" class="channels-grid">
<!-- Relay -->
<template v-if="deviceType === 'relay'">
<GnBadge
v-for="ch in channels"
:key="channelId(ch)"
:variant="ch.state === 'on' || ch.state === true ? 'success' : 'secondary'"
size="sm"
>
<template v-if="channels.length > 1">{{ channelId(ch) }}: </template>
<b>{{ ch.state == "off" ? "OFF" : "ON" }}</b>
</GnBadge>
</template>
<!-- Button -->
<template v-else-if="deviceType === 'button'">
<GnBadge
v-for="ch in channels"
:key="channelId(ch)"
:variant="buttonVariant(ch.indicator)"
size="sm"
>
{{ channelId(ch) }}: <b>{{ ch.indicator }}</b>
</GnBadge>
</template>
<!-- Sensor -->
<template v-else-if="deviceType === 'sensor'">
<GnBadge v-if="sensors.radar" variant="primary" size="sm">
<i class="ph" :class="sensors.radar.presence ? 'ph-user-square' : 'ph-square'"></i>
<template v-if="sensors.radar.presence">
{{ sensors.radar.activity_score }}
<i v-if="sensors.radar.activity_score_dynamics === 'increasing'" class="ph ph-caret-up"></i>
<i v-else-if="sensors.radar.activity_score_dynamics === 'decreasing'" class="ph ph-caret-down"></i>
</template>
</GnBadge>
<GnBadge v-if="sensors.temperature" variant="primary" size="sm">
<i class="ph ph-thermometer"></i>
{{ sensors.temperature.current }}°C
<i v-if="sensors.temperature.dynamics === 'increasing'" class="ph ph-caret-up"></i>
<i v-else-if="sensors.temperature.dynamics === 'decreasing'" class="ph ph-caret-down"></i>
</GnBadge>
<GnBadge v-if="sensors.humidity" variant="primary" size="sm">
<i class="ph ph-drop-half-bottom"></i>
{{ sensors.humidity.current }}%
<i v-if="sensors.humidity.dynamics === 'increasing'" class="ph ph-caret-up"></i>
<i v-else-if="sensors.humidity.dynamics === 'decreasing'" class="ph ph-caret-down"></i>
</GnBadge>
<GnBadge v-if="sensors.pressure" variant="primary" size="sm">
{{ sensors.pressure.current }}hpa
<i v-if="sensors.pressure.dynamics === 'increasing'" class="ph ph-caret-up"></i>
<i v-else-if="sensors.pressure.dynamics === 'decreasing'" class="ph ph-caret-down"></i>
</GnBadge>
<GnBadge v-if="sensors.light" variant="primary" size="sm">
<i class="ph ph-lightbulb"></i>
{{ sensors.light.percent }}%
</GnBadge>
<GnBadge v-if="sensors.microphone" variant="primary" size="sm">
<i class="ph ph-ear"></i>
{{ sensors.microphone.current_noise }}dBi
<i v-if="sensors.microphone.noise_dynamics === 'increasing'" class="ph ph-caret-up"></i>
<i v-else-if="sensors.microphone.noise_dynamics === 'decreasing'" class="ph ph-caret-down"></i>
</GnBadge>
</template>
<!-- Hatch -->
<template v-else-if="deviceType === 'hatch'">
<GnBadge :variant="hatchVariant" size="sm">
{{ hatchState }}
<template v-if="hatchShowPosition"> - {{ hatchPosition }}%</template>
</GnBadge>
</template>
<template v-else>
<span class="unknown-type text-muted">Unknown type</span>
<pre v-if="jsonPreview" class="raw-json text-muted">{{ jsonPreview }}</pre>
</template>
</div>
<GnBadge v-else variant="secondary" size="sm">No data</GnBadge>
</div>
</template>
<style scoped>
.device-channels-state {
display: inline-flex;
align-items: center;
}
.channels-grid {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.unknown-type {
font-size: 12px;
}
.raw-json {
font-size: 10px;
margin: 4px 0 0;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>