This commit is contained in:
j3d1 2026-08-27 19:41:52 +02:00
parent ebd0db4b43
commit 2bb6624d50
4 changed files with 156 additions and 216 deletions

View file

@ -2,12 +2,26 @@
<BaseLayout>
<main class="content">
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Inventory Own & Friends"</h1>
<h1 class="h3 mb-3">Inventory</h1>
<div class="row mb-3">
<div class="col-12 col-md-4">
<label for="ownerSelect" class="form-label">Viewing inventory for</label>
<select id="ownerSelect" class="form-select" v-model="selectedOwner">
<option :value="user">{{ user }} (you)</option>
<option v-for="group in ownerGroups" :value="group.handle" :key="group.handle">
{{ group.handle }}
</option>
<option v-for="friend in friends" :value="friend.handle" :key="friend.handle">
{{ friend.handle }}
</option>
</select>
</div>
</div>
<div class="row">
<div class="col-12 col-xl-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">{{ user }}'s Inventory</h5>
<h5 class="card-title">{{ selectedOwner }}'s Inventory</h5>
<button v-if="layout === 'grid'" @click="layout = 'table'" class="btn">
<b-icon-list></b-icon-list>
</button>
@ -25,7 +39,7 @@
</tr>
</thead>
<tbody>
<tr v-for="item in inventory_items" :key="item.id">
<tr v-for="item in items" :key="item.id">
<td>
<router-link :to="itemRoute(item)">{{ item.name }}</router-link>
</td>
@ -34,10 +48,10 @@
</td>
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
<td class="table-action">
<router-link :to="`${itemRoute(item)}/edit`">
<router-link v-if="canEdit" :to="`${itemRoute(item)}/edit`">
<b-icon-pencil-square></b-icon-pencil-square>
</router-link>
<a :href="`${itemRoute(item)}/delete`" @click.prevent="deleteInventoryItem(item)">
<a v-if="canEdit" :href="`${itemRoute(item)}/delete`" @click.prevent="tryDeleteItem(item)">
<b-icon-trash></b-icon-trash>
</a>
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)">
@ -52,7 +66,7 @@
</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="item in inventory_items"
<div class="col-12 col-md-4 col-lg-3 col-xl-2" v-for="item in items"
:key="item.id">
<div class="card">
<img :src="item.image" class="" :alt="item.image">
@ -70,10 +84,10 @@
<span class="float-right">{{ item.owned_quantity }}</span>
</div>
<div class="btn-group">
<button class="btn btn-danger btn-sm"
@click="deleteInventoryItem(item)">Delete
<button v-if="canEdit" class="btn btn-danger btn-sm"
@click="tryDeleteItem(item)">Delete
</button>
<router-link :to="`${itemRoute(item)}/edit`"
<router-link v-if="canEdit" :to="`${itemRoute(item)}/edit`"
class="btn btn-primary btn-sm">Edit
</router-link>
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)"
@ -93,8 +107,8 @@
</div>
<div class="card">
<button class="btn" @click="fetchInventoryItems">Refresh</button>
<router-link to="/inventory/new" class="btn btn-primary">Add</router-link>
<button class="btn" @click="fetchItemsForOwner">Refresh</button>
<router-link v-if="canEdit" :to="addItemRoute" class="btn btn-primary">Add</router-link>
</div>
</div>
</div>
@ -115,6 +129,7 @@ export default {
data() {
return {
layout: "grid",
selectedOwner: null,
}
},
components: {
@ -123,15 +138,53 @@ export default {
...BIcons
},
computed: {
...mapGetters(["inventory_items", "loaded_items", "identityIdByHandle", "groupIdByHandle"]),
...mapState(["user", "storage_locations"]),
...mapGetters(["inventory_items", "groupInventoryItems", "loaded_items", "identityIdByHandle", "groupIdByHandle"]),
...mapState(["user", "storage_locations", "groups", "groupMemberships", "friends"]),
// Groups hosted here plus groups only known via a GroupMembership pointer - see
// Groups.vue's allGroups and InventoryNew.vue's ownerGroups for the same merge/dedupe.
ownerGroups() {
const hostedHandles = new Set(this.groups.map(group => group.handle))
const foreign = this.groupMemberships.filter(m => !hostedHandles.has(m.handle))
return [...this.groups, ...foreign].sort((a, b) => a.handle.localeCompare(b.handle))
},
isGroupSelected() {
return !!this.selectedOwner && this.selectedOwner.startsWith('#')
},
// Own items and group items can be created/edited/deleted here; a friend's items are
// shown for browsing only - the backend rejects writes for anyone but the owner or a
// fellow group member (see inventory.py's perform_create/update/destroy).
canEdit() {
return this.selectedOwner === this.user || this.isGroupSelected
},
items() {
// item_map is keyed by owner handle regardless of whether that owner is a group or a
// friend, so the same getter serves both - see store.js's groupInventoryItems.
return this.selectedOwner === this.user ? this.inventory_items : this.groupInventoryItems(this.selectedOwner)
},
addItemRoute() {
return this.selectedOwner === this.user
? '/inventory/new'
: `/inventory/new?group=${encodeHandleForUrl(this.selectedOwner)}`
}
},
methods: {
...mapActions(["fetchInventoryItems", "deleteInventoryItem", "fetchStorageLocations", "fetchIdMap"]),
// Always the viewer's own items (fetchInventoryItems has no group filter); the owner
// handle is always included so /inventory/:handle/:id has exactly one shape.
...mapActions(["fetchInventoryItems", "fetchGroupInventoryItems", "fetchFriendInventoryItems",
"deleteInventoryItem", "fetchStorageLocations", "fetchIdMap", "fetchGroups",
"fetchGroupMemberships", "fetchFriends"]),
fetchItemsForOwner() {
if (this.selectedOwner === this.user) return this.fetchInventoryItems()
if (this.isGroupSelected) return this.fetchGroupInventoryItems({groupHandle: this.selectedOwner})
return this.fetchFriendInventoryItems({friendHandle: this.selectedOwner})
},
tryDeleteItem(item) {
this.deleteInventoryItem(item).then(() => {
this.fetchItemsForOwner()
})
},
// The owner handle is derived from the item itself, not the current selection, so a
// link stays correct even if the dropdown selection changes underneath it.
itemRoute(item) {
return `/inventory/${encodeHandleForUrl(this.user)}/${item.id}`
return `/inventory/${encodeHandleForUrl(item.owner_group || item.owner)}/${item.id}`
},
only_images(files) {
return files.filter(file => file.mime_type.startsWith("image/"));
@ -156,10 +209,22 @@ export default {
return {path: '/print', query: {kind: 'item', userHandle, item: item.id}}
},
},
watch: {
selectedOwner() {
this.fetchItemsForOwner()
}
},
created() {
// Set before the first render so addItemRoute/items never see selectedOwner=null
// while user is already populated (which would misroute to the group branch).
this.selectedOwner = this.user
},
async mounted() {
await this.fetchInventoryItems()
await this.fetchStorageLocations()
await this.fetchIdMap()
await this.fetchGroups()
await this.fetchGroupMemberships()
await this.fetchFriends()
}
}
</script>