<template>
<section class="page">
<AppLoadingState v-if="areasStore.isLoading || areasStore.isLoadingAreaDetail" text="Loading area" />
<AppErrorState
v-else-if="areasStore.error && !areasStore.areas.length"
title="Areas loading failed"
:message="areasStore.error.message"
:retry="init"
/>
<AppErrorState
v-else-if="areasStore.errorAreaDetail"
title="Area loading failed"
:message="areasStore.errorAreaDetail.message"
:retry="init"
/>
<div v-else-if="area">
<GnPageHeader :title="area.display_name" kicker="Area">
<template #actions>
<GnButton variant="secondary" icon="ph-pencil" @click="openRename">Rename</GnButton>
<GnButton
variant="secondary"
icon="ph-arrow-up"
:disabled="area.parent_id == null || area.parent_id === 0"
@click="unassign"
>
Unassign
</GnButton>
<GnButton
:variant="isFavorite ? 'warning' : 'secondary'"
:icon="isFavorite ? 'ph-star' : 'ph-star'"
@click="favoritesStore.toggle(area.id)"
>
{{ isFavorite ? 'Unstar' : 'Star' }}
</GnButton>
<GnButton variant="danger" icon="ph-trash" @click="openRemove">Remove</GnButton>
</template>
</GnPageHeader>
<div class="area-meta">
<GnBadge variant="secondary">{{ area.type }}</GnBadge>
<code>{{ area.alias }}</code>
<span v-if="area.parent_id" class="area-parent">Parent: {{ parentArea?.display_name || area.parent_id }}</span>
</div>
<div class="devices-panel">
<div class="block-title">Devices ({{ areasStore.currentAreaDevices.length }})</div>
<AppEmptyState
v-if="areasStore.currentAreaDevices.length === 0"
title="No devices"
message="No devices assigned to this area."
/>
<GnTable v-else :rows="areasStore.currentAreaDevices" :columns="deviceColumns">
<template #cell-actions="{ row }">
<router-link :to="{ name: 'devices' }" class="area-link">View device</router-link>
</template>
</GnTable>
</div>
<div class="devices-panel">
<div class="block-title">Scripts ({{ areasStore.currentAreaScripts.length }})</div>
<AppEmptyState
v-if="areasStore.currentAreaScripts.length === 0"
title="No scripts"
message="No scripts assigned to this area."
/>
<GnTable v-else :rows="areasStore.currentAreaScripts" :columns="scriptColumns">
<template #cell-actions="{ row }">
<router-link :to="{ name: 'scripts-actions' }" class="area-link">View script</router-link>
</template>
</GnTable>
</div>
</div>
<AppEmptyState
v-else
title="Area not found"
message="The requested area does not exist."
/>
<GnModal :open="showRenameModal" title="Rename area" @update:open="showRenameModal = $event">
<div class="form-group">
<GnInput v-model="renameForm.display_name" label="Display name" />
</div>
<div v-if="renameError" class="form-group">
<GnAlert variant="danger">{{ renameError }}</GnAlert>
</div>
<template #footer>
<GnButton variant="secondary" @click="showRenameModal = false">Cancel</GnButton>
<GnButton variant="primary" icon="ph-check" :loading="renameLoading" @click="submitRename">Rename</GnButton>
</template>
</GnModal>
<GnConfirmDialog
:open="showRemoveDialog"
title="Remove area"
:message="removeDialogMessage"
confirm-text="Remove"
cancel-text="Cancel"
confirm-variant="danger"
@update:open="showRemoveDialog = $event"
@confirm="submitRemove"
/>
</section>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useAreasStore } from "../../../stores/areas";
import { useFavoritesStore } from "../../../stores/favorites";
import {
GnPageHeader,
GnButton,
GnBadge,
GnModal,
GnInput,
GnAlert,
GnTable,
GnConfirmDialog,
} from "gnexus-ui-kit/vue";
import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
import AppErrorState from "../../../components/feedback/AppErrorState.vue";
import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
const route = useRoute();
const router = useRouter();
const areasStore = useAreasStore();
const favoritesStore = useFavoritesStore();
const area = computed(() => areasStore.areasById[String(route.params.id)] || null);
const isFavorite = computed(() => (area.value ? favoritesStore.has(area.value.id) : false));
const parentArea = computed(() => {
if (!area.value?.parent_id) return null;
return areasStore.areasById[String(area.value.parent_id)] || null;
});
const showRenameModal = ref(false);
const renameLoading = ref(false);
const renameError = ref("");
const renameForm = reactive({ areaId: null, display_name: "" });
const showRemoveDialog = ref(false);
const removeDialogMessage = ref("");
const deviceColumns = [
{ key: "alias", label: "Alias" },
{ key: "name", label: "Name" },
{ key: "device_type", label: "Type" },
];
const scriptColumns = [
{ key: "alias", label: "Alias" },
{ key: "name", label: "Name" },
{ key: "state", label: "State" },
];
function openRename() {
if (!area.value) return;
renameForm.areaId = area.value.id;
renameForm.display_name = area.value.display_name;
renameError.value = "";
showRenameModal.value = true;
}
async function submitRename() {
renameLoading.value = true;
renameError.value = "";
const result = await areasStore.renameArea(renameForm.areaId, renameForm.display_name);
renameLoading.value = false;
if (!result.ok) {
renameError.value = result.error?.message || "Failed to rename area";
return;
}
showRenameModal.value = false;
}
function openRemove() {
if (!area.value) return;
removeDialogMessage.value = `Are you sure you want to remove area "${area.value.display_name}"?`;
showRemoveDialog.value = true;
}
async function submitRemove() {
if (!area.value) return;
await areasStore.removeArea(area.value.id);
router.push({ name: "areas-tree" });
}
async function unassign() {
if (!area.value) return;
await areasStore.unassignArea(area.value.id);
}
async function init() {
const id = route.params.id;
if (!id) return;
if (areasStore.areas.length === 0) {
await areasStore.loadAreas();
}
if (areasStore.areasById[String(id)]) {
await areasStore.loadAreaDetail(id);
}
}
onMounted(() => {
init();
});
onUnmounted(() => {
areasStore.clearAreaDetail();
});
</script>
<style scoped>
.area-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 24px;
}
.area-parent {
color: var(--color-muted);
}
.devices-panel {
margin-bottom: 24px;
}
.block-title {
font-weight: 700;
text-transform: uppercase;
margin-bottom: 12px;
color: var(--color-primary);
}
.form-group {
margin-bottom: 16px;
}
.area-link {
color: var(--color-primary);
}
</style>