Newer
Older
smart-home-server / webclient-vue / src / features / areas / pages / AreaFavoritesPage.vue
<template>
  <section class="page">
    <GnPageHeader title="Favorites" kicker="Areas">
      <template #actions>
        <GnBadge variant="primary">{{ favoriteAreas.length }} favorite areas</GnBadge>
      </template>
    </GnPageHeader>

    <AppLoadingState v-if="areasStore.isLoading" text="Loading areas" />

    <AppErrorState
      v-else-if="areasStore.error"
      title="Areas loading failed"
      :message="areasStore.error.message"
      :retry="areasStore.loadAreas"
    />

    <AppEmptyState
      v-else-if="favoriteAreas.length === 0"
      title="No favorite areas"
      message="Favorite areas from the current client are preserved through localStorage."
    />

    <ul v-else class="area-favorites-list">
      <li
        v-for="area in favoriteAreas"
        :key="area.id"
        class="area-favorite-item"
      >
        <article class="area-favorite-card" @click="goToDetail(area)">
          <div class="area-favorite-icon">
            <i class="ph ph-fill ph-bookmark-simple" />
          </div>

          <div class="area-favorite-info">
            <h2 class="area-favorite-title">{{ area.display_name }}</h2>
            <p class="area-favorite-meta">
              <GnBadge variant="secondary">{{ area.type }}</GnBadge>
              <code>{{ area.alias }}</code>
              <span v-if="parentName(area)" class="area-favorite-parent">
                in <router-link
                  :to="{ name: 'area-detail', params: { id: String(area.parent_id) } }"
                  class="parent-link"
                  @click.stop
                >
                  {{ parentName(area) }}
                </router-link>
              </span>
            </p>
          </div>

          <div class="area-favorite-actions">
            <AreaFavoriteButton :area-id="area.id" />
          </div>
        </article>
      </li>
    </ul>
  </section>
</template>

<script setup>
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useAreasStore } from "../../../stores/areas";
import { useFavoritesStore } from "../../../stores/favorites";
import { GnPageHeader, GnBadge } from "gnexus-ui-kit/vue";
import AreaFavoriteButton from "../components/AreaFavoriteButton.vue";
import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
import AppErrorState from "../../../components/feedback/AppErrorState.vue";
import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";

const areasStore = useAreasStore();
const favoritesStore = useFavoritesStore();
const router = useRouter();

const favoriteAreas = computed(() => {
  const favoriteIds = new Set(favoritesStore.ids.map(String));
  return areasStore.areas.filter((area) => favoriteIds.has(String(area.id)));
});

function parentName(area) {
  if (!area.parent_id) return null;
  const parent = areasStore.areasById[String(area.parent_id)];
  return parent?.display_name || null;
}

function goToDetail(area) {
  router.push({ name: "area-detail", params: { id: String(area.id) } });
}

onMounted(() => {
  if (areasStore.areas.length === 0) {
    areasStore.loadAreas();
  }
});
</script>

<style scoped>
.area-favorites-list {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.area-favorite-card {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid rgba(192, 202, 245, 0.12);
  background: var(--color-panel);
  cursor: pointer;
  transition: background 0.15s ease;
}

.area-favorite-card:hover {
  background: var(--color-panel-strong);
}

.area-favorite-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 2px solid var(--color-primary);
  color: var(--color-primary);
  font-size: 20px;
}

.area-favorite-info {
  min-width: 0;
}

.area-favorite-title {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.area-favorite-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
  color: var(--color-muted);
  font-size: 13px;
}

.area-favorite-parent {
  color: var(--color-muted);
}

.area-favorite-parent .parent-link {
  color: var(--color-primary);
  text-decoration: none;
}

.area-favorite-parent .parent-link:hover {
  text-decoration: underline;
}

@media (max-width: 720px) {
  .area-favorite-card {
    grid-template-columns: auto 1fr;
    gap: 12px;
  }

  .area-favorite-actions {
    grid-column: 1 / -1;
    justify-self: end;
  }
}
</style>