diff --git a/Layout/default/VueViews/Vue.php b/Layout/default/VueViews/Vue.php index 35a73afdd..5869d53b1 100644 --- a/Layout/default/VueViews/Vue.php +++ b/Layout/default/VueViews/Vue.php @@ -21,6 +21,7 @@ $additionalCSS = [ ...$additionalCSS, 'plugins/daterangepicker/daterangepicker.css', 'plugins/vue/tt-components/css/tt-table.css', + 'plugins/vue/tt-components/css/tt-tooltip.css', 'plugins/vue/tt-components/css/tt-loader.css', 'plugins/vue/tt-components/css/tt-position-manager.css', ]; diff --git a/application/WarehouseShippingNote/WarehouseShippingNoteController.php b/application/WarehouseShippingNote/WarehouseShippingNoteController.php index fe0e2398f..7936fc276 100644 --- a/application/WarehouseShippingNote/WarehouseShippingNoteController.php +++ b/application/WarehouseShippingNote/WarehouseShippingNoteController.php @@ -16,7 +16,7 @@ class WarehouseShippingNoteController extends TTCrud { ['value' => 'cancelled', 'text' => 'Storniert', 'icon' => 'fas fa-ban text-danger'], ['value' => 'on_hold', 'text' => 'In Wartestellung', 'icon' => 'fas fa-pause text-warning'], ]]], - ['key' => 'type', 'text' => 'Typ', 'required' => true], + ['key' => 'type', 'text' => 'Typ', 'required' => false], ['key' => 'deliveryAddressName', 'text' => 'L.-Adr. Name', 'required' => true], ['key' => 'deliveryAddressLine', 'text' => 'L.-Adr.', 'required' => true], ['key' => 'deliveryAddressPLZ', 'text' => 'L.-Adr. PLZ', 'required' => true], diff --git a/public/bundler.php b/public/bundler.php index 9bd9e99eb..2f6228deb 100644 --- a/public/bundler.php +++ b/public/bundler.php @@ -48,6 +48,7 @@ $jsFiles = [ "plugins/vue/tt-components/tt-checkbox.js", "plugins/vue/tt-components/tt-textarea.js", "plugins/vue/tt-components/tt-position-manager.js", + "plugins/vue/tt-components/tt-tooltip.js", ]; diff --git a/public/js/pages/WarehouseOrder/WarehouseOrder.js b/public/js/pages/WarehouseOrder/WarehouseOrder.js index 7d958f542..bb8b9b4e7 100644 --- a/public/js/pages/WarehouseOrder/WarehouseOrder.js +++ b/public/js/pages/WarehouseOrder/WarehouseOrder.js @@ -373,23 +373,26 @@ Vue.component('warehouse-order-modal', { return; } - const orderRequest = JSON.parse(localStorage.getItem('WarehouseOrder_create')); - if (!orderRequest) return; + const orderRequests = JSON.parse(localStorage.getItem('WarehouseOrder_create')); + if (!orderRequests) return; - const positions = JSON.parse(orderRequest.positions); - this.order.positions = await Promise.all(positions.map(async p => { - const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`, - {params: {articleId: p.articleId}})).data[0]; - return { - article: p.articleId, - amount: p.amount, - buyPrice: distributor.purchasePrice, - distributorId: distributor.id, - distributorArticleNumber: distributor.externalArticleNumber, - verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`, - linkedOrderRequestId: orderRequest.id - }; - })); + for (const orderRequest of orderRequests) { + const positions = JSON.parse(orderRequest.positions); + const parsedPositions = await Promise.all(positions.map(async p => { + const distributor = (await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrder/getArticleDistributorData`, + {params: {articleId: p.articleId}})).data[0]; + return { + article: p.articleId, + amount: p.amount, + buyPrice: distributor.purchasePrice, + distributorId: distributor.id, + distributorArticleNumber: distributor.externalArticleNumber, + verwendung: `${p.hasOwnProperty('purpose') ? p.purpose : ''} [Bestellwunsch: #${orderRequest.id}]`, + linkedOrderRequestId: orderRequest.id + }; + })); + this.order.positions = [...this.order.positions, ...parsedPositions]; + } localStorage.removeItem('WarehouseOrder_create'); }, diff --git a/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js index 3a1b94baf..13cb73efb 100644 --- a/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js +++ b/public/js/pages/WarehouseOrderRequest/WarehouseOrderRequest.js @@ -17,8 +17,8 @@ window.TT_CONFIG["CRUD_CONFIG"]["additionalActions"] = [ condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 1, }, { - key: "createOrder", - title: "Bestellung erstellen", + key: "addOrderToCart", + title: "Bestellung in den Warenkorb", class: "fas fa-shopping-cart text-primary", // Use shopping-cart to indicate order creation condition: (row) => window.TT_CONFIG['WAREHOUSE_ADMIN'] === '1' && row.cancelled === 0 && (!row.linkedOrderIds || row.linkedOrderIds.length === 0) @@ -203,7 +203,18 @@ Vue.component('warehouse-order-request', { @undoneOrder="undoneOrder" @createLog="createLog" @createOrder="createOrder" + @addOrderToCart="addOrderToCart" ref="crud"> + + @@ -229,7 +240,8 @@ Vue.component('warehouse-order-request', { addLogModalId: null, showHiddenRequests: false, showCanceledRequests: false, - orderRequestModalId: null + orderRequestModalId: null, + orderShoppingCart: [], }), methods: { openHistory(e) { @@ -263,12 +275,26 @@ Vue.component('warehouse-order-request', { uncancelRequest(row) { this.cancelRequest(row, '0'); }, - async createOrder(row) { - const res = await axios.get(`${window.TT_CONFIG.BASE_PATH}/WarehouseOrderRequest/getById?id=${row.id}`); + async createOrder() { + if (this.orderShoppingCart.length > 0) { + localStorage.setItem('WarehouseOrder_create', JSON.stringify(this.orderShoppingCart)); + window.location.href = `${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrder`; + } else window.notify('warning', 'Warenkorb ist leer'); + }, + async addOrderToCart(row) { + const res = await axios.get(`${window.TT_CONFIG["BASE_PATH"]}/WarehouseOrderRequest/getById?id=${row.id}`); if (res.data?.positions && typeof res.data.positions === 'string') { - localStorage.setItem('WarehouseOrder_create', JSON.stringify(res.data)); - window.location.href = `${window.TT_CONFIG.BASE_PATH}/WarehouseOrder`; - } else window.notify('error', res.data.message || 'Fehler beim erstellen der Bestellung'); + this.orderShoppingCart.push(res.data); + window.notify('success', 'Bestellung in den Warenkorb gelegt'); + } else window.notify('error', res.data.message || 'Fehler beim hinzufügen der Bestellung'); + } + }, + computed: { + createOrdersButtonText: function () { + return this.orderShoppingCart.length > 0 ? `${this.orderShoppingCart.length} Bestellung(en) erstellen` : 'Bestellung(en) erstellen'; + }, + createOrdersButtonToolTipText: function () { + return this.orderShoppingCart.length > 0 ? `Erstellt ${this.orderShoppingCart.length} Bestellung(en)` : 'Keine Bestellung im Warenkorb'; } } }); diff --git a/public/plugins/vue/tt-components/css/tt-tooltip.css b/public/plugins/vue/tt-components/css/tt-tooltip.css new file mode 100644 index 000000000..38f74f779 --- /dev/null +++ b/public/plugins/vue/tt-components/css/tt-tooltip.css @@ -0,0 +1,96 @@ +.tt-tooltip-wrapper { + position: relative; + display: inline-block; /* Or 'block' depending on your layout needs */ + cursor: pointer; +} + +.tt-tooltip-box { + position: absolute; + background-color: #333; /* Dark background */ + color: #fff; /* White text */ + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + white-space: nowrap; + z-index: 10; /* Ensure it's above other elements */ + opacity: 0; /* Hidden by default, fade in */ + transition: opacity 0.3s; + pointer-events: none; /* Prevent tooltip from interfering with mouse events */ + text-align: center; /* Center text */ +} + +/* Make tooltip visible when showTooltip is true */ +.tt-tooltip-wrapper:hover .tt-tooltip-box { + opacity: 1; +} + + +/* Positioning */ +.tt-tooltip-top { + bottom: 100%; + left: 50%; + transform: translateX(-50%); + margin-bottom: 5px; /* Space between element and tooltip */ +} + +.tt-tooltip-bottom { + top: 100%; + left: 50%; + transform: translateX(-50%); + margin-top: 5px; +} + +.tt-tooltip-left { + right: 100%; + top: 50%; + transform: translateY(-50%); + margin-right: 5px; +} + +.tt-tooltip-right { + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 5px; +} + +/* Optional: Add arrows */ +.tt-tooltip-box::after { + content: ''; + position: absolute; + border-width: 5px; + border-style: solid; +} + +.tt-tooltip-top::after { + top: 100%; + left: 50%; + margin-left: -5px; + border-color: #333 transparent transparent transparent; +} + +.tt-tooltip-bottom::after { + bottom: 100%; + left: 50%; + margin-left: -5px; + border-color: transparent transparent #333 transparent; +} + +.tt-tooltip-left::after { + top: 50%; + left: 100%; + margin-top: -5px; + border-color: transparent transparent transparent #333; +} + +.tt-tooltip-right::after { + top: 50%; + right: 100%; + margin-top: -5px; + border-color: transparent #333 transparent transparent; +} + +.tt-tooltip-wrapper > * { + display: inline-block; /* Ensure the tooltip wrapper behaves correctly */ + width: 100% !important; +} \ No newline at end of file diff --git a/public/plugins/vue/tt-components/tt-button.js b/public/plugins/vue/tt-components/tt-button.js index 472ae9cb7..c88220236 100644 --- a/public/plugins/vue/tt-components/tt-button.js +++ b/public/plugins/vue/tt-components/tt-button.js @@ -2,9 +2,7 @@ Vue.component('tt-button', { //language=Vue template: ` -
- - - - -
`, props: { sm: {type: Boolean, default: false}, diff --git a/public/plugins/vue/tt-components/tt-table-crud.js b/public/plugins/vue/tt-components/tt-table-crud.js index feac23f5a..860c07c1a 100644 --- a/public/plugins/vue/tt-components/tt-table-crud.js +++ b/public/plugins/vue/tt-components/tt-table-crud.js @@ -65,7 +65,7 @@ Vue.component('tt-table-crud', { - + diff --git a/public/plugins/vue/tt-components/tt-tooltip.js b/public/plugins/vue/tt-components/tt-tooltip.js new file mode 100644 index 000000000..308236fbc --- /dev/null +++ b/public/plugins/vue/tt-components/tt-tooltip.js @@ -0,0 +1,31 @@ +Vue.component('tt-tooltip', { + props: { + text: { + type: String, + required: true, + default: 'Tooltip text' + }, + position: { + type: String, + default: 'top', // Options: top, bottom, left, right + validator: function (value) { + // The value must match one of these strings + return ['top', 'bottom', 'left', 'right'].indexOf(value) !== -1; + } + } + }, + data() { + return { + showTooltip: true + }; + }, + template: ` +
+
+ {{ text }} +
+
+ ` +}); \ No newline at end of file