stash
This commit is contained in:
parent
ebd0db4b43
commit
2bb6624d50
4 changed files with 156 additions and 216 deletions
|
|
@ -619,12 +619,16 @@ export default createStore({
|
|||
const {domain} = splitGroupHandle(groupHandle)
|
||||
const servers = await dispatch('getFriendServers', {username: 'x@' + domain})
|
||||
const items = await servers.get(getters.signAuth, '/api/v1/inventory_items/' + encodeHandleForUrl(groupHandle) + '/')
|
||||
// Keyed by the full handle, not a bare id: a group pk is only unique within its own
|
||||
// backend's database, so two different domains could otherwise collide on the same
|
||||
// item_map key - the handle already carries the domain, so no separate namespacing is needed.
|
||||
commit('setInventoryItems', {url: '/' + groupHandle, items})
|
||||
return items
|
||||
},
|
||||
async fetchFriendInventoryItems({commit, dispatch, getters}, {friendHandle}) {
|
||||
const servers = await dispatch('getFriendServers', {username: friendHandle})
|
||||
const items = await servers.get(getters.signAuth, '/api/v1/inventory_items/' + encodeHandleForUrl(friendHandle) + '/')
|
||||
items.map(item => item.files.map(file => file.owner = item.owner))
|
||||
commit('setInventoryItems', {url: '/' + friendHandle, items})
|
||||
return items
|
||||
},
|
||||
async fetchFiles({state, commit, dispatch, getters}) {
|
||||
if (state.last_load.files > Date.now() - 1000 * 60 * 60 * 24) {
|
||||
return state.files
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">{{ group ? group.handle : '' }}</h1>
|
||||
<div class="row" v-if="group">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Members</h5>
|
||||
|
|
@ -50,136 +50,6 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Group inventory</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%">Availability Policy</th>
|
||||
<th class="d-none d-md-table-cell" style="width:25%">Amount</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in items" :key="item.id">
|
||||
<td>
|
||||
<router-link :to="itemRoute(item)">{{ item.name }}</router-link>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<span class="badge bg-secondary text-white">{{ item.availability_policy }}</span>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
|
||||
<td class="table-action">
|
||||
<router-link :to="`${itemRoute(item)}/edit`">
|
||||
<b-icon-pencil-square></b-icon-pencil-square>
|
||||
</router-link>
|
||||
<a :href="`${itemRoute(item)}/delete`" @click.prevent="tryDeleteItem(item)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
</a>
|
||||
<router-link v-if="printLinkFor(item)" :to="printLinkFor(item)">
|
||||
<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="item in items" :key="item.id">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-0">
|
||||
<router-link :to="itemRoute(item)">
|
||||
{{ item.name }}
|
||||
</router-link>
|
||||
</h5>
|
||||
<div class="card-text text-black-50">
|
||||
<span class="badge bg-secondary text-white">{{ item.availability_policy }}</span>
|
||||
<span class="float-right">{{ item.owned_quantity }}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-sm" @click="tryDeleteItem(item)">
|
||||
Delete
|
||||
</button>
|
||||
<router-link :to="`${itemRoute(item)}/edit`"
|
||||
class="btn btn-primary btn-sm">Edit
|
||||
</router-link>
|
||||
<router-link v-if="printLinkFor(item)" :to="printLinkFor(item)"
|
||||
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="fetchItems">Refresh</button>
|
||||
<router-link :to="`/inventory/new?group=${encodeHandleForUrl(group.handle)}`"
|
||||
class="btn btn-primary">Add
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Group storage locations</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<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 storageLocations" :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="tryDeleteLocation(location)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
</a>
|
||||
<router-link v-if="locationPrintLinkFor(location)" :to="locationPrintLinkFor(location)">
|
||||
<b-icon-qr-code></b-icon-qr-code>
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card">
|
||||
<button class="btn" @click="fetchLocations">Refresh</button>
|
||||
<router-link :to="`/storage-locations/new?group=${encodeHandleForUrl(group.handle)}`"
|
||||
class="btn btn-primary">Add
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
@ -187,11 +57,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
import {mapActions} from "vuex";
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
import UserNameTag from "@/components/UserNameTag.vue";
|
||||
import {decodeHandleFromUrl, encodeHandleForUrl} from "@/router";
|
||||
import {decodeHandleFromUrl} from "@/router";
|
||||
|
||||
export default {
|
||||
name: 'GroupDetail',
|
||||
|
|
@ -209,7 +79,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
group: null,
|
||||
layout: "grid",
|
||||
show_invite: false,
|
||||
invitee: ""
|
||||
}
|
||||
|
|
@ -217,19 +86,10 @@ export default {
|
|||
computed: {
|
||||
decodedHandle() {
|
||||
return decodeHandleFromUrl(this.handle)
|
||||
},
|
||||
items() {
|
||||
return this.group ? this.groupInventoryItems(this.group.handle) : []
|
||||
},
|
||||
storageLocations() {
|
||||
return this.group ? this.groupStorageLocations(this.group.handle) : []
|
||||
},
|
||||
...mapGetters(['groupInventoryItems', 'groupStorageLocations'])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
encodeHandleForUrl,
|
||||
...mapActions(['fetchGroup', 'removeGroupMember', 'inviteToGroup', 'fetchGroupInventoryItems',
|
||||
'deleteInventoryItem', 'fetchGroupStorageLocations', 'deleteStorageLocation']),
|
||||
...mapActions(['fetchGroup', 'removeGroupMember', 'inviteToGroup']),
|
||||
// Loads by handle regardless of which backend actually hosts the group - see
|
||||
// store.js's fetchGroup.
|
||||
refresh() {
|
||||
|
|
@ -237,33 +97,6 @@ export default {
|
|||
this.group = group
|
||||
})
|
||||
},
|
||||
fetchItems() {
|
||||
if (this.group) {
|
||||
this.fetchGroupInventoryItems({groupHandle: this.group.handle})
|
||||
}
|
||||
},
|
||||
itemRoute(item) {
|
||||
return `/inventory/${encodeHandleForUrl(this.group.handle)}/${item.id}`
|
||||
},
|
||||
printLinkFor(item) {
|
||||
return {path: '/print', query: {kind: 'item', userHandle: this.group.handle, item: item.id}}
|
||||
},
|
||||
fetchLocations() {
|
||||
if (this.group) {
|
||||
this.fetchGroupStorageLocations({groupHandle: this.group.handle})
|
||||
}
|
||||
},
|
||||
locationRoute(location) {
|
||||
return `/storage-locations/${encodeHandleForUrl(this.group.handle)}/${location.id}`
|
||||
},
|
||||
locationPrintLinkFor(location) {
|
||||
return {path: '/print', query: {kind: 'storage-location', userHandle: this.group.handle, location: location.id}}
|
||||
},
|
||||
tryDeleteLocation(location) {
|
||||
this.deleteStorageLocation(location).then(() => {
|
||||
this.fetchLocations()
|
||||
})
|
||||
},
|
||||
tryInvite() {
|
||||
this.inviteToGroup({groupHandle: this.group.handle, invitee: this.invitee}).then((ok) => {
|
||||
if (ok) {
|
||||
|
|
@ -279,18 +112,10 @@ export default {
|
|||
this.refresh()
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryDeleteItem(item) {
|
||||
this.deleteInventoryItem(item).then(() => {
|
||||
this.fetchItems()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refresh().then(() => {
|
||||
this.fetchItems()
|
||||
this.fetchLocations()
|
||||
})
|
||||
this.refresh()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,22 @@
|
|||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">Storage Locations</h1>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="ownerSelect" class="form-label">Viewing storage locations 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>
|
||||
</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 Storage Locations</h5>
|
||||
<h5 class="card-title">{{ selectedOwner }}'s Storage Locations</h5>
|
||||
<button v-if="layout === 'grid'" @click="layout = 'table'" class="btn">
|
||||
<b-icon-list></b-icon-list>
|
||||
</button>
|
||||
|
|
@ -25,7 +36,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="location in storage_locations" :key="location.id">
|
||||
<tr v-for="location in locations" :key="location.id">
|
||||
<td>
|
||||
<router-link :to="locationRoute(location)">{{ location.name }}</router-link>
|
||||
</td>
|
||||
|
|
@ -40,7 +51,7 @@
|
|||
<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)">
|
||||
<a :href="`${locationRoute(location)}/delete`" @click.prevent="tryDeleteLocation(location)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
</a>
|
||||
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)">
|
||||
|
|
@ -55,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="location in storage_locations"
|
||||
<div class="col-12 col-md-4 col-lg-3 col-xl-2" v-for="location in locations"
|
||||
:key="location.id">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
|
@ -73,7 +84,7 @@
|
|||
</div>
|
||||
<div class="btn-group mt-2">
|
||||
<button class="btn btn-danger btn-sm"
|
||||
@click="deleteStorageLocation(location)">Delete
|
||||
@click="tryDeleteLocation(location)">Delete
|
||||
</button>
|
||||
<router-link :to="`${locationRoute(location)}/edit`"
|
||||
class="btn btn-primary btn-sm">Edit
|
||||
|
|
@ -95,8 +106,8 @@
|
|||
|
||||
</div>
|
||||
<div class="card">
|
||||
<button class="btn" @click="fetchStorageLocations">Refresh</button>
|
||||
<router-link to="/storage-locations/new" class="btn btn-primary">Add</router-link>
|
||||
<button class="btn" @click="fetchLocationsForOwner">Refresh</button>
|
||||
<router-link :to="addLocationRoute" class="btn btn-primary">Add</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -116,6 +127,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
layout: "grid",
|
||||
selectedOwner: null,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -123,14 +135,37 @@ export default {
|
|||
...BIcons
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["identityIdByHandle", "groupIdByHandle"]),
|
||||
...mapState(["user", "storage_locations"]),
|
||||
...mapGetters(["identityIdByHandle", "groupIdByHandle", "groupStorageLocations"]),
|
||||
...mapState(["user", "storage_locations", "groups", "groupMemberships"]),
|
||||
// Groups hosted here plus groups only known via a GroupMembership pointer - see
|
||||
// Groups.vue's allGroups and Inventory.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))
|
||||
},
|
||||
locations() {
|
||||
return this.selectedOwner === this.user ? this.storage_locations : this.groupStorageLocations(this.selectedOwner)
|
||||
},
|
||||
addLocationRoute() {
|
||||
return this.selectedOwner === this.user
|
||||
? '/storage-locations/new'
|
||||
: `/storage-locations/new?group=${encodeHandleForUrl(this.selectedOwner)}`
|
||||
}
|
||||
},
|
||||
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.
|
||||
...mapActions(["fetchStorageLocations", "fetchGroupStorageLocations", "deleteStorageLocation",
|
||||
"fetchIdMap", "fetchGroups", "fetchGroupMemberships"]),
|
||||
fetchLocationsForOwner() {
|
||||
return this.selectedOwner === this.user
|
||||
? this.fetchStorageLocations()
|
||||
: this.fetchGroupStorageLocations({groupHandle: this.selectedOwner})
|
||||
},
|
||||
tryDeleteLocation(location) {
|
||||
this.deleteStorageLocation(location).then(() => {
|
||||
this.fetchLocationsForOwner()
|
||||
})
|
||||
},
|
||||
locationRoute(location) {
|
||||
return `/storage-locations/${encodeHandleForUrl(location.owner_group || location.owner)}/${location.id}`
|
||||
},
|
||||
|
|
@ -153,9 +188,20 @@ export default {
|
|||
return {path: '/print', query: {kind: 'storage-location', userHandle, location: location.id}}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedOwner() {
|
||||
this.fetchLocationsForOwner()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// Set before the first render so addLocationRoute/locations never see selectedOwner=null
|
||||
// while user is already populated (which would misroute to the group branch).
|
||||
this.selectedOwner = this.user
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchStorageLocations()
|
||||
await this.fetchIdMap()
|
||||
await this.fetchGroups()
|
||||
await this.fetchGroupMemberships()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue