diff --git a/web/src/components/StorageHierarchy.vue b/web/src/components/StorageHierarchy.vue index 96e4736..85445b5 100644 --- a/web/src/components/StorageHierarchy.vue +++ b/web/src/components/StorageHierarchy.vue @@ -50,16 +50,43 @@ </template> <script lang="ts" setup> -import { ref, computed, watch, onMounted, defineComponent } from 'vue'; +import { ref, computed, watch, onMounted } from 'vue'; import { useStorageStore } from '@/stores/storage'; -// Component for recursive display of storage boxes +// Define types for our data +interface StorageSpace { + id: number; + parent: { + valid: boolean; + int64: number; + } | null; + location: string | null; +} + +interface StorageObject { + id: number; + storagespaceId: number; + name: string; + description: string | null; + serialnumber: string | null; +} + +// Create a separate component for storage boxes const StorageBox = defineComponent({ name: 'StorageBox', props: { - storage: Object, - objects: Array, - nestedStorages: Array + storage: { + type: Object as () => StorageSpace, + required: true + }, + objects: { + type: Array as () => StorageObject[], + required: true + }, + nestedStorages: { + type: Array as () => StorageSpace[], + required: true + } }, setup(props) { const objectsInCurrentStorage = computed(() => { @@ -72,87 +99,81 @@ const StorageBox = defineComponent({ ); }); - return () => ( - <v-expansion-panel> - <v-expansion-panel-title> - <div class="d-flex align-center"> - <v-icon class="mr-2">mdi-package-variant-closed</v-icon> - <span>{ props.storage.location || `Storage #${props.storage.id}` }</span> - <v-chip class="ml-2" size="small" color="primary" variant="outlined"> - { objectsInCurrentStorage.value.length } objects - </v-chip> - </div> - </v-expansion-panel-title> - <v-expansion-panel-text> - <div class="mb-4"> - <h3 class="text-subtitle-1 mb-2">Objects in this storage:</h3> - {objectsInCurrentStorage.value.length > 0 ? ( - <v-list lines="two"> - {objectsInCurrentStorage.value.map(obj => ( - <v-list-item - :key="obj.id" - :title="obj.name" - :subtitle="obj.description || 'No description'" - > - <template v-slot:prepend> - <v-icon>mdi-cube-outline</v-icon> - </template> - <template v-slot:append> - <v-chip size="small" color="grey" variant="flat"> - SN: { obj.serialnumber || 'N/A' } - </v-chip> - </template> - </v-list-item> - ))} - </v-list> - ) : ( - <v-alert type="info" variant="tonal" density="compact"> - No objects in this storage - </v-alert> - )} - </div> - - {childStorages.value.length > 0 && ( - <div class="mt-4"> - <h3 class="text-subtitle-1 mb-2">Nested storage spaces:</h3> - <v-expansion-panels> - {childStorages.value.map(childStorage => ( - <StorageBox - key={childStorage.id} - storage={childStorage} - objects={props.objects} - nestedStorages={props.nestedStorages} - /> - ))} - </v-expansion-panels> - </div> - )} - </v-expansion-panel-text> - </v-expansion-panel> - ); - } + return { objectsInCurrentStorage, childStorages }; + }, + template: ` + <v-expansion-panel> + <v-expansion-panel-title> + <div class="d-flex align-center"> + <v-icon class="mr-2">mdi-package-variant-closed</v-icon> + <span>{{ storage.location || \`Storage #\${storage.id}\` }}</span> + <v-chip class="ml-2" size="small" color="primary" variant="outlined"> + {{ objectsInCurrentStorage.length }} objects + </v-chip> + </div> + </v-expansion-panel-title> + <v-expansion-panel-text> + <div class="mb-4"> + <h3 class="text-subtitle-1 mb-2">Objects in this storage:</h3> + <v-list v-if="objectsInCurrentStorage.length > 0" lines="two"> + <v-list-item + v-for="obj in objectsInCurrentStorage" + :key="obj.id" + :title="obj.name" + :subtitle="obj.description || 'No description'" + > + <template v-slot:prepend> + <v-icon>mdi-cube-outline</v-icon> + </template> + <template v-slot:append> + <v-chip size="small" color="grey" variant="flat"> + SN: {{ obj.serialnumber || 'N/A' }} + </v-chip> + </template> + </v-list-item> + </v-list> + <v-alert v-else type="info" variant="tonal" density="compact"> + No objects in this storage + </v-alert> + </div> + + <div v-if="childStorages.length > 0" class="mt-4"> + <h3 class="text-subtitle-1 mb-2">Nested storage spaces:</h3> + <v-expansion-panels> + <storage-box + v-for="childStorage in childStorages" + :key="childStorage.id" + :storage="childStorage" + :objects="objects" + :nested-storages="nestedStorages" + /> + </v-expansion-panels> + </div> + </v-expansion-panel-text> + </v-expansion-panel> + ` }); // Main component logic const storageStore = useStorageStore(); const loading = ref(false); -const error = ref(null); -const selectedStorageId = ref(null); +const error = ref<string | null>(null); +const selectedStorageId = ref<number | null>(null); -const storageSpaces = computed(() => storageStore.storageSpaces); -const objects = computed(() => storageStore.objects); +const storageSpaces = computed<StorageSpace[]>(() => storageStore.storageSpaces); +const objects = computed<StorageObject[]>(() => storageStore.objects); -const selectedStorage = computed(() => { +const selectedStorage = computed<StorageSpace | null>(() => { if (!selectedStorageId.value) return null; - return storageSpaces.value.find(s => s.id === selectedStorageId.value); + return storageSpaces.value.find(s => s.id === selectedStorageId.value) || null; }); -const objectsInStorage = computed(() => { +const objectsInStorage = computed<StorageObject[]>(() => { if (!selectedStorageId.value) return []; return objects.value; }); -const nestedStorages = computed(() => { +const nestedStorages = computed<StorageSpace[]>(() => { return storageSpaces.value; }); @@ -162,13 +183,13 @@ watch(selectedStorageId, async (newId) => { } }); -async function fetchStorageData(storageId) { +async function fetchStorageData(storageId: number): Promise<void> { loading.value = true; error.value = null; try { await storageStore.fetchStorageHierarchy(storageId); - } catch (err) { + } catch (err: any) { error.value = err.message || 'Failed to load storage data'; } finally { loading.value = false; @@ -184,7 +205,7 @@ onMounted(async () => { if (storageSpaces.value.length > 0) { selectedStorageId.value = storageSpaces.value[0].id; } - } catch (err) { + } catch (err: any) { error.value = err.message || 'Failed to load storage spaces'; } finally { loading.value = false; diff --git a/web/src/stores/storage.ts b/web/src/stores/storage.ts index 3deabf2..d48da2c 100644 --- a/web/src/stores/storage.ts +++ b/web/src/stores/storage.ts @@ -1,31 +1,49 @@ import { defineStore } from 'pinia'; +// Define types for our data +interface StorageSpace { + id: number; + parent: { + valid: boolean; + int64: number; + } | null; + location: string | null; +} + +interface StorageObject { + id: number; + storagespaceId: number; + name: string; + description: string | null; + serialnumber: string | null; +} + export const useStorageStore = defineStore('storage', { state: () => ({ - storageSpaces: [] as any[], - objects: [] as any[], + storageSpaces: [] as StorageSpace[], + objects: [] as StorageObject[], loading: false, error: null as string | null, }), getters: { - getStorageById: (state) => (id: number) => { + getStorageById: (state) => (id: number): StorageSpace | undefined => { return state.storageSpaces.find(storage => storage.id === id); }, - getObjectsInStorage: (state) => (storageId: number) => { + getObjectsInStorage: (state) => (storageId: number): StorageObject[] => { return state.objects.filter(obj => obj.storagespaceId === storageId); }, - getChildStorages: (state) => (parentId: number) => { + getChildStorages: (state) => (parentId: number): StorageSpace[] => { return state.storageSpaces.filter(storage => - storage.parent && storage.parent === parentId + storage.parent && storage.parent.valid && storage.parent.int64 === parentId ); } }, actions: { - async fetchStorageSpaces() { + async fetchStorageSpaces(): Promise<void> { this.loading = true; this.error = null; @@ -54,7 +72,7 @@ export const useStorageStore = defineStore('storage', { } }, - async fetchObjectsInStorage(storageId: number) { + async fetchObjectsInStorage(storageId: number): Promise<void> { this.loading = true; this.error = null; @@ -70,7 +88,7 @@ export const useStorageStore = defineStore('storage', { } const data = await response.json(); - this.objects = data; + this.objects = data as StorageObject[]; } catch (error: any) { this.error = error.message; throw error; @@ -79,7 +97,7 @@ export const useStorageStore = defineStore('storage', { } }, - async fetchStorageHierarchy(rootStorageId: number) { + async fetchStorageHierarchy(rootStorageId: number): Promise<void> { this.loading = true; this.error = null; @@ -101,7 +119,7 @@ export const useStorageStore = defineStore('storage', { } const data = await response.json(); - this.objects = data; + this.objects = data as StorageObject[]; } catch (error: any) { this.error = error.message; throw error;