diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index e374f59f8..167735794 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -2531,8 +2531,12 @@ function _get_all_containers() 'MANUAL' => 'queuedmanualsubsamples', 'AUTO' => 'queuedautosubsamples', ); $dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC'; - if (array_key_exists($this->arg('sort_by'), $cols)) + if (array_key_exists($this->arg('sort_by'), $cols)) { $order = $cols[$this->arg('sort_by')] . ' ' . $dir; + if ($this->arg('sort_by') == 'NAME') { + $order .= ', c.containerid ' . $dir; + } + } } // $this->db->set_debug(True); $rows = $this->db->paginate("SELECT $select diff --git a/client/src/css/partials/_utility.scss b/client/src/css/partials/_utility.scss index 546580d85..5ed296780 100644 --- a/client/src/css/partials/_utility.scss +++ b/client/src/css/partials/_utility.scss @@ -316,3 +316,81 @@ ul.ui-autocomplete { .toggle-input:checked::before { transform: translateX(18px); } + +.flat-button { + text-align: center; + vertical-align: middle; + cursor: pointer; + box-sizing: content-box; + padding: 0.375rem 0.75rem; + border-radius: 0.25rem; + &:disabled, &.disabled { + opacity: 0.5; + cursor: auto; + } + &.primary { + @apply tw-bg-blue-500 tw-text-white; + &:hover, &:focus { + @apply tw-bg-blue-700; + } + } + &.secondary { + @apply tw-bg-gray-400 tw-text-black; + &:hover, &:focus { + @apply tw-bg-gray-600 tw-text-white; + } + } + &.success { + @apply tw-bg-green-500 tw-text-white; + &:hover, &:focus { + @apply tw-bg-green-700; + } + } + &.info { + /* Cyan would be usual for info, but Tailwind has no such thing */ + @apply tw-bg-pink-500 tw-text-black; + &:hover, &:focus { + @apply tw-bg-pink-700; + } + } + &.warning { + @apply tw-bg-yellow-300 tw-text-black; + &:hover, &:focus { + @apply tw-bg-yellow-500; + } + } + &.danger { + @apply tw-bg-red-500 tw-text-white; + &:hover, &:focus { + @apply tw-bg-red-700; + } + } +} + +.btn-group { + @apply tw-px-4; + @apply tw-py-2; + @apply tw-flex; + @apply tw-flex-row; + @apply tw-gap-2; + @apply tw-items-center; + @apply tw-border; + @apply tw-rounded; +} + +.header-row { + @apply tw-px-4; + @apply tw-pl-0; + @apply tw-py-2; + @apply tw-mb-4; + @apply tw-flex; + @apply tw-flex-row; + @apply tw-gap-2; + @apply tw-items-center; + border-bottom: 1px solid grey; + + h1 { + padding: 0 !important; + @apply tw-flex-grow; + } +} diff --git a/client/src/js/app/components/flat-button.vue b/client/src/js/app/components/flat-button.vue index 7ef1dd751..1fad3072d 100644 --- a/client/src/js/app/components/flat-button.vue +++ b/client/src/js/app/components/flat-button.vue @@ -25,55 +25,3 @@ export default { }, } - - diff --git a/client/src/js/app/components/page-title-header.vue b/client/src/js/app/components/page-title-header.vue index ae06e4435..4b29fb752 100644 --- a/client/src/js/app/components/page-title-header.vue +++ b/client/src/js/app/components/page-title-header.vue @@ -48,22 +48,3 @@ export default { }, }; - - \ No newline at end of file diff --git a/client/src/js/app/components/prev-next-btngroup.vue b/client/src/js/app/components/prev-next-btngroup.vue index c5ca3ddba..6d2928b70 100644 --- a/client/src/js/app/components/prev-next-btngroup.vue +++ b/client/src/js/app/components/prev-next-btngroup.vue @@ -80,16 +80,3 @@ export default { }, }; - - \ No newline at end of file diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index 55578f6be..64fa96554 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -7,6 +7,7 @@ define(['marionette', 'collections/samples', 'collections/subsamples', 'collections/ligands', + 'collections/containers', 'modules/shipment/collections/containerhistory', @@ -57,6 +58,7 @@ define(['marionette', Samples, Subsamples, Ligands, + Containers, ContainerHistory, @@ -273,6 +275,10 @@ define(['marionette', schemaspan: '.schemaspan', class: 'select[name=class]', heatmap: '.heatmap-canvas', + + currentIdx: '.currentIdx', + prev: '.prev', + next: '.next', }, events: { @@ -308,6 +314,8 @@ define(['marionette', 'change @ui.class': 'setAutoStatus', 'change @ui.schema': 'selectSchema', + 'click @ui.prev': 'goToPrevContainer', + 'click @ui.next': 'goToNextContainer', }, modelEvents: { @@ -686,6 +694,8 @@ define(['marionette', initialize: function(options) { this.isPlaying = false this.playthread = null + this.nextContainer = null; + this.prevContainer = null; this.samples = new Samples(null, { state: {pageSize: 9999} }) this.samples.queryParams.cid = options.model.get('CONTAINERID') @@ -746,6 +756,11 @@ define(['marionette', this.touchstartX = 0; this.touchstartY = 0; + this.siblingContainers = new Containers(null, { state: { pageSize: 9999 }}) + this.siblingContainers.setSorting('NAME') + this.siblingContainers.dewarID = this.model.get('DEWARID') + this.siblingContainers.fetch().done(this.updateSiblingContainers.bind(this)) + Backbone.Validation.bind(this) }, @@ -753,6 +768,49 @@ define(['marionette', this.ui.pipeline.html(''+this.processing_pipelines.opts()) }, + updateSiblingContainers: function() { + let foundCurrent = false; + this.siblingContainers.each((container, index) => { + let idx = index+1 + if (foundCurrent && !this.nextContainer) { + this.nextContainer = container; + } + if (container.get('CONTAINERID') === this.model.get('CONTAINERID')) { + foundCurrent = true; + this.ui.currentIdx.html(idx + " of " + this.siblingContainers.length) + if (idx === this.siblingContainers.length) { + this.ui.next.removeClass('primary').addClass('disabled') + } + if (idx === 1) { + this.ui.prev.removeClass('primary').addClass('disabled') + } + } + if (!foundCurrent) { + this.prevContainer = container; + } + }) + if (this.prevContainer) { + this.ui.prev.attr('title', this.prevContainer.get('NAME')) + } + if (this.nextContainer) { + this.ui.next.attr('title', this.nextContainer.get('NAME')) + } + }, + + goToPrevContainer: function() { + if (this.prevContainer) { + const cid = this.prevContainer.get('CONTAINERID') + app.trigger('container:show', cid) + } + }, + + goToNextContainer: function() { + if (this.nextContainer) { + const cid = this.nextContainer.get('CONTAINERID') + app.trigger('container:show', cid) + } + }, + updateSchemas: function() { if (this.autoscoreschemas.length === 1) { this.ui.schemaspan.html(this.autoscoreschemas.at(0).get('SCHEMANAME')) diff --git a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue index 8abb49d11..f9b521ea0 100644 --- a/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue +++ b/client/src/js/modules/types/mx/shipment/views/mx-container-view.vue @@ -375,11 +375,9 @@ export default { sampleLocation: 0, containers: [], - containersCollection: null, shipments: [], shipmentsCollection: null, dewars: [], - dewarsCollection: null, selectedDewarId: null, selectedShipmentId: null, shippingSafetyLevel: null, @@ -393,7 +391,6 @@ export default { prevNextTargetLinks() { return _.chain(this.siblingContainers) .map(sib => ({ value: sib.CONTAINERID, text: sib.NAME })) - .sortBy((c) => c.text) .value(); }, }, @@ -591,44 +588,12 @@ export default { * Also tracks the index of the current Container */ async fetchSiblingContainers() { - var result; - - if (this.containersCollection?.length>0) { - // ! if ContainersCollection exists then filter it instead rather than re-fetching. - // !! WARNING - THis assumes that containersCollection has ALL containers of the dewar - result = _.filter(this.containersCollection, (c) => - c.DEWARID === this.container.DEWARID - ); - - } else { - result = new Containers(); - result.dewarID = this.container.DEWARID; - await result.fetch(); - } - + let result = new Containers(null, { state: { pageSize: 9999 } }); + result.setSorting('NAME'); + result.dewarID = this.container.DEWARID; + await result.fetch(); this.siblingContainers = result.toJSON(); }, - - async fetchContainers() { - this.containersCollection = new Containers(null, { state: { pageSize: 9999 } }) - this.containersCollection.queryParams.did = this.containersSamplesGroupData.dewarId - - const result = await this.$store.dispatch('getCollection', this.containersCollection) - this.containers = result.toJSON().map(container => ({ - value: container.CONTAINERID, - text: container.NAME - })) - }, - async fetchDewars() { - this.dewarsCollection = new Dewars(null, { state: { pageSize: 9999 } }) - this.dewarsCollection.queryParams.sid = this.containersSamplesGroupData.shipmentId - - const result = await this.$store.dispatch('getCollection', this.dewarsCollection) - this.dewars = result.toJSON().map(dewar => ({ - value: dewar.DEWARID, - text: dewar.CODE - })) - }, async fetchShipments() { this.shipmentsCollection = new Shipments(null, { state: { pageSize: 9999 } }) @@ -638,15 +603,6 @@ export default { text: shipment.SHIPPINGNAME })) }, - async updateContainerSampleGroupsData(newData) { - if (newData.shipmentId !== null) { - await this.fetchDewars() - } - - if (newData.dewarId !== null) { - await this.fetchContainers() - } - }, async onUpdateSamples() { this.disableUpdateSamples = true const containerId = this.containerId diff --git a/client/src/js/templates/shipment/containerplate.html b/client/src/js/templates/shipment/containerplate.html index 1980adebc..3ededaee2 100644 --- a/client/src/js/templates/shipment/containerplate.html +++ b/client/src/js/templates/shipment/containerplate.html @@ -1,4 +1,11 @@ -

Container: <%-NAME%>

+
+

Container <%-NAME%>

+
+ + 0 of 0 + +
+

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

diff --git a/client/src/js/templates/shipment/containerplateimage.html b/client/src/js/templates/shipment/containerplateimage.html index dae3276e6..e11a0a3f1 100644 --- a/client/src/js/templates/shipment/containerplateimage.html +++ b/client/src/js/templates/shipment/containerplateimage.html @@ -1,4 +1,11 @@ -

Container: <%-NAME%>

+
+

Container <%-NAME%>

+
+ + 0 of 0 + +
+

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