diff --git a/api/src/Page/Imaging.php b/api/src/Page/Imaging.php index 4e2d411ca..147f6dc9a 100644 --- a/api/src/Page/Imaging.php +++ b/api/src/Page/Imaging.php @@ -611,12 +611,25 @@ function _get_inspection_images() array_push($args, $this->arg('sid')); } - $images = $this->db->pq("SELECT i.containerid, si.containerinspectionid, ROUND(TIMESTAMPDIFF('HOUR', min(i2.bltimestamp), i.bltimestamp)/24,1) as delta, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI') as bltimestamp, sc.name as scorename, sc.score, sc.colour as scorecolour, max.maxscore, scorecolours.colour as maxscorecolour + $order = 'i.bltimestamp'; + + if ($this->has_arg('sort_by')) { + $cols = array( + 'BLTIMESTAMP' => 'i.bltimestamp', + 'LOCATION' => 'b.location+0' + ); + $dir = $this->has_arg('order') ? ($this->arg('order') == 'asc' ? 'ASC' : 'DESC') : 'ASC'; + if (array_key_exists($this->arg('sort_by'), $cols)) + $order = $cols[$this->arg('sort_by')] . ' ' . $dir; + } + + $images = $this->db->pq("SELECT i.containerid, si.containerinspectionid, ROUND(TIMESTAMPDIFF('HOUR', min(i2.bltimestamp), i.bltimestamp)/24,1) as delta, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI') as bltimestamp, sc.name as scorename, sc.score, sc.colour as scorecolour, max.maxscore, scorecolours.colour as maxscorecolour, b.location FROM blsampleimage si LEFT OUTER JOIN blsampleimagescore sc ON sc.blsampleimagescoreid = si.blsampleimagescoreid INNER JOIN containerinspection i ON i.containerinspectionid = si.containerinspectionid LEFT OUTER JOIN containerinspection i2 ON i.containerid = i2.containerid + INNER JOIN blsample b ON b.blsampleid = si.blsampleid INNER JOIN container c ON c.containerid = i.containerid INNER JOIN dewar d ON d.dewarid = c.dewarid INNER JOIN shipping s ON s.shippingid = d.shippingid @@ -631,7 +644,7 @@ function _get_inspection_images() WHERE p.proposalid = :1 $where GROUP BY i.containerid, si.containerinspectionid, i.bltimestamp, si.blsampleimageid, si.blsampleid, si.micronsperpixelx, si.micronsperpixely, si.blsampleimagescoreid, si.comments, TO_CHAR(si.bltimestamp, 'DD-MM-YYYY HH24:MI'), sc.name, sc.score, sc.colour - ORDER BY i.bltimestamp", $args); + ORDER BY $order", $args); if ($this->has_arg('imid')) { diff --git a/client/src/css/partials/_base.scss b/client/src/css/partials/_base.scss index 3167726bd..3fc24eaa0 100644 --- a/client/src/css/partials/_base.scss +++ b/client/src/css/partials/_base.scss @@ -21,6 +21,13 @@ body { } } +body.dialog-open { + overflow: hidden; + position: fixed; + width: 100%; + height: 100%; +} + a { color: $link-color; diff --git a/client/src/css/partials/_utility.scss b/client/src/css/partials/_utility.scss index c1a9c18d3..eb7e802a0 100644 --- a/client/src/css/partials/_utility.scss +++ b/client/src/css/partials/_utility.scss @@ -72,6 +72,10 @@ flex: 0 0 auto } +.wrap { + flex-wrap: wrap; +} + /* TODO: If we can ever get rid of this class, we can also remove the work-around in plotly support which needs to circumvent it */ .active { diff --git a/client/src/js/modules/imaging/views/inspectionimages.js b/client/src/js/modules/imaging/views/inspectionimages.js new file mode 100644 index 000000000..62ff8217c --- /dev/null +++ b/client/src/js/modules/imaging/views/inspectionimages.js @@ -0,0 +1,94 @@ +define(['views/dialog', + 'utils/xhrimage', + 'modules/imaging/collections/inspectionimages', + ], function(DialogView, + XHRImage, + InspectionImages) { + + return DialogView.extend({ + template: '
', + + ui: { + images: '.images', + }, + + initialize: function(options) { + // disable scrolling on the main page + $('body').addClass('dialog-open') + this.inspectionimages = new InspectionImages() + this.inspectionimages.queryParams.iid = options.CONTAINERINSPECTIONID + this.inspectionimages.queryParams.sort_by = 'LOCATION' + }, + + inspectionLoaded: function() { + var self = this + const loadDelay = 200 // milliseconds + this.imageTimers = new Map() // Track timers per image + + this.observer = new IntersectionObserver(function(entries, observer) { + entries.forEach(entry => { + const img = $(entry.target) + + if (entry.isIntersecting) { + // Start a delayed load + const timerId = setTimeout(() => { + let im = new XHRImage() + im.load(img.data('src'), function(loadedImage) { + img.attr('src', loadedImage.src) + }) + observer.unobserve(entry.target) // Unobserve after loading + self.imageTimers.delete(entry.target) + }, loadDelay) + + // Store timer so we can cancel if needed + self.imageTimers.set(entry.target, timerId) + } else { + // No longer visible: cancel pending load if exists + const timerId = self.imageTimers.get(entry.target) + if (timerId) { + clearTimeout(timerId) + self.imageTimers.delete(entry.target) + } + } + }) + }) + + this.inspectionimages.each(function(inspectionimage) { + let locationEl = $('
  • ').text(inspectionimage.get('LOCATION')) + + let imageEl = $('').css('min-height', '400px') + .data('src', app.apiurl + '/imaging/inspection/image/' + inspectionimage.get('BLSAMPLEIMAGEID') + '?f=1') + + locationEl.append(imageEl) + self.ui.images.append(locationEl) + self.observer.observe(imageEl[0]) + }) + }, + + onRender: function() { + this.inspectionimages.fetch().done(this.inspectionLoaded.bind(this)) + }, + + closeDialog: function(e) { + DialogView.prototype.closeDialog.apply(this, arguments) + this.destroy() + }, + + onDestroy: function() { + // re-enable scrolling on the main page + $('body').removeClass('dialog-open') + if (this.observer) { + this.observer.disconnect() + } + + // Cancel all pending image timers + if (this.imageTimers) { + for (const timer of this.imageTimers.values()) { + clearTimeout(timer) + } + this.imageTimers.clear() + } + }, + }) + +}) diff --git a/client/src/js/modules/shipment/views/containerplate.js b/client/src/js/modules/shipment/views/containerplate.js index 5a5de4ec5..38e42f612 100644 --- a/client/src/js/modules/shipment/views/containerplate.js +++ b/client/src/js/modules/shipment/views/containerplate.js @@ -20,6 +20,7 @@ define(['marionette', 'modules/imaging/views/imagehistory', 'modules/imaging/views/addinspection', + 'modules/imaging/views/inspectionimages', 'modules/imaging/views/actualschedule', 'modules/imaging/views/subtosample', @@ -61,7 +62,7 @@ define(['marionette', SingleSample, ContainerInspections, InspectionImage, InspectionImages, ImageViewer, ImageHistoryView, - AddInspectionView, ActualScheduleView, SubToSampleView, + AddInspectionView, InspectionImagesView, ActualScheduleView, SubToSampleView, ScreenComponentGroups, ScreenComponents, @@ -282,6 +283,7 @@ define(['marionette', 'click a.add_inspection': 'showAddInspection', 'click a.view_sched': 'showViewSchedule', 'click @ui.play': 'playInspection', + 'click a.inspection_images': 'showInspectionImages', 'dragover @ui.drop': 'dragHover', 'dragleave @ui.drop': 'dragHover', @@ -452,6 +454,20 @@ define(['marionette', } else this.ui.ins.val(m.get('CONTAINERINSPECTIONID')).trigger('change') }, + showInspectionImages: function(e) { + e.preventDefault() + this.inspectionImagesView = new InspectionImagesView({ dialog: true, CONTAINERINSPECTIONID: this.ui.ins.val() }) + app.dialog.show(new DialogView({ + title: 'Inspection Images', + view: this.inspectionImagesView, + autoSize: true, + destroyOnClose: true, + dOptions: { + height: $(window).height() * 0.8, + }, + })) + }, + showViewSchedule: function(e) { e.preventDefault() diff --git a/client/src/js/templates/imaging/inspectionadd.html b/client/src/js/templates/imaging/inspectionadd.html index c4e42b946..7b043436c 100644 --- a/client/src/js/templates/imaging/inspectionadd.html +++ b/client/src/js/templates/imaging/inspectionadd.html @@ -9,7 +9,7 @@

    Add New Container Inspection

  • -