toolshed/frontend/src/views/StorageLocation.vue
2026-08-26 22:18:02 +02:00

175 lines
9.2 KiB
Vue

<template>
<BaseLayout>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Storage Locations</h1>
<div class="row">
<div class="col-12 col-xl-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">{{ user }}'s Storage Locations</h5>
<button v-if="layout === 'grid'" @click="layout = 'table'" class="btn">
<b-icon-list></b-icon-list>
</button>
<button v-else @click="layout = 'grid'" class="btn">
<b-icon-grid></b-icon-grid>
</button>
</div>
<table class="table table-striped" v-if="layout === 'table'">
<thead>
<tr>
<th style="width:40%;">Name</th>
<th style="width:25%">Path</th>
<th class="d-none d-md-table-cell" style="width:25%">Category</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="location in storage_locations" :key="location.id">
<td>
<router-link :to="locationRoute(location)">{{ location.name }}</router-link>
</td>
<td class="d-none d-md-table-cell">
<span class="text-muted">{{ location.path }}</span>
</td>
<td class="d-none d-md-table-cell">
<span class="badge bg-info text-white" v-if="location.category">{{ location.category }}</span>
<span class="text-muted" v-else>-</span>
</td>
<td class="table-action">
<router-link :to="`${locationRoute(location)}/edit`">
<b-icon-pencil-square></b-icon-pencil-square>
</router-link>
<a :href="`${locationRoute(location)}/delete`" @click.prevent="deleteStorageLocation(location)">
<b-icon-trash></b-icon-trash>
</a>
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)">
<b-icon-link></b-icon-link>
</router-link>
<router-link v-if="printLinkFor(location)" :to="printLinkFor(location)">
<b-icon-qr-code></b-icon-qr-code>
</router-link>
</td>
</tr>
</tbody>
</table>
<div class="card-body" v-else>
<div class="row">
<div class="col-12 col-md-4 col-lg-3 col-xl-2" v-for="location in storage_locations"
:key="location.id">
<div class="card">
<div class="card-body">
<h5 class="card-title mb-0">
<router-link :to="locationRoute(location)">
{{ location.name }}
</router-link>
</h5>
<div class="card-text text-black-50">
<small class="text-muted d-block">{{ location.path }}</small>
<span class="badge bg-info text-white" v-if="location.category">{{ location.category }}</span>
</div>
<div class="card-text" v-if="location.description">
<small>{{ location.description }}</small>
</div>
<div class="btn-group mt-2">
<button class="btn btn-danger btn-sm"
@click="deleteStorageLocation(location.id)">Delete
</button>
<router-link :to="`${locationRoute(location)}/edit`"
class="btn btn-primary btn-sm">Edit
</router-link>
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)"
class="btn btn-secondary btn-sm">
<b-icon-link></b-icon-link>
</router-link>
<router-link v-if="printLinkFor(location)" :to="printLinkFor(location)"
class="btn btn-secondary btn-sm">
<b-icon-qr-code></b-icon-qr-code>
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<button class="btn" @click="fetchStorageLocations">Refresh</button>
<router-link to="/storage-locations/new" class="btn btn-primary">Add</router-link>
</div>
</div>
</div>
</div>
</main>
</BaseLayout>
</template>
<script>
import {mapActions, mapGetters, mapState} from "vuex";
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import {shortenedRoute, encodeHandleForUrl} from "@/router";
export default {
name: "StorageLocation",
data() {
return {
layout: "grid",
}
},
components: {
BaseLayout,
...BIcons
},
computed: {
...mapGetters(["identityIdByHandle", "groupIdByHandle"]),
...mapState(["user", "storage_locations"]),
},
methods: {
...mapActions(["fetchStorageLocations", "deleteStorageLocation", "fetchIdMap"]),
// This list is always the caller's own personal locations (fetchStorageLocations has no
// group content), so the owner_group branches below are currently unreachable here - kept
// for parity with Inventory.vue's shortIdLink/printLinkFor in case that ever changes.
locationRoute(location) {
return `/storage-locations/${encodeHandleForUrl(location.owner_group || location.owner)}/${location.id}`
},
shortIdLink(location) {
if (location.owner_group) {
const owner_group_id = this.groupIdByHandle[location.owner_group]
if (owner_group_id === undefined) return null
return shortenedRoute({
kind: 'group_storage_location', owner_group_id, storage_location_id: location.id
})
}
const owner_identity_id = this.identityIdByHandle[location.owner]
if (owner_identity_id === undefined) return null
return shortenedRoute({kind: 'storage_location', owner_identity_id, storage_location_id: location.id})
},
// Routes to Print.vue with this location's raw identity, same shape as Inventory.vue's
// printLinkFor. See docs/implementation.md#print-link-shape-for-storage-locations.
printLinkFor(location) {
const userHandle = location.owner_group || location.owner
return {path: '/print', query: {kind: 'storage-location', userHandle, location: location.id}}
},
},
async mounted() {
await this.fetchStorageLocations()
await this.fetchIdMap()
}
}
</script>
<style scoped>
.card-text small {
font-size: 0.8rem;
}
.btn-group {
width: 100%;
}
.btn-group .btn {
flex: 1;
}
</style>