Skip to content

Commit ee2979d

Browse files
authored
LIMS-1632: Buttons to go to next & previous puck in dewar (#912)
* feature: retrieve sibling containers of the current * feat: add btn group component for moving between prev & next links * feat: add prev-next buttons to container page * feat: update prevNextBtnGroup to use a pathPrefix * feat: add a page-title-header component * refactor: update mx-container to use computed properies where possible * refactor: moved prev/next logic down into the component * feat: add the prev/next puck buttons into the page-header * style: update margin-bottom on page-title-header * fix: remove ShippingID from the container filtering
1 parent 970f69f commit ee2979d

File tree

3 files changed

+212
-2
lines changed

3 files changed

+212
-2
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<template>
2+
<div class="content header-row">
3+
<h1 class="nou">
4+
<slot>Title</slot>
5+
</h1>
6+
7+
<prev-next-btngroup
8+
v-show="this.prevNextTargets"
9+
next-btn-label="Next Container"
10+
prev-btn-label="Prev Container"
11+
:path-prefix="this.prevNextPathPrefix"
12+
:allTargets="this.prevNextTargets"
13+
:currentValue="this.currentValue"
14+
>
15+
</prev-next-btngroup>
16+
</div>
17+
</template>
18+
19+
<script>
20+
import PrevNextBtngroup from "./prev-next-btngroup.vue";
21+
22+
export default {
23+
name: "PageTitleHeader",
24+
components: {
25+
"prev-next-btngroup": PrevNextBtngroup,
26+
},
27+
props: {
28+
prevNextPathPrefix: {
29+
type: String,
30+
default: null
31+
},
32+
prevNextTargets: {
33+
type: Array,
34+
default: []
35+
},
36+
currentValue: {
37+
type: String,
38+
default: null
39+
},
40+
prevNextBtnProps: {
41+
type: Object,
42+
default: () => ({
43+
nextBtnLabel: "Next",
44+
prevBtnLabel: "Prev",
45+
})
46+
},
47+
48+
},
49+
};
50+
</script>
51+
52+
<style scoped>
53+
h1 {
54+
padding: 0;
55+
@apply tw-flex-grow
56+
}
57+
58+
.header-row {
59+
@apply tw-px-4;
60+
@apply tw-pl-0;
61+
@apply tw-py-2;
62+
@apply tw-mb-4;
63+
@apply tw-flex;
64+
@apply tw-flex-row;
65+
@apply tw-gap-2;
66+
@apply tw-items-center;
67+
border-bottom: 1px solid grey;
68+
}
69+
</style>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<template>
2+
<div class="btn-group">
3+
<flat-button
4+
@click="pushToPrev()"
5+
v-bind:level="prevTarget?.value ? 'primary' : 'disabled'"
6+
v-bind:hint="prevTarget?.text ?? prevTarget?.value"
7+
>
8+
{{ prevBtnLabel }}
9+
</flat-button>
10+
{{ currentIdx +1 }} of {{ allTargets.length }}
11+
12+
<flat-button
13+
@click="pushToNext()"
14+
v-bind:level="nextTarget?.value ? 'primary' : 'disabled'"
15+
v-bind:hint="nextTarget?.text ?? nextTarget?.value"
16+
>
17+
{{ nextBtnLabel }}
18+
</flat-button>
19+
</div>
20+
</template>
21+
22+
<script>
23+
import FlatButton from "./flat-button.vue";
24+
import router from "../router/router";
25+
26+
/**
27+
* A "simple" component that supplies functionality for prev & next router-links.
28+
* Button labels default to "Prev" & "Next" but alternatives can be supplied.
29+
* Keeping state minimal for future re-use as the page is fully reloaded every time.
30+
*/
31+
export default {
32+
name: "PrevNextBtnGroup",
33+
components: {
34+
"flat-button": FlatButton,
35+
},
36+
computed: {
37+
currentIdx () {
38+
return _.findIndex(this.allTargets, target => target.value === this.currentValue);
39+
},
40+
nextTarget() {
41+
return this.allTargets[this.currentIdx+1];
42+
},
43+
prevTarget(){
44+
return this.allTargets[this.currentIdx-1];
45+
}
46+
},
47+
methods: {
48+
pushToNext() {
49+
if (this.nextTarget?.value)
50+
router.push({ path: this.pathprefix + this.nextTarget.value });
51+
},
52+
pushToPrev() {
53+
if (this.prevTarget?.value)
54+
router.push({ path: this.pathprefix + this.prevTarget.value });
55+
},
56+
},
57+
props: {
58+
pathprefix: {
59+
type: String,
60+
default: "",
61+
},
62+
nextBtnLabel: {
63+
type: String,
64+
default: "Next",
65+
},
66+
prevBtnLabel: {
67+
type: String,
68+
default: "Prev",
69+
},
70+
71+
allTargets: {
72+
type: Array, // { value: string text: string }
73+
default: [],
74+
},
75+
76+
currentValue: {
77+
type: String,
78+
default: null,
79+
},
80+
},
81+
};
82+
</script>
83+
84+
<style scoped>
85+
.btn-group {
86+
@apply tw-px-4;
87+
@apply tw-py-2;
88+
@apply tw-flex;
89+
@apply tw-flex-row;
90+
@apply tw-gap-2;
91+
@apply tw-items-center;
92+
@apply tw-border;
93+
@apply tw-rounded;
94+
}
95+
</style>

client/src/js/modules/types/mx/shipment/views/mx-container-view.vue

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
<template>
22
<div class="content">
3-
<h1 data-testid="container-header">Container {{container.NAME}}</h1>
3+
<!-- <h1 data-testid="container-header">Container {{container.NAME}}</h1> -->
4+
<page-title-header
5+
prevNextPathPrefix="/containers/cid/"
6+
:prevNextTargets="this.prevNextTargetLinks"
7+
:currentValue="this.containerId"
8+
>
9+
Container {{container.NAME }}
10+
</page-title-header>
411

512
<p class="help">
613
This page shows the contents of the selected container. Samples can be added and edited by clicking the pencil icon, and removed by clicking the x
@@ -249,6 +256,8 @@ import Shipments from 'collections/shipments'
249256
import Containers from 'collections/containers'
250257
import Dewars from 'collections/dewars'
251258
259+
import PrevNextBtngroup from 'app/components/prev-next-btngroup.vue'
260+
import PageTitleHeader from 'app/components/page-title-header.vue'
252261
import BaseInputSelect from 'app/components/base-input-select.vue'
253262
import BaseInputText from 'app/components/base-input-text.vue'
254263
import BaseInputTextArea from 'app/components/base-input-textarea.vue'
@@ -263,6 +272,8 @@ import ValidContainerGraphic from 'modules/types/mx/samples/valid-container-grap
263272
export default {
264273
name: 'MxContainerView',
265274
components: {
275+
'prev-next-btngroup': PrevNextBtngroup,
276+
'page-title-header': PageTitleHeader,
266277
'base-input-text': BaseInputText,
267278
'base-input-textarea': BaseInputTextArea,
268279
'base-input-select': BaseInputSelect,
@@ -273,7 +284,7 @@ export default {
273284
'single-sample-plate': SingleSample,
274285
'mx-puck-samples-table': MxPuckSamplesTable,
275286
'validation-observer': ValidationObserver,
276-
'validation-provider': ValidationProvider
287+
'validation-provider': ValidationProvider,
277288
},
278289
mixins: [ContainerMixin],
279290
props: {
@@ -286,6 +297,7 @@ export default {
286297
return {
287298
container: {},
288299
containerId: 0,
300+
siblingContainers: {}, // All containers using this Dewar and shippingID
289301
samplesCollection: null,
290302
291303
containerHistory: [],
@@ -336,6 +348,12 @@ export default {
336348
containersSamplesGroupData() {
337349
return this.$store.getters['samples/getContainerSamplesGroupData']
338350
},
351+
prevNextTargetLinks() {
352+
return _.chain(this.siblingContainers)
353+
.map(sib => ({ value: sib.CONTAINERID, text: sib.NAME }))
354+
.sortBy((c) => c.text)
355+
.value();
356+
},
339357
},
340358
created: function() {
341359
// Get samples for this container id
@@ -354,6 +372,10 @@ export default {
354372
this.getImagingCollections()
355373
this.getImagingScheduleCollections()
356374
this.getImagingScreensCollections()
375+
376+
},
377+
beforeMount: function(){
378+
this.fetchSiblingContainers();
357379
},
358380
methods: {
359381
loadContainerData() {
@@ -517,6 +539,30 @@ export default {
517539
// TODO: Toggle loading state off
518540
}
519541
},
542+
543+
/**
544+
* Fetch any other containers sharing the same Dewar & shipment, sorted by NAME.
545+
* Also tracks the index of the current Container
546+
*/
547+
async fetchSiblingContainers() {
548+
var result;
549+
550+
if (this.containersCollection?.length>0) {
551+
// ! if ContainersCollection exists then filter it instead rather than re-fetching.
552+
// !! WARNING - THis assumes that containersCollection has ALL containers of the dewar
553+
result = _.filter(this.containersCollection, (c) =>
554+
c.DEWARID === this.container.DEWARID
555+
);
556+
557+
} else {
558+
result = new Containers();
559+
result.dewarID = this.container.DEWARID;
560+
await result.fetch();
561+
}
562+
563+
this.siblingContainers = result.toJSON();
564+
},
565+
520566
async fetchContainers() {
521567
this.containersCollection = new Containers(null, { state: { pageSize: 9999 } })
522568
this.containersCollection.queryParams.did = this.containersSamplesGroupData.dewarId

0 commit comments

Comments
 (0)