stash
This commit is contained in:
parent
6d2167ac66
commit
4787acd8eb
23 changed files with 1241 additions and 25 deletions
207
frontend/src/views/GroupDetail.vue
Normal file
207
frontend/src/views/GroupDetail.vue
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<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="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Members</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="refresh" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
<a @click="show_invite = true" class="align-middle">
|
||||
<b-icon-plus></b-icon-plus>
|
||||
Add member
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="show_invite">
|
||||
<td>
|
||||
<input type="text" class="form-control" placeholder="user@domain"
|
||||
v-model="invitee">
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" @click="tryInvite">Invite</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="member in group.members" :key="member.id">
|
||||
<td>
|
||||
<user-name-tag :user="member"/>
|
||||
</td>
|
||||
<td class="table-action">
|
||||
<a href="#" class="align-middle" @click.prevent="tryRemoveMember(member)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
Remove
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</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="`/inventory/${item.id}`">{{ 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="`/inventory/${item.id}/edit`">
|
||||
<b-icon-pencil-square></b-icon-pencil-square>
|
||||
</router-link>
|
||||
<a :href="`/inventory/${item.id}/delete`" @click.prevent="tryDeleteItem(item)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
</a>
|
||||
</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="`/inventory/${item.id}`">
|
||||
{{ 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="`/inventory/${item.id}/edit`"
|
||||
class="btn btn-primary btn-sm">Edit
|
||||
</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=${id}`" 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 UserNameTag from "@/components/UserNameTag.vue";
|
||||
|
||||
export default {
|
||||
name: 'GroupDetail',
|
||||
components: {
|
||||
BaseLayout,
|
||||
UserNameTag,
|
||||
...BIcons
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
group: null,
|
||||
layout: "grid",
|
||||
show_invite: false,
|
||||
invitee: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
items() {
|
||||
return this.groupInventoryItems(this.id)
|
||||
},
|
||||
...mapGetters(['groupInventoryItems'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchGroup', 'removeGroupMember', 'inviteToGroup', 'fetchGroupInventoryItems',
|
||||
'deleteInventoryItem']),
|
||||
refresh() {
|
||||
this.fetchGroup({id: this.id}).then((group) => {
|
||||
this.group = group
|
||||
})
|
||||
},
|
||||
fetchItems() {
|
||||
this.fetchGroupInventoryItems(this.id)
|
||||
},
|
||||
tryInvite() {
|
||||
this.inviteToGroup({groupId: this.id, groupHandle: this.group.handle, invitee: this.invitee})
|
||||
.then((ok) => {
|
||||
if (ok) {
|
||||
this.show_invite = false
|
||||
this.invitee = ""
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryRemoveMember(member) {
|
||||
this.removeGroupMember({groupId: this.id, identityId: member.id}).then(() => {
|
||||
this.refresh()
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryDeleteItem(item) {
|
||||
this.deleteInventoryItem(item).then(() => {
|
||||
this.fetchItems()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refresh()
|
||||
this.fetchItems()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
52
frontend/src/views/GroupNew.vue
Normal file
52
frontend/src/views/GroupNew.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">New group</h1>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" v-model="name">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="width: 100%"
|
||||
@click="tryCreateGroup">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions} from "vuex";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
|
||||
export default {
|
||||
name: 'GroupNew',
|
||||
components: {
|
||||
BaseLayout
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createGroup']),
|
||||
tryCreateGroup() {
|
||||
this.createGroup({name: this.name}).then((group) => {
|
||||
this.$router.push(`/groups/${group.id}`)
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
122
frontend/src/views/Groups.vue
Normal file
122
frontend/src/views/Groups.vue
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">Groups</h1>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">My Groups</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="d-none d-md-table-cell" style="width:25%">Members</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="fetchGroups" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
<router-link to="/groups/new" class="align-middle">
|
||||
<b-icon-plus></b-icon-plus>
|
||||
New group
|
||||
</router-link>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="group in groups" :key="group.id">
|
||||
<td>
|
||||
<router-link :to="`/groups/${group.id}`">{{ group.handle }}</router-link>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">{{ group.members.length }}</td>
|
||||
<td class="table-action"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Open group invites</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th class="d-none d-md-table-cell" style="width:25%">Invited by</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="fetchGroupInvites" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="invite in groupInvites" :key="invite.id">
|
||||
<td>{{ invite.group }}</td>
|
||||
<td class="d-none d-md-table-cell">{{ invite.inviter }}</td>
|
||||
<td class="table-action">
|
||||
<button class="btn btn-sm btn-success" @click="tryAcceptInvite(invite)">
|
||||
<b-icon-check></b-icon-check>
|
||||
Accept
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="tryDeclineInvite(invite)">
|
||||
<b-icon-x></b-icon-x>
|
||||
Decline
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapState} from "vuex";
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
|
||||
export default {
|
||||
name: 'Groups',
|
||||
components: {
|
||||
BaseLayout,
|
||||
...BIcons
|
||||
},
|
||||
computed: {
|
||||
...mapState(['groups', 'groupInvites']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchGroups', 'fetchGroupInvites', 'acceptGroupInvite', 'declineGroupInvite']),
|
||||
tryAcceptInvite(invite) {
|
||||
this.acceptGroupInvite(invite).then(() => {
|
||||
this.fetchGroupInvites()
|
||||
this.fetchGroups()
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryDeclineInvite(invite) {
|
||||
this.declineGroupInvite(invite).then(() => {
|
||||
this.fetchGroupInvites()
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchGroups()
|
||||
this.fetchGroupInvites()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -92,7 +92,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["inventory_items"]),
|
||||
...mapGetters(["loaded_items"]),
|
||||
...mapState(["availability_policies", "storage_locations"]),
|
||||
item() {
|
||||
return {
|
||||
|
|
@ -104,14 +104,14 @@ export default {
|
|||
image: "",
|
||||
files: [],
|
||||
storage_location: null,
|
||||
...this.inventory_items.find(item => item.id === parseInt(this.id))
|
||||
...this.loaded_items.find(item => item.id === parseInt(this.id))
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["fetchInventoryItems", "updateInventoryItem", "fetchInfo", "fetchStorageLocations"]),
|
||||
changeFiles(files) {
|
||||
this.inventory_items.find(item => item.id === parseInt(this.id)).files = files
|
||||
this.loaded_items.find(item => item.id === parseInt(this.id)).files = files
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@
|
|||
<label for="property" class="form-label">Property</label>
|
||||
<property-field :value="item.properties"></property-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="owner" class="form-label">Owner</label>
|
||||
<select class="form-select" id="owner" name="owner" v-model="item.owner_group">
|
||||
<option :value="null">Myself</option>
|
||||
<option v-for="group in groups" :value="group.id" :key="group.id">
|
||||
{{ group.handle }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="quantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
||||
|
|
@ -96,19 +105,24 @@ export default {
|
|||
tags: [],
|
||||
properties: [],
|
||||
files: [],
|
||||
storage_location: null
|
||||
storage_location: null,
|
||||
owner_group: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createInventoryItem', 'fetchInfo', 'fetchStorageLocations'])
|
||||
...mapActions(['createInventoryItem', 'fetchInfo', 'fetchStorageLocations', 'fetchGroups'])
|
||||
},
|
||||
computed: {
|
||||
...mapState(["availability_policies", "storage_locations"]),
|
||||
...mapState(["availability_policies", "storage_locations", "groups"]),
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchInfo();
|
||||
await this.fetchStorageLocations();
|
||||
await this.fetchGroups();
|
||||
if (this.$route.query.group) {
|
||||
this.item.owner_group = parseInt(this.$route.query.group, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue