From 33c0f1c76cb3ff1c377228ec23d9130aada77400 Mon Sep 17 00:00:00 2001 From: Luca Haid Date: Thu, 14 Aug 2025 10:41:31 +0200 Subject: [PATCH] added new logout button and fixed pdf viewing --- .../WarehouseShippingNoteController.php | 11 +- .../WarehouseShippingNote.js | 57 ++- .../vue/tt-components/css/tt-file-gallery.css | 24 ++ .../vue/tt-components/tt-file-gallery.js | 328 +++++++++++------- 4 files changed, 291 insertions(+), 129 deletions(-) diff --git a/application/WarehouseShippingNote/WarehouseShippingNoteController.php b/application/WarehouseShippingNote/WarehouseShippingNoteController.php index b11dc7c56..9cedd2d6d 100644 --- a/application/WarehouseShippingNote/WarehouseShippingNoteController.php +++ b/application/WarehouseShippingNote/WarehouseShippingNoteController.php @@ -706,7 +706,7 @@ class WarehouseShippingNoteController extends TTCrud { $endTimestamp = $endDate->setTime(23, 59, 59)->getTimestamp(); $filteredEvents = array_filter($allEvents, function ($event) use ($startTimestamp, $endTimestamp, $calendarId) { - if (!isset($event['cstart']) && !isset($event['category']) || (intval($event['calendar_id']['calendar_id']) !== $calendarId)) { + if (!isset($event['cstart']) && !isset($event['category']) || (intval($event['calendar_id']['calendar_id']) != $calendarId)) { return false; } $eventStartTimestamp = strtotime($event['cstart']['cstart'] ?? $event['cstart']); @@ -739,6 +739,15 @@ class WarehouseShippingNoteController extends TTCrud { self::returnJson($finalResponse); } + + protected function warehouseLogoutAction() { + if (isset($_SESSION[MFAPPNAME . '_warehouse_login_override'])) { + unset($_SESSION[MFAPPNAME . '_warehouse_login_override']); + self::returnJson(['success' => true, 'message' => 'Logout erfolgreich']); + } else { + self::returnJson(['success' => false, 'message' => 'Kein aktiver Login gefunden']); + } + } protected function createNewLogAction() { $postData = json_decode(file_get_contents('php://input'), true); diff --git a/public/js/pages/WarehouseShippingNote/WarehouseShippingNote.js b/public/js/pages/WarehouseShippingNote/WarehouseShippingNote.js index 3a4df25d7..93196dbcd 100644 --- a/public/js/pages/WarehouseShippingNote/WarehouseShippingNote.js +++ b/public/js/pages/WarehouseShippingNote/WarehouseShippingNote.js @@ -405,6 +405,13 @@ Vue.component('warehouse-shipping-note', { //language=Vue template: ` + + { window.location.reload(); } } +}); + + +document.addEventListener('DOMContentLoaded', () => { + if (!window.matchMedia('(display-mode: standalone)').matches) return; + + const logoutButton = document.createElement('button'); + logoutButton.innerHTML = ' Logout'; + + Object.assign(logoutButton.style, { + position: 'fixed', + top: '10px', + left: '50%', + transform: 'translateX(-50%)', + zIndex: '10000', + padding: '8px 16px', + backgroundColor: '#f44336', + color: 'white', + border: 'none', + borderRadius: '4px', + cursor: 'pointer', + fontSize: '14px', + boxShadow: '0 2px 5px rgba(0,0,0,0.2)' + }); + + const handleLogout = async () => { + logoutButton.disabled = true; + try { + const { data } = await axios.get('/WarehouseShippingNote/warehouseLogoutAction'); + const type = data.success ? 'success' : 'error'; + const message = data.message || (data.success ? 'Erfolgreich ausgeloggt' : 'Logout fehlgeschlagen'); + window.notify(type, message); + } catch (error) { + console.error('Logout request failed:', error); + window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.'); + } finally { + setTimeout(() => window.location.reload(), 1000); + } + }; + + logoutButton.addEventListener('click', handleLogout); + document.body.appendChild(logoutButton); }); \ No newline at end of file diff --git a/public/plugins/vue/tt-components/css/tt-file-gallery.css b/public/plugins/vue/tt-components/css/tt-file-gallery.css index 5b82055a7..f33043e53 100644 --- a/public/plugins/vue/tt-components/css/tt-file-gallery.css +++ b/public/plugins/vue/tt-components/css/tt-file-gallery.css @@ -208,4 +208,28 @@ .tt-fullscreen-nav-btn.right { right: 15px; +} + +/* Center the loader on the screen */ +.tt-fullscreen-loader { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 10; +} + +/* Apply the animation to the SVG inside the loader */ +.tt-fullscreen-loader svg { + animation: svg-spinner 1.2s linear infinite; +} + +/* Define the keyframes for the spinning animation */ +@keyframes svg-spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } \ No newline at end of file diff --git a/public/plugins/vue/tt-components/tt-file-gallery.js b/public/plugins/vue/tt-components/tt-file-gallery.js index 7b5c8941b..b5f5c82fb 100644 --- a/public/plugins/vue/tt-components/tt-file-gallery.js +++ b/public/plugins/vue/tt-components/tt-file-gallery.js @@ -1,102 +1,89 @@ -// tt-file-gallery.js -Vue.component('tt-file-gallery', { +Vue.component('tt-fullscreen-viewer', { props: { - files: { type: Array, default: () => [] }, - editMode: { type: Boolean, default: false }, - deleteMode: { type: Boolean, default: false }, - selectable: { type: Boolean, default: false }, - }, - data() { - return { - fullscreenItem: null, - currentImageIndex: 0, - editingFile: null, - selectedFiles: [], - zoom: 1, - pan: { x: 0, y: 0 }, - isPanning: false, - panStart: { x: 0, y: 0 }, - lastPinchDist: 0, - } + item: {type: Object, required: true}, + url: {type: String, default: null}, + items: {type: Array, default: () => []}, + initialIndex: {type: Number, default: -1}, }, + data: () => ({ + currentItem: null, + currentImageIndex: -1, + zoom: 1, + pan: {x: 0, y: 0}, + isPanning: false, + panStart: {x: 0, y: 0}, + lastPinchDist: 0, + // NEW: State to track if the content is loading + isLoading: true, + }), computed: { - imageFiles() { - return this.files.filter(this.isImage); + contentSrc() { + if (this.url) { + return this.url; + } + if (this.currentItem?.fileId) { + return `/File/show?id=${this.currentItem.fileId}`; + } + return null; }, isViewingImage() { - return this.fullscreenItem && this.isImage(this.fullscreenItem); + return this.currentItem && this.isImage(this.currentItem); }, imageTransformStyle() { - const { x, y } = this.pan; return { - transform: `translate(${x}px, ${y}px) scale(${this.zoom})`, + transform: `translate(${this.pan.x}px, ${this.pan.y}px) scale(${this.zoom})`, cursor: this.isPanning ? 'grabbing' : 'grab', transition: this.isPanning ? 'none' : 'transform 0.2s', }; }, - fullscreenDownloadUrl() { - if (!this.fullscreenItem) return '#'; - return `/File/download?id=${this.fullscreenItem.fileId}`; + downloadUrl() { + return this.currentItem?.fileId ? `/File/download?id=${this.currentItem.fileId}` : '#'; } }, methods: { - isImage: file => file.mimetype && file.mimetype.startsWith('image/'), - isPdf: file => file.mimetype === 'application/pdf', - getFileIcon(file) { - const extension = file.fileName?.split('.').pop().toLowerCase(); - switch (extension) { - case 'doc': case 'docx': return 'fas fa-file-word text-primary'; - case 'xls': case 'xlsx': return 'fas fa-file-excel text-success'; - case 'zip': case 'rar': return 'fas fa-file-archive text-warning'; - default: return 'fas fa-file text-secondary'; - } + isImage: file => file?.mimetype?.startsWith('image/'), + isPdf: file => file?.mimetype === 'application/pdf', + onContentLoad() { + this.isLoading = false; }, - toggleSelection(fileId) { - if (!this.selectable) return; - const index = this.selectedFiles.indexOf(fileId); - if (index > -1) { - this.selectedFiles.splice(index, 1); - } else { - this.selectedFiles.push(fileId); - } - this.$emit('selection-changed', this.selectedFiles); + closeViewer() { + this.$emit('close'); }, - openViewer(file) { - if(this.editingFile) return; - this.fullscreenItem = file; - if (this.isImage(file)) { - this.currentImageIndex = this.imageFiles.findIndex(img => img.id === file.id); - } - this.resetZoomAndPan(); - this.$nextTick(() => { this.$refs.viewer?.focus(); }); - }, - closeViewer() { this.fullscreenItem = null; }, navigateImage(direction) { const newIndex = this.currentImageIndex + direction; - if (newIndex >= 0 && newIndex < this.imageFiles.length) { + if (newIndex >= 0 && newIndex < this.items.length) { + // NEW: Reset loading state before loading the new image + this.isLoading = true; this.currentImageIndex = newIndex; - this.fullscreenItem = this.imageFiles[newIndex]; + this.currentItem = this.items[newIndex]; this.resetZoomAndPan(); } }, - handleKeyDown(event) { - event.stopPropagation(); - if (!this.fullscreenItem) return; - switch (event.key) { - case 'Escape': this.closeViewer(); break; - case 'ArrowLeft': this.isViewingImage && this.navigateImage(-1); break; - case 'ArrowRight': this.isViewingImage && this.navigateImage(1); break; + handleKeyDown(e) { + e.stopPropagation(); + if (!this.currentItem) return; + switch (e.key) { + case 'Escape': + this.closeViewer(); + break; + case 'ArrowLeft': + this.isViewingImage && this.navigateImage(-1); + break; + case 'ArrowRight': + this.isViewingImage && this.navigateImage(1); + break; } }, resetZoomAndPan() { - this.zoom = 1; this.pan = { x: 0, y: 0 }; this.isPanning = false; + this.zoom = 1; + this.pan = {x: 0, y: 0}; + this.isPanning = false; }, handleWheel(e) { if (!this.isViewingImage) return; e.preventDefault(); - const scaleFactor = 0.2; - const newZoom = this.zoom - (e.deltaY > 0 ? scaleFactor : -scaleFactor); - this.zoom = Math.max(1, Math.min(newZoom, 5)); + const scaleFactor = e.deltaY > 0 ? -0.2 : 0.2; + this.zoom = Math.max(1, Math.min(this.zoom + scaleFactor, 5)); }, onPanStart(e) { if (this.zoom <= 1) return; @@ -110,16 +97,134 @@ Vue.component('tt-file-gallery', { this.pan.x = e.clientX - this.panStart.x; this.pan.y = e.clientY - this.panStart.y; }, - onPanEnd() { this.isPanning = false; }, - onTouchStart(e) { /* ... touch logic ... */ }, - onTouchMove(e) { /* ... touch logic ... */ }, - onTouchEnd(e) { /* ... touch logic ... */ }, + onPanEnd() { + this.isPanning = false; + }, + onTouchStart(e) {}, + onTouchMove(e) {}, + onTouchEnd(e) {}, + }, + created() { + this.currentItem = this.item; + this.currentImageIndex = this.initialIndex; + }, + mounted() { + document.body.style.overflow = 'hidden'; + this.$nextTick(() => this.$refs.viewer?.focus()); + }, + beforeDestroy() { + document.body.style.overflow = ''; + }, + //language=Vue + template: ` +
+
+ + + + +
+ +
+
+ + + + +
+ + + + + +
+ + +
`, +}); + +// You can add this style to your main CSS file or a