toolshed/frontend/src/views/StorageLocationEdit.vue
2026-08-27 21:12:31 +02:00

148 lines
6 KiB
Vue

<template>
<BaseLayout>
<main class="content">
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">Edit Storage Location</div>
<div class="card-body">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name"
placeholder="Enter storage location name" v-model="location.name">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" name="description"
placeholder="Enter description" v-model="location.description"></textarea>
</div>
<div class="mb-3">
<label for="category" class="form-label">Category</label>
<select class="form-select" id="category" name="category"
v-model="location.category">
<option value="">No Category</option>
<option v-for="category in categories" :value="category"
:selected="category === location.category">
{{ category }}
</option>
</select>
</div>
<div class="mb-3">
<label for="parent" class="form-label">Parent Location</label>
<select class="form-select" id="parent" name="parent"
v-model="location.parent">
<option value="">No Parent</option>
<option v-for="parent in availableParents" :value="parent.id"
:selected="parent.id === location.parent">
{{ parent.path }}
</option>
</select>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary" style="width: 100%"
@click="submitForm()">Update
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import * as BIcons from "bootstrap-icons-vue";
import {mapActions, mapState} from "vuex";
import BaseLayout from "@/components/BaseLayout.vue";
import {decodeHandleFromUrl, ownerLocationOverviewRoute} from "@/router";
export default {
name: "StorageLocationEdit",
components: {
BaseLayout,
...BIcons
},
props: {
handle: {
type: String,
required: true
},
id: {
type: String,
required: true
}
},
data() {
return {
location: {
name: "",
description: "",
category: null,
parent: null,
},
// The location's own owner's full location list, for the parent dropdown - own
// personal locations, or the owning group's own (see loadLocation).
siblingLocations: []
}
},
computed: {
...mapState(["categories"]),
decodedHandle() {
return decodeHandleFromUrl(this.handle)
},
availableParents() {
// Filter out self and children to prevent circular references
return this.siblingLocations.filter(loc =>
loc.id !== parseInt(this.id) &&
!this.isChildOf(loc, parseInt(this.id))
)
}
},
methods: {
ownerLocationOverviewRoute,
...mapActions(["fetchStorageLocationByHandle", "updateStorageLocation", "fetchInfo",
"fetchStorageLocations", "fetchGroupStorageLocations"]),
async loadLocation() {
const loaded = await this.fetchStorageLocationByHandle({handle: this.decodedHandle, id: this.id})
if (loaded) {
this.location = {...this.location, ...loaded}
}
if (this.decodedHandle.startsWith('#')) {
this.siblingLocations = await this.fetchGroupStorageLocations({groupHandle: this.decodedHandle})
} else {
this.siblingLocations = await this.fetchStorageLocations()
}
},
submitForm() {
// Convert empty strings to null for ForeignKey fields
const locationData = {
...this.location,
category: this.location.category === "" ? null : this.location.category,
parent: this.location.parent === "" ? null : this.location.parent
};
this.updateStorageLocation(locationData).then(updated => this.$router.push(ownerLocationOverviewRoute(updated)));
},
isChildOf(location, parentId) {
// Simple check to prevent circular references
let current = location;
while (current && current.parent) {
if (current.parent === parentId) return true;
current = this.siblingLocations.find(loc => loc.id === current.parent);
}
return false;
}
},
watch: {
handle: 'loadLocation',
id: 'loadLocation'
},
async mounted() {
await this.fetchInfo();
await this.loadLocation();
}
}
</script>
<style scoped>
</style>