stash
This commit is contained in:
parent
3cd9200d87
commit
e88dc898b3
18 changed files with 298 additions and 84 deletions
|
|
@ -9,7 +9,7 @@
|
||||||
<SearchBox/>
|
<SearchBox/>
|
||||||
<div class="navbar-collapse collapse">
|
<div class="navbar-collapse collapse">
|
||||||
<ul class="navbar-nav navbar-align">
|
<ul class="navbar-nav navbar-align">
|
||||||
<router-link to="/scan?navigate=immediate" class="nav-icon">
|
<router-link :to="{name: 'scan', query: {navigate: 'immediate'}}" class="nav-icon">
|
||||||
<b-icon-qr-code-scan class="bi-valign-middle"></b-icon-qr-code-scan>
|
<b-icon-qr-code-scan class="bi-valign-middle"></b-icon-qr-code-scan>
|
||||||
</router-link>
|
</router-link>
|
||||||
<Notifications :notifications="notifications"/>
|
<Notifications :notifications="notifications"/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<nav id="sidebar" class="sidebar">
|
<nav id="sidebar" class="sidebar">
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<router-link to="/" class="sidebar-brand">
|
<router-link :to="{name: 'dashboard'}" class="sidebar-brand">
|
||||||
<img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed Logo" class="align-middle logo mr-2 h-75">
|
<img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed Logo" class="align-middle logo mr-2 h-75">
|
||||||
<span class="align-middle">Toolshed</span>
|
<span class="align-middle">Toolshed</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
@ -10,61 +10,61 @@
|
||||||
Tools & Components
|
Tools & Components
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/inventory" class="sidebar-link">
|
<router-link :to="{name: 'inventory'}" class="sidebar-link">
|
||||||
<b-icon-archive class="bi-valign-middle"></b-icon-archive>
|
<b-icon-archive class="bi-valign-middle"></b-icon-archive>
|
||||||
<span class="align-middle">Inventory</span>
|
<span class="align-middle">Inventory</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/storage-location" class="sidebar-link">
|
<router-link :to="{name: 'locations'}" class="sidebar-link">
|
||||||
<b-icon-boxes class="bi-valign-middle"></b-icon-boxes>
|
<b-icon-boxes class="bi-valign-middle"></b-icon-boxes>
|
||||||
<span class="align-middle">Storage Locations</span>
|
<span class="align-middle">Storage Locations</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/workflows" class="sidebar-link">
|
<router-link :to="{name: 'workflows'}" class="sidebar-link">
|
||||||
<b-icon-diagram3 class="bi-valign-middle"></b-icon-diagram3>
|
<b-icon-diagram3 class="bi-valign-middle"></b-icon-diagram3>
|
||||||
<span class="align-middle">Workflows</span>
|
<span class="align-middle">Workflows</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/friends" class="sidebar-link">
|
<router-link :to="{name: 'friends'}" class="sidebar-link">
|
||||||
<b-icon-people class="bi-valign-middle"></b-icon-people>
|
<b-icon-people class="bi-valign-middle"></b-icon-people>
|
||||||
<span class="align-middle">Friends</span>
|
<span class="align-middle">Friends</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/groups" class="sidebar-link">
|
<router-link :to="{name: 'groups'}" class="sidebar-link">
|
||||||
<b-icon-people-fill class="bi-valign-middle"></b-icon-people-fill>
|
<b-icon-people-fill class="bi-valign-middle"></b-icon-people-fill>
|
||||||
<span class="align-middle">Groups</span>
|
<span class="align-middle">Groups</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/files" class="sidebar-link">
|
<router-link :to="{name: 'files'}" class="sidebar-link">
|
||||||
<b-icon-folder class="bi-valign-middle"></b-icon-folder>
|
<b-icon-folder class="bi-valign-middle"></b-icon-folder>
|
||||||
<span class="align-middle">Files</span>
|
<span class="align-middle">Files</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/admin" class="sidebar-link">
|
<router-link :to="{name: 'admin'}" class="sidebar-link">
|
||||||
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
||||||
<span class="align-middle">Admin</span>
|
<span class="align-middle">Admin</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/swatch" class="sidebar-link">
|
<router-link :to="{name: 'swatch'}" class="sidebar-link">
|
||||||
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
||||||
<span class="align-middle">Swatch</span>
|
<span class="align-middle">Swatch</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/print" class="sidebar-link">
|
<router-link :to="{name: 'print'}" class="sidebar-link">
|
||||||
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
<b-icon-gear class="bi-valign-middle"></b-icon-gear>
|
||||||
<span class="align-middle">Print</span>
|
<span class="align-middle">Print</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="sidebar-item">
|
<li class="sidebar-item">
|
||||||
<router-link to="/scan" class="sidebar-link">
|
<router-link :to="{name: 'scan'}" class="sidebar-link">
|
||||||
<b-icon-qr-code-scan class="bi-valign-middle"></b-icon-qr-code-scan>
|
<b-icon-qr-code-scan class="bi-valign-middle"></b-icon-qr-code-scan>
|
||||||
<span class="align-middle">Scan</span>
|
<span class="align-middle">Scan</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<router-link to="/profile" class="dropdown-item">
|
<router-link :to="{name: 'profile'}" class="dropdown-item">
|
||||||
<b-icon-person class="bi-valign-middle mr-1"></b-icon-person>
|
<b-icon-person class="bi-valign-middle mr-1"></b-icon-person>
|
||||||
Profile
|
Profile
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link to="/settings" class="dropdown-item">
|
<router-link :to="{name: 'settings'}" class="dropdown-item">
|
||||||
<b-icon-sliders class="bi-valign-middle mr-1"></b-icon-sliders>
|
<b-icon-sliders class="bi-valign-middle mr-1"></b-icon-sliders>
|
||||||
Settings &
|
Settings &
|
||||||
Privacy
|
Privacy
|
||||||
|
|
|
||||||
|
|
@ -51,35 +51,39 @@ export function decodeHandleFromUrl(segment) {
|
||||||
|
|
||||||
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
export function ownerOverviewRoute(item) {
|
export function ownerOverviewRoute(item) {
|
||||||
return item.owner_group ? `/inventory/${encodeHandleForUrl(item.owner_group)}` : '/inventory';
|
return item.owner_group
|
||||||
|
? {name: 'inventory', params: {owner: encodeHandleForUrl(item.owner_group)}}
|
||||||
|
: {name: 'inventory'};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ownerLocationOverviewRoute(location) {
|
export function ownerLocationOverviewRoute(location) {
|
||||||
return location.owner_group ? `/storage-location/${encodeHandleForUrl(location.owner_group)}` : '/storage-location';
|
return location.owner_group
|
||||||
|
? {name: 'locations', params: {owner: encodeHandleForUrl(location.owner_group)}}
|
||||||
|
: {name: 'locations'};
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXPANDED_ROUTE_BUILDERS = {
|
const EXPANDED_ROUTE_BUILDERS = {
|
||||||
item: ({owner_identity_id, item_local_id}) => {
|
item: ({owner_identity_id, item_local_id}) => {
|
||||||
const handle = store.getters.identityHandleById[owner_identity_id];
|
const handle = store.getters.identityHandleById[owner_identity_id];
|
||||||
return handle ? `/inventory/${encodeHandleForUrl(handle)}/${item_local_id}` : null;
|
return handle ? {name: 'inventory-detail', params: {handle: encodeHandleForUrl(handle), id: item_local_id}} : null;
|
||||||
},
|
},
|
||||||
group_item: ({owner_group_id, item_local_id}) => {
|
group_item: ({owner_group_id, item_local_id}) => {
|
||||||
const handle = store.getters.groupHandleById[owner_group_id];
|
const handle = store.getters.groupHandleById[owner_group_id];
|
||||||
return handle ? `/inventory/${encodeHandleForUrl(handle)}/${item_local_id}` : null;
|
return handle ? {name: 'inventory-detail', params: {handle: encodeHandleForUrl(handle), id: item_local_id}} : null;
|
||||||
},
|
},
|
||||||
group: ({group_id}) => {
|
group: ({group_id}) => {
|
||||||
const handle = store.getters.groupHandleById[group_id];
|
const handle = store.getters.groupHandleById[group_id];
|
||||||
return handle ? `/groups/${encodeHandleForUrl(handle)}` : null;
|
return handle ? {name: 'group-detail', params: {handle: encodeHandleForUrl(handle)}} : null;
|
||||||
},
|
},
|
||||||
storage_location: ({owner_identity_id, storage_location_id}) => {
|
storage_location: ({owner_identity_id, storage_location_id}) => {
|
||||||
const handle = store.getters.identityHandleById[owner_identity_id];
|
const handle = store.getters.identityHandleById[owner_identity_id];
|
||||||
return handle ? `/storage-locations/${encodeHandleForUrl(handle)}/${storage_location_id}` : null;
|
return handle ? {name: 'locations-detail', params: {handle: encodeHandleForUrl(handle), id: storage_location_id}} : null;
|
||||||
},
|
},
|
||||||
group_storage_location: ({owner_group_id, storage_location_id}) => {
|
group_storage_location: ({owner_group_id, storage_location_id}) => {
|
||||||
const handle = store.getters.groupHandleById[owner_group_id];
|
const handle = store.getters.groupHandleById[owner_group_id];
|
||||||
return handle ? `/storage-locations/${encodeHandleForUrl(handle)}/${storage_location_id}` : null;
|
return handle ? {name: 'locations-detail', params: {handle: encodeHandleForUrl(handle), id: storage_location_id}} : null;
|
||||||
},
|
},
|
||||||
workflow: ({workflow_id}) => `/workflows/${workflow_id}`,
|
workflow: ({workflow_id}) => ({name: 'workflow-detail', params: {id: workflow_id}}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Kinds whose route needs identityHandleById/groupHandleById (from state.idmap); ShortId.vue only waits on an idmap fetch for these.
|
// Kinds whose route needs identityHandleById/groupHandleById (from state.idmap); ShortId.vue only waits on an idmap fetch for these.
|
||||||
|
|
@ -121,30 +125,33 @@ export async function domainQualifiedRoute(domain, ints) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return owned.isLocation
|
return owned.isLocation
|
||||||
? `/storage-locations/${encodeHandleForUrl(resolved.handle)}/${resolved.id}`
|
? {name: 'locations-detail', params: {handle: encodeHandleForUrl(resolved.handle), id: resolved.id}}
|
||||||
: `/inventory/${encodeHandleForUrl(resolved.handle)}/${resolved.id}`;
|
: {name: 'inventory-detail', params: {handle: encodeHandleForUrl(resolved.handle), id: resolved.id}};
|
||||||
}
|
}
|
||||||
|
|
||||||
const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
const routes = [{path: '/', name: 'dashboard', component: Dashboard, meta: {requiresAuth: true}}, {
|
||||||
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
path: '/inventory/:owner?',
|
path: '/inventory/:owner?',
|
||||||
|
name: 'inventory',
|
||||||
component: Inventory,
|
component: Inventory,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
path: '/inventory/:handle/:id',
|
path: '/inventory/:handle/:id',
|
||||||
|
name: 'inventory-detail',
|
||||||
component: InventoryDetail,
|
component: InventoryDetail,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
path: '/inventory/:handle/:id/edit',
|
path: '/inventory/:handle/:id/edit',
|
||||||
|
name: 'inventory-edit',
|
||||||
component: InventoryEdit,
|
component: InventoryEdit,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
// Label/short-link entry point; alias of /inventory/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
// Label/short-link entry point; alias of /inventory/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
||||||
path: '/i/:handle/:id',
|
path: '/i/:handle/:id',
|
||||||
redirect: to => `/inventory/${to.params.handle}/${to.params.id}`
|
redirect: to => ({name: 'inventory-detail', params: to.params})
|
||||||
}, {
|
}, {
|
||||||
// beforeEnter, not redirect: falls through to ShortId.vue when the route can't resolve synchronously. See docs/implementation.md#beforeenter-guard-vs-redirect-for-short_id.
|
// beforeEnter, not redirect: falls through to ShortId.vue when the route can't resolve synchronously. See docs/implementation.md#beforeenter-guard-vs-redirect-for-short_id.
|
||||||
path: '/:short_id',
|
path: '/:short_id',
|
||||||
|
|
@ -160,28 +167,34 @@ const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
||||||
}, {
|
}, {
|
||||||
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
path: '/inventory/new/:group?',
|
path: '/inventory/new/:group?',
|
||||||
|
name: 'inventory-new',
|
||||||
component: InventoryNew,
|
component: InventoryNew,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
path: '/friends',
|
path: '/friends',
|
||||||
|
name: 'friends',
|
||||||
component: Friends,
|
component: Friends,
|
||||||
meta: {requiresAuth: true}
|
meta: {requiresAuth: true}
|
||||||
}, {
|
}, {
|
||||||
path: '/groups',
|
path: '/groups',
|
||||||
|
name: 'groups',
|
||||||
component: Groups,
|
component: Groups,
|
||||||
meta: {requiresAuth: true}
|
meta: {requiresAuth: true}
|
||||||
}, {
|
}, {
|
||||||
path: '/groups/new',
|
path: '/groups/new',
|
||||||
|
name: 'groups-new',
|
||||||
component: GroupNew,
|
component: GroupNew,
|
||||||
meta: {requiresAuth: true}
|
meta: {requiresAuth: true}
|
||||||
}, {
|
}, {
|
||||||
path: '/groups/:handle',
|
path: '/groups/:handle',
|
||||||
|
name: 'group-detail',
|
||||||
component: GroupDetail,
|
component: GroupDetail,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {path: '/files', component: Files, meta: {requiresAuth: true}
|
}, {path: '/files', name: 'files', component: Files, meta: {requiresAuth: true}
|
||||||
}, {path: '/workflows',
|
}, {path: '/workflows',
|
||||||
|
name: 'workflows',
|
||||||
component: Workflows,
|
component: Workflows,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
}, {path: '/workflows/:id/:step?',
|
}, {path: '/workflows/:id/:step?',
|
||||||
|
|
@ -190,10 +203,12 @@ const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {path: '/admin',
|
}, {path: '/admin',
|
||||||
|
name: 'admin',
|
||||||
component: Admin,
|
component: Admin,
|
||||||
meta: {requiresAuth: true}
|
meta: {requiresAuth: true}
|
||||||
}, {path: '/swatch', component: Swatch, meta: {requiresAuth: true}}, {
|
}, {path: '/swatch', name: 'swatch', component: Swatch, meta: {requiresAuth: true}}, {
|
||||||
path: '/print',
|
path: '/print',
|
||||||
|
name: 'print',
|
||||||
component: Print,
|
component: Print,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: route => {
|
props: route => {
|
||||||
|
|
@ -204,23 +219,27 @@ const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: '/scan',
|
path: '/scan',
|
||||||
|
name: 'scan',
|
||||||
component: Scan,
|
component: Scan,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
}, {
|
}, {
|
||||||
path: '/search/:query',
|
path: '/search/:query',
|
||||||
|
name: 'search',
|
||||||
component: Search,
|
component: Search,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {path: '/login', component: Login, meta: {requiresAuth: false}}, {
|
}, {path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}}, {
|
||||||
path: '/register',
|
path: '/register',
|
||||||
|
name: 'register',
|
||||||
component: Register,
|
component: Register,
|
||||||
meta: {requiresAuth: false}
|
meta: {requiresAuth: false}
|
||||||
}, {path: '/pairing', component: Pairing, meta: {requiresAuth: false}}, {
|
}, {path: '/pairing', name: 'pairing', component: Pairing, meta: {requiresAuth: false}}, {
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
|
name: 'profile',
|
||||||
component: Profile,
|
component: Profile,
|
||||||
meta: {requiresAuth: true}
|
meta: {requiresAuth: true}
|
||||||
}, {
|
}, {
|
||||||
path: '/settings', component: Settings, meta: {requiresAuth: true}, children: [{
|
path: '/settings', name: 'settings', component: Settings, meta: {requiresAuth: true}, children: [{
|
||||||
path: '', name: 'account', component: Account, meta: {requiresAuth: true}
|
path: '', name: 'account', component: Account, meta: {requiresAuth: true}
|
||||||
},{
|
},{
|
||||||
path: 'password/', name: 'password', component: Password, meta: {requiresAuth: true}
|
path: 'password/', name: 'password', component: Password, meta: {requiresAuth: true}
|
||||||
|
|
@ -237,27 +256,31 @@ const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
||||||
}]
|
}]
|
||||||
}, {
|
}, {
|
||||||
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
path: '/storage-location/:owner?',
|
path: '/locations/:owner?',
|
||||||
|
name: 'locations',
|
||||||
component: StorageLocation,
|
component: StorageLocation,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
path: '/storage-locations/:handle/:id',
|
path: '/locations/:handle/:id',
|
||||||
|
name: 'locations-detail',
|
||||||
component: StorageLocationDetail,
|
component: StorageLocationDetail,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
path: '/storage-locations/:handle/:id/edit',
|
path: '/locations/:handle/:id/edit',
|
||||||
|
name: 'locations-edit',
|
||||||
component: StorageLocationEdit,
|
component: StorageLocationEdit,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
}, {
|
}, {
|
||||||
// Label/short-link entry point for storage locations, the counterpart of /i/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
// Label/short-link entry point for storage locations, the counterpart of /i/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
||||||
path: '/s/:handle/:id',
|
path: '/s/:handle/:id',
|
||||||
redirect: to => `/storage-locations/${to.params.handle}/${to.params.id}`
|
redirect: to => ({name: 'locations-detail', params: to.params})
|
||||||
}, {
|
}, {
|
||||||
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
path: '/storage-locations/new/:group?',
|
path: '/locations/new/:group?',
|
||||||
|
name: 'locations-new',
|
||||||
component: StorageLocationNew,
|
component: StorageLocationNew,
|
||||||
meta: {requiresAuth: true},
|
meta: {requiresAuth: true},
|
||||||
props: true
|
props: true
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,130 @@
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<main class="content">
|
<main class="content">
|
||||||
<div class="container-fluid p-0">
|
<div class="container-fluid p-0">
|
||||||
<h1 class="h3 mb-3">Dashboard</h1>
|
<div class="d-flex justify-content-between align-items-center flex-wrap mb-3">
|
||||||
|
<h1 class="h3 mb-0">Welcome back, {{ user }}</h1>
|
||||||
|
<div class="btn-group">
|
||||||
|
<router-link :to="{name: 'inventory-new'}" class="btn btn-primary">
|
||||||
|
<b-icon-plus-lg></b-icon-plus-lg>
|
||||||
|
Add item
|
||||||
|
</router-link>
|
||||||
|
<router-link :to="{name: 'locations-new'}" class="btn btn-outline-secondary">
|
||||||
|
<b-icon-plus-lg></b-icon-plus-lg>
|
||||||
|
Add storage location
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-6 col-md-3">
|
||||||
|
<router-link :to="{name: 'inventory'}" class="text-decoration-none">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<b-icon-boxes class="stat-icon me-3"></b-icon-boxes>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ inventory_items.length }}</div>
|
||||||
|
<div class="stat-label">Items</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<router-link :to="{name: 'locations'}" class="text-decoration-none">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<b-icon-geo-alt class="stat-icon me-3"></b-icon-geo-alt>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ storage_locations.length }}</div>
|
||||||
|
<div class="stat-label">Storage locations</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<router-link :to="{name: 'friends'}" class="text-decoration-none">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<b-icon-people class="stat-icon me-3"></b-icon-people>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ friends.length }}</div>
|
||||||
|
<div class="stat-label">Friends</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<router-link :to="{name: 'groups'}" class="text-decoration-none">
|
||||||
|
<div class="card stat-card">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<b-icon-diagram3 class="stat-icon me-3"></b-icon-diagram3>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ ownerGroups.length }}</div>
|
||||||
|
<div class="stat-label">Groups</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-lg-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">Empty card</h5>
|
<h5 class="card-title mb-0">Recent items</h5>
|
||||||
|
<router-link :to="{name: 'inventory'}" class="small">View all</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="logo">
|
<div class="row" v-if="recentItems.length > 0">
|
||||||
<img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo">
|
<div class="col-6 col-md-4 col-xl-3" v-for="item in recentItems" :key="item.id">
|
||||||
|
<router-link :to="itemRoute(item)" class="text-decoration-none">
|
||||||
|
<div class="card">
|
||||||
|
<authenticated-image v-if="only_images(item.files).length > 0"
|
||||||
|
:src="only_images(item.files)[0].name"
|
||||||
|
:owner="only_images(item.files)[0].owner"
|
||||||
|
class="card-img-top img-preview"/>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title mb-0 text-truncate">{{ item.name }}</h6>
|
||||||
|
<span class="text-black-50">{{ item.owned_quantity }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="text-muted mb-0" v-else>
|
||||||
|
No items yet.
|
||||||
|
<router-link :to="{name: 'inventory-new'}">Add your first one</router-link>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title mb-0">Needs your attention</h5>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group list-group-flush" v-if="hasPendingItems">
|
||||||
|
<router-link v-if="active_workflows.length > 0" :to="{name: 'workflows'}"
|
||||||
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||||
|
Workflows in progress
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ active_workflows.length }}</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-if="friendRequests.length > 0" :to="{name: 'friends'}"
|
||||||
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||||
|
Friend requests
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ friendRequests.length }}</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link v-if="groupInvites.length > 0" :to="{name: 'groups'}"
|
||||||
|
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
|
||||||
|
Group invites
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ groupInvites.length }}</span>
|
||||||
|
</router-link>
|
||||||
|
</ul>
|
||||||
|
<div class="card-body" v-else>
|
||||||
|
<p class="text-muted mb-0">You're all caught up.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,18 +136,92 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {mapActions, mapGetters, mapState} from 'vuex';
|
||||||
import * as BIcons from "bootstrap-icons-vue";
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
import BaseLayout from "@/components/BaseLayout.vue";
|
import BaseLayout from "@/components/BaseLayout.vue";
|
||||||
|
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||||
|
import {encodeHandleForUrl} from "@/router";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
components: {
|
components: {
|
||||||
...BIcons,
|
...BIcons,
|
||||||
BaseLayout
|
BaseLayout,
|
||||||
|
AuthenticatedImage
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
friendRequests: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['user', 'storage_locations', 'friends', 'groups', 'groupMemberships', 'groupInvites',
|
||||||
|
'active_workflows']),
|
||||||
|
...mapGetters(['inventory_items']),
|
||||||
|
// Groups hosted here plus GroupMembership-only ones - see 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]
|
||||||
|
},
|
||||||
|
recentItems() {
|
||||||
|
return [...this.inventory_items].sort((a, b) => b.id - a.id).slice(0, 8)
|
||||||
|
},
|
||||||
|
hasPendingItems() {
|
||||||
|
return this.active_workflows.length > 0 || this.friendRequests.length > 0 || this.groupInvites.length > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['fetchInventoryItems', 'fetchStorageLocations', 'fetchFriends', 'fetchGroups',
|
||||||
|
'fetchGroupMemberships', 'fetchGroupInvites', 'fetchActiveWorkflows', 'fetchFriendRequests']),
|
||||||
|
itemRoute(item) {
|
||||||
|
return {name: 'inventory-detail', params: {handle: encodeHandleForUrl(item.owner_group || item.owner), id: item.id}}
|
||||||
|
},
|
||||||
|
only_images(files) {
|
||||||
|
return files.filter(file => file.mime_type.startsWith("image/"));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.fetchInventoryItems()
|
||||||
|
await this.fetchStorageLocations()
|
||||||
|
await this.fetchFriends()
|
||||||
|
await this.fetchGroups()
|
||||||
|
await this.fetchGroupMemberships()
|
||||||
|
await this.fetchGroupInvites()
|
||||||
|
await this.fetchActiveWorkflows()
|
||||||
|
this.friendRequests = await this.fetchFriendRequests()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.stat-card {
|
||||||
|
transition: transform .15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
.stat-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: .8rem;
|
||||||
|
color: var(--bs-text-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-preview {
|
||||||
|
object-fit: cover;
|
||||||
|
height: 100px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||||
Refresh
|
Refresh
|
||||||
</a>
|
</a>
|
||||||
<router-link to="/groups/new" class="align-middle">
|
<router-link :to="{name: 'groups-new'}" class="align-middle">
|
||||||
<b-icon-plus></b-icon-plus>
|
<b-icon-plus></b-icon-plus>
|
||||||
New group
|
New group
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="group in allGroups" :key="group.key">
|
<tr v-for="group in allGroups" :key="group.key">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/groups/${encodeHandleForUrl(group.handle)}`">
|
<router-link :to="{name: 'group-detail', params: {handle: encodeHandleForUrl(group.handle)}}">
|
||||||
{{ group.handle }}
|
{{ group.handle }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,10 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
|
<td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td>
|
||||||
<td class="table-action">
|
<td class="table-action">
|
||||||
<router-link v-if="canEdit" :to="`${itemRoute(item)}/edit`">
|
<router-link v-if="canEdit" :to="itemEditRoute(item)">
|
||||||
<b-icon-pencil-square></b-icon-pencil-square>
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a v-if="canEdit" :href="`${itemRoute(item)}/delete`" @click.prevent="tryDeleteItem(item)">
|
<a v-if="canEdit" href="#" @click.prevent="tryDeleteItem(item)">
|
||||||
<b-icon-trash></b-icon-trash>
|
<b-icon-trash></b-icon-trash>
|
||||||
</a>
|
</a>
|
||||||
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)">
|
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)">
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
<button v-if="canEdit" class="btn btn-danger btn-sm"
|
<button v-if="canEdit" class="btn btn-danger btn-sm"
|
||||||
@click="tryDeleteItem(item)">Delete
|
@click="tryDeleteItem(item)">Delete
|
||||||
</button>
|
</button>
|
||||||
<router-link v-if="canEdit" :to="`${itemRoute(item)}/edit`"
|
<router-link v-if="canEdit" :to="itemEditRoute(item)"
|
||||||
class="btn btn-primary btn-sm">Edit
|
class="btn btn-primary btn-sm">Edit
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)"
|
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)"
|
||||||
|
|
@ -166,8 +166,8 @@ export default {
|
||||||
return this.owner ? decodeHandleFromUrl(this.owner) : this.user
|
return this.owner ? decodeHandleFromUrl(this.owner) : this.user
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
const path = value === this.user ? '/inventory' : `/inventory/${encodeHandleForUrl(value)}`
|
const params = value === this.user ? {} : {owner: encodeHandleForUrl(value)}
|
||||||
this.$router.replace(path)
|
this.$router.replace({name: 'inventory', params})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isGroupSelected() {
|
isGroupSelected() {
|
||||||
|
|
@ -182,9 +182,8 @@ export default {
|
||||||
return this.selectedOwner === this.user ? this.inventory_items : this.groupInventoryItems(this.selectedOwner)
|
return this.selectedOwner === this.user ? this.inventory_items : this.groupInventoryItems(this.selectedOwner)
|
||||||
},
|
},
|
||||||
addItemRoute() {
|
addItemRoute() {
|
||||||
return this.selectedOwner === this.user
|
const params = this.selectedOwner === this.user ? {} : {group: encodeHandleForUrl(this.selectedOwner)}
|
||||||
? '/inventory/new'
|
return {name: 'inventory-new', params}
|
||||||
: `/inventory/new/${encodeHandleForUrl(this.selectedOwner)}`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -203,7 +202,10 @@ export default {
|
||||||
},
|
},
|
||||||
// Derived from the item's own owner, not the current selection, so the link stays correct if the dropdown changes underneath it.
|
// Derived from the item's own owner, not the current selection, so the link stays correct if the dropdown changes underneath it.
|
||||||
itemRoute(item) {
|
itemRoute(item) {
|
||||||
return `/inventory/${encodeHandleForUrl(item.owner_group || item.owner)}/${item.id}`
|
return {name: 'inventory-detail', params: {handle: encodeHandleForUrl(item.owner_group || item.owner), id: item.id}}
|
||||||
|
},
|
||||||
|
itemEditRoute(item) {
|
||||||
|
return {name: 'inventory-edit', params: {handle: encodeHandleForUrl(item.owner_group || item.owner), id: item.id}}
|
||||||
},
|
},
|
||||||
only_images(files) {
|
only_images(files) {
|
||||||
return files.filter(file => file.mime_type.startsWith("image/"));
|
return files.filter(file => file.mime_type.startsWith("image/"));
|
||||||
|
|
@ -225,7 +227,7 @@ export default {
|
||||||
printLinkFor(item) {
|
printLinkFor(item) {
|
||||||
const userHandle = item.owner || item.owner_group
|
const userHandle = item.owner || item.owner_group
|
||||||
if (!userHandle) return null
|
if (!userHandle) return null
|
||||||
return {path: '/print', query: {kind: 'item', userHandle, id: item.id}}
|
return {name: 'print', query: {kind: 'item', userHandle, id: item.id}}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card" v-if="canEdit">
|
<div class="card" v-if="canEdit">
|
||||||
<button class="btn btn-primary" @click="$router.push(`/inventory/${handle}/${id}/edit`)">
|
<button class="btn btn-primary" @click="$router.push({name: 'inventory-edit', params: {handle, id}})">
|
||||||
<b-icon-pencil-square></b-icon-pencil-square>
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<button v-if="canPrint" class="btn btn-secondary"
|
<button v-if="canPrint" class="btn btn-secondary"
|
||||||
@click="$router.push({path: '/print', query: {kind: 'item', userHandle: decodedHandle, id: id}})">
|
@click="$router.push({name: 'print', query: {kind: 'item', userHandle: decodedHandle, id: id}})">
|
||||||
<b-icon-printer></b-icon-printer>
|
<b-icon-printer></b-icon-printer>
|
||||||
Print label
|
Print label
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,11 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Don’t have an account?
|
Don’t have an account?
|
||||||
<router-link to="/register">Sign up</router-link>
|
<router-link :to="{name: 'register'}">Sign up</router-link>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Connect with existing account?
|
Connect with existing account?
|
||||||
<router-link to="/pairing">Pairing</router-link>
|
<router-link :to="{name: 'pairing'}">Pairing</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,11 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<router-link to="/login">Login</router-link>
|
<router-link :to="{name: 'login'}">Login</router-link>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Don’t have an account?
|
Don’t have an account?
|
||||||
<router-link to="/register">Sign up</router-link>
|
<router-link :to="{name: 'register'}">Sign up</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,11 @@
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<router-link to="/login">Login</router-link>
|
<router-link :to="{name: 'login'}">Login</router-link>
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0 text-muted">
|
<p class="mb-0 text-muted">
|
||||||
Connect with existing account?
|
Connect with existing account?
|
||||||
<router-link to="/pairing">Pairing</router-link>
|
<router-link :to="{name: 'pairing'}">Pairing</router-link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ function classifyScanText(text) {
|
||||||
const [, handle, kindLetter, id] = ownerMatch;
|
const [, handle, kindLetter, id] = ownerMatch;
|
||||||
const kind = kindLetter === "s" ? "storage_location" : "item";
|
const kind = kindLetter === "s" ? "storage_location" : "item";
|
||||||
const to = kind === "storage_location"
|
const to = kind === "storage_location"
|
||||||
? `/storage-locations/${encodeHandleForUrl(handle)}/${id}`
|
? {name: 'locations-detail', params: {handle: encodeHandleForUrl(handle), id}}
|
||||||
: `/inventory/${encodeHandleForUrl(handle)}/${id}`;
|
: {name: 'inventory-detail', params: {handle: encodeHandleForUrl(handle), id}};
|
||||||
return {to, ownerQualifiedId: {handle, kind, id}};
|
return {to, ownerQualifiedId: {handle, kind, id}};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ export default {
|
||||||
// user handle - one route shape, no owner-is-me special case.
|
// user handle - one route shape, no owner-is-me special case.
|
||||||
this.fetchSearchResults({query: this.query}).then((results) => {
|
this.fetchSearchResults({query: this.query}).then((results) => {
|
||||||
this.search_results = results.map(e => (
|
this.search_results = results.map(e => (
|
||||||
{...e, route: `/inventory/${encodeHandleForUrl(e.owner)}/${e.id}`}))
|
{...e, route: {name: 'inventory-detail', params: {handle: encodeHandleForUrl(e.owner), id: e.id}}}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,10 @@
|
||||||
<span class="badge bg-secondary text-white">{{ location.visibility_policy }}</span>
|
<span class="badge bg-secondary text-white">{{ location.visibility_policy }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-action">
|
<td class="table-action">
|
||||||
<router-link :to="`${locationRoute(location)}/edit`">
|
<router-link :to="locationEditRoute(location)">
|
||||||
<b-icon-pencil-square></b-icon-pencil-square>
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
</router-link>
|
</router-link>
|
||||||
<a :href="`${locationRoute(location)}/delete`" @click.prevent="tryDeleteLocation(location)">
|
<a href="#" @click.prevent="tryDeleteLocation(location)">
|
||||||
<b-icon-trash></b-icon-trash>
|
<b-icon-trash></b-icon-trash>
|
||||||
</a>
|
</a>
|
||||||
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)">
|
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)">
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
<button class="btn btn-danger btn-sm"
|
<button class="btn btn-danger btn-sm"
|
||||||
@click="tryDeleteLocation(location)">Delete
|
@click="tryDeleteLocation(location)">Delete
|
||||||
</button>
|
</button>
|
||||||
<router-link :to="`${locationRoute(location)}/edit`"
|
<router-link :to="locationEditRoute(location)"
|
||||||
class="btn btn-primary btn-sm">Edit
|
class="btn btn-primary btn-sm">Edit
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)"
|
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)"
|
||||||
|
|
@ -129,7 +129,7 @@ import {shortenedRoute, encodeHandleForUrl, decodeHandleFromUrl} from "@/router"
|
||||||
export default {
|
export default {
|
||||||
name: "StorageLocation",
|
name: "StorageLocation",
|
||||||
props: {
|
props: {
|
||||||
// Matches /storage-location/:owner; absent means "me". See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// Matches /locations/:owner; absent means "me". See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
owner: {
|
owner: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
|
@ -163,17 +163,16 @@ export default {
|
||||||
return this.owner ? decodeHandleFromUrl(this.owner) : this.user
|
return this.owner ? decodeHandleFromUrl(this.owner) : this.user
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
const path = value === this.user ? '/storage-location' : `/storage-location/${encodeHandleForUrl(value)}`
|
const params = value === this.user ? {} : {owner: encodeHandleForUrl(value)}
|
||||||
this.$router.replace(path)
|
this.$router.replace({name: 'locations', params})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
locations() {
|
locations() {
|
||||||
return this.selectedOwner === this.user ? this.storage_locations : this.groupStorageLocations(this.selectedOwner)
|
return this.selectedOwner === this.user ? this.storage_locations : this.groupStorageLocations(this.selectedOwner)
|
||||||
},
|
},
|
||||||
addLocationRoute() {
|
addLocationRoute() {
|
||||||
return this.selectedOwner === this.user
|
const params = this.selectedOwner === this.user ? {} : {group: encodeHandleForUrl(this.selectedOwner)}
|
||||||
? '/storage-locations/new'
|
return {name: 'locations-new', params}
|
||||||
: `/storage-locations/new/${encodeHandleForUrl(this.selectedOwner)}`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -190,7 +189,10 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
locationRoute(location) {
|
locationRoute(location) {
|
||||||
return `/storage-locations/${encodeHandleForUrl(location.owner_group || location.owner)}/${location.id}`
|
return {name: 'locations-detail', params: {handle: encodeHandleForUrl(location.owner_group || location.owner), id: location.id}}
|
||||||
|
},
|
||||||
|
locationEditRoute(location) {
|
||||||
|
return {name: 'locations-edit', params: {handle: encodeHandleForUrl(location.owner_group || location.owner), id: location.id}}
|
||||||
},
|
},
|
||||||
shortIdLink(location) {
|
shortIdLink(location) {
|
||||||
if (location.owner_group) {
|
if (location.owner_group) {
|
||||||
|
|
@ -208,7 +210,7 @@ export default {
|
||||||
// printLinkFor. See docs/implementation.md#print-link-shape.
|
// printLinkFor. See docs/implementation.md#print-link-shape.
|
||||||
printLinkFor(location) {
|
printLinkFor(location) {
|
||||||
const userHandle = location.owner_group || location.owner
|
const userHandle = location.owner_group || location.owner
|
||||||
return {path: '/print', query: {kind: 'storage-location', userHandle, id: location.id}}
|
return {name: 'print', query: {kind: 'storage-location', userHandle, id: location.id}}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card" v-if="canEdit">
|
<div class="card" v-if="canEdit">
|
||||||
<button class="btn btn-primary" @click="$router.push(`/storage-locations/${handle}/${id}/edit`)">
|
<button class="btn btn-primary" @click="$router.push({name: 'locations-edit', params: {handle, id}})">
|
||||||
<b-icon-pencil-square></b-icon-pencil-square>
|
<b-icon-pencil-square></b-icon-pencil-square>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-secondary"
|
<button class="btn btn-secondary"
|
||||||
@click="$router.push({path: '/print', query: {kind: 'storage-location', userHandle: decodedHandle, id: id}})">
|
@click="$router.push({name: 'print', query: {kind: 'storage-location', userHandle: decodedHandle, id: id}})">
|
||||||
<b-icon-printer></b-icon-printer>
|
<b-icon-printer></b-icon-printer>
|
||||||
Print label
|
Print label
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export default {
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
// Matches /storage-locations/new/:group?. See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
// Matches /locations/new/:group?. See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
||||||
group: {
|
group: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item">
|
<li class="breadcrumb-item">
|
||||||
<router-link to="/workflows" class="text-decoration-none">Workflows</router-link>
|
<router-link :to="{name: 'workflows'}" class="text-decoration-none">Workflows</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{ workflowDefinition?.title || workflowInstance?.title || 'Loading...' }}</li>
|
<li class="breadcrumb-item active" aria-current="page">{{ workflowDefinition?.title || workflowInstance?.title || 'Loading...' }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import StorageOptimizationWorkflow from '@/components/workflow/workflows/Storage
|
||||||
import MaintenanceScheduleWorkflow from '@/components/workflow/workflows/MaintenanceScheduleWorkflow.vue';
|
import MaintenanceScheduleWorkflow from '@/components/workflow/workflows/MaintenanceScheduleWorkflow.vue';
|
||||||
import ExpiryCheckWorkflow from '@/components/workflow/workflows/ExpiryCheckWorkflow.vue';
|
import ExpiryCheckWorkflow from '@/components/workflow/workflows/ExpiryCheckWorkflow.vue';
|
||||||
import BackupRestoreWorkflow from '@/components/workflow/workflows/BackupRestoreWorkflow.vue';
|
import BackupRestoreWorkflow from '@/components/workflow/workflows/BackupRestoreWorkflow.vue';
|
||||||
import BulkLabelPrintWorkflow from '@/components/workflow/workflows/BulkLabelPrintWorkflow.vue';
|
//import BulkLabelPrintWorkflow from '@/components/workflow/workflows/BulkLabelPrintWorkflow.vue';
|
||||||
|
|
||||||
const workflows = [
|
const workflows = [
|
||||||
{...FotoFirstBulkImportWorkflow.meta, component: FotoFirstBulkImportWorkflow},
|
{...FotoFirstBulkImportWorkflow.meta, component: FotoFirstBulkImportWorkflow},
|
||||||
|
|
@ -16,7 +16,7 @@ const workflows = [
|
||||||
{...MaintenanceScheduleWorkflow.meta, component: MaintenanceScheduleWorkflow},
|
{...MaintenanceScheduleWorkflow.meta, component: MaintenanceScheduleWorkflow},
|
||||||
{...ExpiryCheckWorkflow.meta, component: ExpiryCheckWorkflow},
|
{...ExpiryCheckWorkflow.meta, component: ExpiryCheckWorkflow},
|
||||||
{...BackupRestoreWorkflow.meta, component: BackupRestoreWorkflow},
|
{...BackupRestoreWorkflow.meta, component: BackupRestoreWorkflow},
|
||||||
{...BulkLabelPrintWorkflow.meta, component: BulkLabelPrintWorkflow},
|
// {...BulkLabelPrintWorkflow.meta, component: BulkLabelPrintWorkflow},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getAllWorkflows() {
|
export function getAllWorkflows() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue