Newer
Older
smart-home-server / webclient-vue / src / stores / areas.js
import { defineStore } from "pinia";
import { areasApi } from "../api/modules/areas";

function buildAreaTree(areas) {
  const map = {};
  const roots = [];

  for (const area of areas) {
    map[area.id] = { ...area, children: [] };
  }

  for (const area of areas) {
    const node = map[area.id];
    const isSelfReference = area.parent_id && area.parent_id == area.id;
    const parentExists = area.parent_id && map[area.parent_id];

    if (!isSelfReference && parentExists) {
      map[area.parent_id].children.push(node);
    } else {
      roots.push(node);
    }
  }

  if (roots.length === 0 && areas.length > 0) {
    return Object.values(map);
  }

  return roots;
}

export const useAreasStore = defineStore("areas", {
  state: () => ({
    areas: [],
    isLoading: false,
    error: null,
    _listAbortController: null,
    currentArea: null,
    currentAreaDevices: [],
    currentAreaScripts: [],
    isLoadingAreaDetail: false,
    errorAreaDetail: null,
    _areaDetailAbortController: null,
  }),

  getters: {
    areasById(state) {
      return Object.fromEntries(state.areas.map((area) => [String(area.id), area]));
    },

    areaTree(state) {
      return buildAreaTree(state.areas);
    },
  },

  actions: {
    async loadAreas() {
      this._listAbortController?.abort();
      const controller = new AbortController();
      this._listAbortController = controller;

      this.isLoading = true;
      this.error = null;

      const result = await areasApi.list({ signal: controller.signal });
      this._listAbortController = null;
      this.isLoading = false;

      if (!result.ok) {
        if (result.error?.type === "timeout") {
          return result;
        }
        this.error = result.error;
        return result;
      }

      this.areas = result.data?.data?.areas || [];
      return result;
    },

    async loadAreaDetail(areaId) {
      this._areaDetailAbortController?.abort();
      const controller = new AbortController();
      this._areaDetailAbortController = controller;

      this.isLoadingAreaDetail = true;
      this.errorAreaDetail = null;
      this.currentArea = this.areasById[String(areaId)] || null;
      this.currentAreaDevices = [];
      this.currentAreaScripts = [];

      const [devicesResult, scriptsResult] = await Promise.all([
        areasApi.devices(areaId, { signal: controller.signal }),
        areasApi.scripts(areaId, { signal: controller.signal }),
      ]);

      this._areaDetailAbortController = null;
      this.isLoadingAreaDetail = false;

      if (!devicesResult.ok) {
        if (devicesResult.error?.type !== "timeout") {
          this.errorAreaDetail = devicesResult.error;
        }
        return { ok: false, error: this.errorAreaDetail };
      }

      if (!scriptsResult.ok) {
        if (scriptsResult.error?.type !== "timeout") {
          this.errorAreaDetail = scriptsResult.error;
        }
        return { ok: false, error: this.errorAreaDetail };
      }

      this.currentAreaDevices = devicesResult.data?.data?.devices || [];
      this.currentAreaScripts = scriptsResult.data?.data?.scripts || [];
      return { ok: true };
    },

    clearAreaDetail() {
      this.currentArea = null;
      this.currentAreaDevices = [];
      this.currentAreaScripts = [];
      this.errorAreaDetail = null;
      this._areaDetailAbortController?.abort();
      this._areaDetailAbortController = null;
    },

    async loadAreaDevices(areaId) {
      const result = await areasApi.devices(areaId);
      if (result.ok) {
        this.currentAreaDevices = result.data?.data?.devices || [];
      }
      return result;
    },

    async loadAreaScripts(areaId) {
      const result = await areasApi.scripts(areaId);
      if (result.ok) {
        this.currentAreaScripts = result.data?.data?.scripts || [];
      }
      return result;
    },

    async createArea(payload) {
      const result = await areasApi.newArea(payload);

      if (result.ok) {
        const newArea = result.data?.data?.area;
        if (newArea) {
          this.areas.push(newArea);
        }
      }

      return result;
    },

    async renameArea(areaId, displayName) {
      const result = await areasApi.updateDisplayName({ area_id: areaId, display_name: displayName });

      if (result.ok) {
        const idx = this.areas.findIndex((a) => a.id === areaId);
        if (idx !== -1) {
          this.areas[idx] = { ...this.areas[idx], display_name: displayName };
        }
      }

      return result;
    },

    async removeArea(areaId) {
      const result = await areasApi.remove(areaId);

      if (result.ok) {
        this.areas = this.areas.filter((a) => a.id !== areaId);
      }

      return result;
    },

    async assignToArea(areaId, parentAreaId) {
      const result = await areasApi.placeInArea({ target_id: areaId, place_in_area_id: parentAreaId });

      if (result.ok) {
        const idx = this.areas.findIndex((a) => a.id === areaId);
        if (idx !== -1) {
          this.areas.splice(idx, 1, { ...this.areas[idx], parent_id: Number(parentAreaId) });
        }
      }

      return result;
    },

    async unassignArea(areaId) {
      const result = await areasApi.unassign(areaId);

      if (result.ok) {
        const idx = this.areas.findIndex((a) => a.id === areaId);
        if (idx !== -1) {
          this.areas.splice(idx, 1, { ...this.areas[idx], parent_id: 0 });
        }
      }

      return result;
    },
  },
});