This commit is contained in:
Philippe Torrel
2026-07-24 13:31:30 +02:00
parent c118892017
commit e5c2f47063
19 changed files with 1380 additions and 659 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,344 @@
(() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
try {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
} catch (e) {
throw mod = 0, e;
}
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// node_modules/toastify-js/src/toastify.js
var require_toastify = __commonJS({
"node_modules/toastify-js/src/toastify.js"(exports, module) {
(function(root, factory) {
if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.Toastify = factory();
}
})(exports, function(global) {
var Toastify2 = function(options) {
return new Toastify2.lib.init(options);
}, version = "1.12.0";
Toastify2.defaults = {
oldestFirst: true,
text: "Toastify is awesome!",
node: void 0,
duration: 3e3,
selector: void 0,
callback: function() {
},
destination: void 0,
newWindow: false,
close: false,
gravity: "toastify-top",
positionLeft: false,
position: "",
backgroundColor: "",
avatar: "",
className: "",
stopOnFocus: true,
onClick: function() {
},
offset: { x: 0, y: 0 },
escapeMarkup: true,
ariaLive: "polite",
style: { background: "" }
};
Toastify2.lib = Toastify2.prototype = {
toastify: version,
constructor: Toastify2,
// Initializing the object with required parameters
init: function(options) {
if (!options) {
options = {};
}
this.options = {};
this.toastElement = null;
this.options.text = options.text || Toastify2.defaults.text;
this.options.node = options.node || Toastify2.defaults.node;
this.options.duration = options.duration === 0 ? 0 : options.duration || Toastify2.defaults.duration;
this.options.selector = options.selector || Toastify2.defaults.selector;
this.options.callback = options.callback || Toastify2.defaults.callback;
this.options.destination = options.destination || Toastify2.defaults.destination;
this.options.newWindow = options.newWindow || Toastify2.defaults.newWindow;
this.options.close = options.close || Toastify2.defaults.close;
this.options.gravity = options.gravity === "bottom" ? "toastify-bottom" : Toastify2.defaults.gravity;
this.options.positionLeft = options.positionLeft || Toastify2.defaults.positionLeft;
this.options.position = options.position || Toastify2.defaults.position;
this.options.backgroundColor = options.backgroundColor || Toastify2.defaults.backgroundColor;
this.options.avatar = options.avatar || Toastify2.defaults.avatar;
this.options.className = options.className || Toastify2.defaults.className;
this.options.stopOnFocus = options.stopOnFocus === void 0 ? Toastify2.defaults.stopOnFocus : options.stopOnFocus;
this.options.onClick = options.onClick || Toastify2.defaults.onClick;
this.options.offset = options.offset || Toastify2.defaults.offset;
this.options.escapeMarkup = options.escapeMarkup !== void 0 ? options.escapeMarkup : Toastify2.defaults.escapeMarkup;
this.options.ariaLive = options.ariaLive || Toastify2.defaults.ariaLive;
this.options.style = options.style || Toastify2.defaults.style;
if (options.backgroundColor) {
this.options.style.background = options.backgroundColor;
}
return this;
},
// Building the DOM element
buildToast: function() {
if (!this.options) {
throw "Toastify is not initialized";
}
var divElement = document.createElement("div");
divElement.className = "toastify on " + this.options.className;
if (!!this.options.position) {
divElement.className += " toastify-" + this.options.position;
} else {
if (this.options.positionLeft === true) {
divElement.className += " toastify-left";
console.warn("Property `positionLeft` will be depreciated in further versions. Please use `position` instead.");
} else {
divElement.className += " toastify-right";
}
}
divElement.className += " " + this.options.gravity;
if (this.options.backgroundColor) {
console.warn('DEPRECATION NOTICE: "backgroundColor" is being deprecated. Please use the "style.background" property.');
}
for (var property in this.options.style) {
divElement.style[property] = this.options.style[property];
}
if (this.options.ariaLive) {
divElement.setAttribute("aria-live", this.options.ariaLive);
}
if (this.options.node && this.options.node.nodeType === Node.ELEMENT_NODE) {
divElement.appendChild(this.options.node);
} else {
if (this.options.escapeMarkup) {
divElement.innerText = this.options.text;
} else {
divElement.innerHTML = this.options.text;
}
if (this.options.avatar !== "") {
var avatarElement = document.createElement("img");
avatarElement.src = this.options.avatar;
avatarElement.className = "toastify-avatar";
if (this.options.position == "left" || this.options.positionLeft === true) {
divElement.appendChild(avatarElement);
} else {
divElement.insertAdjacentElement("afterbegin", avatarElement);
}
}
}
if (this.options.close === true) {
var closeElement = document.createElement("button");
closeElement.type = "button";
closeElement.setAttribute("aria-label", "Close");
closeElement.className = "toast-close";
closeElement.innerHTML = "✖";
closeElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.removeElement(this.toastElement);
window.clearTimeout(this.toastElement.timeOutValue);
}.bind(this)
);
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
if ((this.options.position == "left" || this.options.positionLeft === true) && width > 360) {
divElement.insertAdjacentElement("afterbegin", closeElement);
} else {
divElement.appendChild(closeElement);
}
}
if (this.options.stopOnFocus && this.options.duration > 0) {
var self = this;
divElement.addEventListener(
"mouseover",
function(event) {
window.clearTimeout(divElement.timeOutValue);
}
);
divElement.addEventListener(
"mouseleave",
function() {
divElement.timeOutValue = window.setTimeout(
function() {
self.removeElement(divElement);
},
self.options.duration
);
}
);
}
if (typeof this.options.destination !== "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
if (this.options.newWindow === true) {
window.open(this.options.destination, "_blank");
} else {
window.location = this.options.destination;
}
}.bind(this)
);
}
if (typeof this.options.onClick === "function" && typeof this.options.destination === "undefined") {
divElement.addEventListener(
"click",
function(event) {
event.stopPropagation();
this.options.onClick();
}.bind(this)
);
}
if (typeof this.options.offset === "object") {
var x = getAxisOffsetAValue("x", this.options);
var y = getAxisOffsetAValue("y", this.options);
var xOffset = this.options.position == "left" ? x : "-" + x;
var yOffset = this.options.gravity == "toastify-top" ? y : "-" + y;
divElement.style.transform = "translate(" + xOffset + "," + yOffset + ")";
}
return divElement;
},
// Displaying the toast
showToast: function() {
this.toastElement = this.buildToast();
var rootElement;
if (typeof this.options.selector === "string") {
rootElement = document.getElementById(this.options.selector);
} else if (this.options.selector instanceof HTMLElement || typeof ShadowRoot !== "undefined" && this.options.selector instanceof ShadowRoot) {
rootElement = this.options.selector;
} else {
rootElement = document.body;
}
if (!rootElement) {
throw "Root element is not defined";
}
var elementToInsert = Toastify2.defaults.oldestFirst ? rootElement.firstChild : rootElement.lastChild;
rootElement.insertBefore(this.toastElement, elementToInsert);
Toastify2.reposition();
if (this.options.duration > 0) {
this.toastElement.timeOutValue = window.setTimeout(
function() {
this.removeElement(this.toastElement);
}.bind(this),
this.options.duration
);
}
return this;
},
hideToast: function() {
if (this.toastElement.timeOutValue) {
clearTimeout(this.toastElement.timeOutValue);
}
this.removeElement(this.toastElement);
},
// Removing the element from the DOM
removeElement: function(toastElement) {
toastElement.className = toastElement.className.replace(" on", "");
window.setTimeout(
function() {
if (this.options.node && this.options.node.parentNode) {
this.options.node.parentNode.removeChild(this.options.node);
}
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
this.options.callback.call(toastElement);
Toastify2.reposition();
}.bind(this),
400
);
}
};
Toastify2.reposition = function() {
var topLeftOffsetSize = {
top: 15,
bottom: 15
};
var topRightOffsetSize = {
top: 15,
bottom: 15
};
var offsetSize = {
top: 15,
bottom: 15
};
var allToasts = document.getElementsByClassName("toastify");
var classUsed;
for (var i = 0; i < allToasts.length; i++) {
if (containsClass(allToasts[i], "toastify-top") === true) {
classUsed = "toastify-top";
} else {
classUsed = "toastify-bottom";
}
var height = allToasts[i].offsetHeight;
classUsed = classUsed.substr(9, classUsed.length - 1);
var offset2 = 15;
var width = window.innerWidth > 0 ? window.innerWidth : screen.width;
if (width <= 360) {
allToasts[i].style[classUsed] = offsetSize[classUsed] + "px";
offsetSize[classUsed] += height + offset2;
} else {
if (containsClass(allToasts[i], "toastify-left") === true) {
allToasts[i].style[classUsed] = topLeftOffsetSize[classUsed] + "px";
topLeftOffsetSize[classUsed] += height + offset2;
} else {
allToasts[i].style[classUsed] = topRightOffsetSize[classUsed] + "px";
topRightOffsetSize[classUsed] += height + offset2;
}
}
}
return this;
};
function getAxisOffsetAValue(axis, options) {
if (options.offset[axis]) {
if (isNaN(options.offset[axis])) {
return options.offset[axis];
} else {
return options.offset[axis] + "px";
}
}
return "0px";
}
function containsClass(elem, yourClass) {
if (!elem || typeof yourClass !== "string") {
return false;
} else if (elem.className && elem.className.trim().split(/\s+/gi).indexOf(yourClass) > -1) {
return true;
} else {
return false;
}
}
Toastify2.lib.init.prototype = Toastify2.lib;
return Toastify2;
});
}
});
// node_modules/@popperjs/core/lib/index.js
var lib_exports = {};
@@ -5164,6 +5499,65 @@
enableDismissTrigger(Toast);
defineJQueryPlugin(Toast);
// dev/scripts/modules/Toast.js
var import_toastify_js = __toESM(require_toastify(), 1);
var Toast2 = (text = "", variant = "primary") => {
const getColorByName = (variant2 = "") => {
let color;
switch (variant2.trim().toLowerCase()) {
case "primary":
color = "#0d6efd";
break;
case "secondary":
color = "#6c757d";
break;
case "warning":
color = "#ffc720";
break;
case "success":
color = "#13653f";
break;
case "info":
color = "#25cff2";
break;
case "error":
case "danger":
color = "#a52834";
break;
case "light":
color = "#babbbc";
break;
case "dark":
color = "#373b3e";
break;
default:
console.warn("variant not found");
color: "white";
}
return color;
};
const show = () => {
(0, import_toastify_js.default)({
text,
duration: 2e3,
className: variant,
gravity: "top",
// `top` or `bottom`
position: "center",
// `left`, `center` or `right`
stopOnFocus: true,
// Prevents dismissing of toast on hover
style: {
background: getColorByName(variant)
}
}).showToast();
};
return {
show
};
};
var Toast_default = Toast2;
// dev/scripts/modules/ProductManager.js
var ProductManager = (el = null) => {
const BASE_URL = "http://127.0.0.1:8000";
@@ -5177,25 +5571,41 @@
inputPrice: module.querySelector(".input-product-price"),
btnAdd: module.querySelector(".button-product-add"),
templateRow: module.querySelector(".template-row"),
// Modal
// Modal Edit
modalEdit: module.querySelector(".modal-edit"),
formEdit: module.querySelector(".form-product-edit"),
inputEditName: module.querySelector(".input-edit-name"),
inputEditPrice: module.querySelector(".input-edit-price"),
inputEditId: module.querySelector(".input-edit-id"),
inputEditPosition: module.querySelector(".input-edit-position")
inputEditPosition: module.querySelector(".input-edit-position"),
btnUpdate: module.querySelector(".button-product-update"),
// Modal Delete
modalDelete: module.querySelector(".modal-delete"),
productName: module.querySelector("strong.product-name"),
btnConfirmDelete: module.querySelector(".button-confirm-delete")
};
console.log(DOM);
const bsModalEdit = new Modal(DOM.modalEdit, {
backdrop: "static",
// true
keyboard: true
});
console.log(DOM);
const bsModalDelete = new Modal(DOM.modalDelete, {
backdrop: "static",
// true
keyboard: true
});
const init = () => {
console.log("init");
initProducts();
DOM.btnAdd.addEventListener("click", onClickAdd);
DOM.btnAdd.disabled = true;
DOM.btnConfirmDelete.addEventListener("click", onClickConfirmDelete);
DOM.formEdit.addEventListener("submit", onSubmitEdit);
window.addEventListener("keyup", onKeyUp);
};
const onKeyUp = (e) => {
DOM.btnAdd.disabled = DOM.inputName.value === "" || DOM.inputPrice.value === "";
};
const onSubmitEdit = (e) => {
e.preventDefault();
@@ -5205,11 +5615,17 @@
_id: DOM.inputEditId.value,
position: DOM.inputEditPosition.value
};
DOM.btnUpdate.querySelector("i").classList.add("fa-spin");
updateProduct(product).then((data) => {
console.log(data);
if (data.success) {
Toast_default(data.msg, "success").show();
DOM.btnUpdate.querySelector("i").classList.remove("fa-spin");
loadProducts();
resetFields();
bsModalEdit.hide();
} else {
Toast_default(data.msg, "error").show();
}
});
};
@@ -5223,7 +5639,9 @@
console.log(data);
if (data.success) {
console.log("add");
Toast_default(data.msg, "success").show();
loadProducts();
resetFields();
}
});
disableNonFunctionalButtons();
@@ -5238,9 +5656,25 @@
};
const onClickRemove = (e) => {
const btnEl = e.currentTarget;
const currentRowEl = parents(btnEl, "tr")[0];
currentRowEl.remove();
disableNonFunctionalButtons();
const currentRow = parents(btnEl, "tr")[0];
const id = currentRow.dataset.id;
const productName = currentRow.querySelector(".td-name").textContent.trim();
DOM.btnConfirmDelete.dataset.id = id;
DOM.productName.textContent = productName;
};
const onClickConfirmDelete = (e) => {
const btnEl = e.currentTarget;
const id = btnEl.dataset.id;
deleteProduct(id).then((data) => {
if (data.success) {
Toast_default(data.msg, "success").show();
bsModalDelete.hide();
loadProducts();
} else {
Toast_default(data.msg, "error").show();
console.error(data);
}
});
};
const onClickMoveUp = (e) => {
const btnEl = e.currentTarget;
@@ -5307,6 +5741,7 @@
const data = await response.json();
return data;
} catch (error) {
Toast_default(error, "error").show();
console.error(error);
return error;
}
@@ -5356,11 +5791,30 @@
return error;
}
};
const deleteProduct = async (id) => {
try {
const response = await fetch(`${BASE_URL}/api/products/${id}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
});
if (!response.ok) {
throw new Error("Fetch went wrong.");
}
const data = await response.json();
return data;
} catch (error) {
console.error(error);
return error;
}
};
const initProducts = () => {
getProducts().then((products) => {
products.forEach((product) => {
addProduct(product);
});
Toast_default("Init products").show();
disableNonFunctionalButtons();
});
};
@@ -5405,6 +5859,12 @@
DOM.inputEditId.value = id;
DOM.inputEditPosition.value = position;
};
const resetFields = () => {
DOM.btnAdd.disabled = true;
DOM.inputPrice.value = "";
DOM.inputName.value = "";
DOM.formEdit.reset();
};
const disableNonFunctionalButtons = () => {
Array.from(DOM.tBody.querySelectorAll("tr")).forEach((tr) => {
const btnMoveUp = tr.querySelector(".button-product-move-up");
@@ -5423,7 +5883,8 @@
init();
return {
init,
initProducts
initProducts,
resetFields
};
};
var ProductManager_default = ProductManager;
@@ -5456,6 +5917,15 @@
})();
/*! Bundled license information:
toastify-js/src/toastify.js:
(*!
* Toastify js 1.12.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*)
bootstrap/dist/js/bootstrap.esm.js:
(*!
* Bootstrap v5.3.8 (https://getbootstrap.com/)

File diff suppressed because one or more lines are too long

View File

@@ -30,7 +30,6 @@ import ProductManager from './modules/ProductManager'; // Dateiendung kann wegge
break;
case '/projects/product-manager.html':
console.log('PRODUCT_MANAGER');
const pm = ProductManager();
break;
default:

View File

@@ -1,4 +1,8 @@
import { Modal } from 'bootstrap'; // ohne Pfadangabe (Modul auslesen aus node_modules) funktioniert nur mit JS-Bundler (esbuild, rollup & co.)
import Toast from './Toast';
// import * as bootstrap from 'bootstrap';
// import 'bootstrap';
// import 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js'; // würde auch ohne Bundler funktionieren
// import '../../../node_modules/bootstrap/dist/js/bootstrap.esm.js'; // würde auch ohne Bundler funktionieren
@@ -22,21 +26,31 @@ const ProductManager = (el = null) => {
btnAdd: module.querySelector('.button-product-add'),
templateRow: module.querySelector('.template-row'),
// Modal
// Modal Edit
modalEdit: module.querySelector('.modal-edit'),
formEdit: module.querySelector('.form-product-edit'),
inputEditName: module.querySelector('.input-edit-name'),
inputEditPrice: module.querySelector('.input-edit-price'),
inputEditId: module.querySelector('.input-edit-id'),
inputEditPosition: module.querySelector('.input-edit-position'),
btnUpdate: module.querySelector('.button-product-update'),
// Modal Delete
modalDelete: module.querySelector('.modal-delete'),
productName: module.querySelector('strong.product-name'),
btnConfirmDelete: module.querySelector('.button-confirm-delete'),
};
console.log(DOM);
const bsModalEdit = new Modal(DOM.modalEdit, {
backdrop: 'static', // true
keyboard: true,
});
console.log(DOM);
const bsModalDelete = new Modal(DOM.modalDelete, {
backdrop: 'static', // true
keyboard: true,
});
// === INIT =============
const init = () => {
@@ -46,13 +60,27 @@ const ProductManager = (el = null) => {
// Event-Lauscher zu beginn der Anwendung
DOM.btnAdd.addEventListener('click', onClickAdd);
DOM.btnAdd.disabled = true;
DOM.btnConfirmDelete.addEventListener('click', onClickConfirmDelete);
DOM.formEdit.addEventListener('submit', onSubmitEdit);
window.addEventListener('keyup', onKeyUp);
};
// === EVENTHANDLER =====
const onKeyUp = (e) => {
// if (DOM.inputName.value === '' && DOM.inputPrice.value === '') {
// DOM.btnAdd.disabled = true;
// } else {
// DOM.btnAdd.disabled = false;
// }
DOM.btnAdd.disabled = (DOM.inputName.value === '' || DOM.inputPrice.value === ''); // prettier-ignore
};
const onSubmitEdit = (e) => {
e.preventDefault(); // Standardverhalten unterbinden (Formular nicht an action versenden)
// console.log(Object.entries(new FormData(DOM.formEdit)));
const product = {
name: DOM.inputEditName.value,
price: Number(DOM.inputEditPrice.value),
@@ -61,11 +89,17 @@ const ProductManager = (el = null) => {
};
// update async process
DOM.btnUpdate.querySelector('i').classList.add('fa-spin');
updateProduct(product).then((data) => {
console.log(data);
if (data.success) {
Toast(data.msg, 'success').show();
DOM.btnUpdate.querySelector('i').classList.remove('fa-spin');
loadProducts(); // All Produkte auslesen
resetFields();
bsModalEdit.hide();
} else {
Toast(data.msg, 'error').show();
}
});
};
@@ -82,7 +116,9 @@ const ProductManager = (el = null) => {
console.log(data);
if (data.success) {
console.log('add');
Toast(data.msg, 'success').show();
loadProducts();
resetFields();
}
});
@@ -105,15 +141,42 @@ const ProductManager = (el = null) => {
const onClickRemove = (e) => {
const btnEl = e.currentTarget;
//const currentRowEl = btnEl.parentNode.parentNode; // parentNode -> td - parentNode -> tr
// MDN https://developer.mozilla.org/de/docs/Web/API/Element/closest
//const currentRowEl = btnEl.closest('tr');
const currentRow = parents(btnEl, 'tr')[0];
const id = currentRow.dataset.id;
const currentRowEl = parents(btnEl, 'tr')[0];
const productName = currentRow.querySelector('.td-name').textContent.trim();
currentRowEl.remove(); // kein IE11 support
// DOM.tBody.removeChild(currentRowEl);
disableNonFunctionalButtons();
DOM.btnConfirmDelete.dataset.id = id;
DOM.productName.textContent = productName;
// async fetch
// deleteProduct(id).then((data) => {
// if (data.success) {
// Toast(data.msg, 'success').show();
// console.log('delete: success', data);
// loadProducts();
// } else {
// Toast(data.msg, 'error').show();
// console.error(data);
// }
// });
};
const onClickConfirmDelete = (e) => {
const btnEl = e.currentTarget;
const id = btnEl.dataset.id;
// async fetch
deleteProduct(id).then((data) => {
if (data.success) {
Toast(data.msg, 'success').show();
bsModalDelete.hide();
loadProducts();
} else {
Toast(data.msg, 'error').show();
console.error(data);
}
});
};
const onClickMoveUp = (e) => {
@@ -220,6 +283,8 @@ const ProductManager = (el = null) => {
return data;
} catch (error) {
Toast(error, 'error').show();
console.error(error);
return error;
}
@@ -285,6 +350,29 @@ const ProductManager = (el = null) => {
}
};
// HTTP - Methode - DELETE
// CRU(D) - delete
// BREA(D) - delete - Produkt löschen
const deleteProduct = async (id) => {
try {
const response = await fetch(`${BASE_URL}/api/products/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
}); // HTTP Request URL
if (!response.ok) {
throw new Error('Fetch went wrong.');
}
const data = await response.json();
return data;
} catch (error) {
console.error(error);
return error;
}
};
// === FUNCTIONS ========
const initProducts = () => {
@@ -292,6 +380,7 @@ const ProductManager = (el = null) => {
products.forEach((product) => {
addProduct(product);
});
Toast('Init products').show();
disableNonFunctionalButtons();
});
@@ -359,6 +448,14 @@ const ProductManager = (el = null) => {
DOM.inputEditPosition.value = position;
};
const resetFields = () => {
DOM.btnAdd.disabled = true;
DOM.inputPrice.value = '';
DOM.inputName.value = '';
DOM.formEdit.reset();
};
const disableNonFunctionalButtons = () => {
Array.from(DOM.tBody.querySelectorAll('tr')).forEach((tr) => {
const btnMoveUp = tr.querySelector('.button-product-move-up');
@@ -383,6 +480,7 @@ const ProductManager = (el = null) => {
return {
init,
initProducts,
resetFields,
};
};

View File

@@ -0,0 +1,59 @@
import Toastify from 'toastify-js'; // das Modul wird von node_modules ausgelesen und gebundlet. Funktioniert nur mit JS-Bundler
const Toast = (text = '', variant = 'primary') => {
const getColorByName = (variant = '') => {
let color;
switch (variant.trim().toLowerCase()) {
case 'primary':
color = '#0d6efd';
break;
case 'secondary':
color = '#6c757d';
break;
case 'warning':
color = '#ffc720';
break;
case 'success':
color = '#13653f';
break;
case 'info':
color = '#25cff2';
break;
case 'error':
case 'danger':
color = '#a52834';
break;
case 'light':
color = '#babbbc';
break;
case 'dark':
color = '#373b3e';
break;
default:
console.warn('variant not found');
color: 'white';
}
return color;
};
const show = () => {
Toastify({
text: text,
duration: 2000,
className: variant,
gravity: 'top', // `top` or `bottom`
position: 'center', // `left`, `center` or `right`
stopOnFocus: true, // Prevents dismissing of toast on hover
style: {
background: getColorByName(variant),
},
}).showToast();
};
return {
show,
};
};
export default Toast;

View File

@@ -0,0 +1 @@
@import 'toastify-1.12/toastify';

View File

@@ -0,0 +1,88 @@
/*!
* Toastify js 1.12.0
* https://github.com/apvarun/toastify-js
* @license MIT licensed
*
* Copyright (C) 2018 Varun A P
*/
.toastify {
padding: 12px 20px;
color: #ffffff;
display: inline-block;
box-shadow:
0 3px 6px -1px rgba(0, 0, 0, 0.12),
0 10px 36px -4px rgba(77, 96, 232, 0.3);
background: -webkit-linear-gradient(315deg, #73a5ff, #5477f5);
background: linear-gradient(135deg, #73a5ff, #5477f5);
position: fixed;
opacity: 0;
transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);
border-radius: 2px;
cursor: pointer;
text-decoration: none;
max-width: calc(50% - 20px);
z-index: 2147483647;
}
.toastify.on {
opacity: 1;
}
.toast-close {
background: transparent;
border: 0;
color: white;
cursor: pointer;
font-family: inherit;
font-size: 1em;
opacity: 0.4;
padding: 0 5px;
}
.toastify-right {
right: 15px;
}
.toastify-left {
left: 15px;
}
.toastify-top {
top: -150px;
}
.toastify-bottom {
bottom: -150px;
}
.toastify-rounded {
border-radius: 25px;
}
.toastify-avatar {
width: 1.5em;
height: 1.5em;
margin: -7px 5px;
border-radius: 2px;
}
.toastify-center {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
max-width: -moz-fit-content;
}
@media only screen and (max-width: 360px) {
.toastify-right,
.toastify-left {
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
max-width: fit-content;
}
}

View File

@@ -9,7 +9,8 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"bootstrap": "^5.3.8"
"bootstrap": "^5.3.8",
"toastify-js": "^1.12.0"
},
"devDependencies": {
"browser-sync": "^3.0.4",
@@ -4369,6 +4370,12 @@
"node": ">=8.0"
}
},
"node_modules/toastify-js": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz",
"integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==",
"license": "MIT"
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",

View File

@@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "run-p server css js",
"js": "npx esbuild dev/scripts/main.js --watch --bundle --sourcemap --outfile=assets/js/build.js",
"http-server": "npx http-server -p 3000 -c-1",
"server": "npx browser-sync start --server --files 'assets/css/*.css, assets/js/*.js, **/*.html, *.html'",
@@ -21,6 +22,7 @@
"sass": "^1.101.3"
},
"dependencies": {
"bootstrap": "^5.3.8"
"bootstrap": "^5.3.8",
"toastify-js": "^1.12.0"
}
}

View File

@@ -49,6 +49,23 @@
<!-- ▼ product-manager ▼ -->
<div class="product-manager">
<div class="container">
<nav class="nav-products-actions">
<ul class="list">
<li>
<button class="btn btn-danger button-products-reset">
<i class="fa-solid fa-box-archive"></i>
Reset Products
</button>
</li>
<li>
<button class="btn btn-dark button-products-save">
<i class="fa-solid fa-hard-drive"></i>
Save Products
</button>
</li>
</ul>
</nav>
<!-- table.table.table-striped.table-products>(thead.table-dark>tr>th{Name}+th{Price in &euro;})+tbody>tr>td*2 -->
<table class="table table-striped table-products">
<thead class="table-dark">
@@ -119,7 +136,10 @@
<i class="fas fa-file-pen"></i>
<span class="visually-hidden">Edit Product</span>
</button>
<button class="btn btn-sm btn-danger button-product-remove">
<button
class="btn btn-sm btn-danger button-product-remove"
data-bs-toggle="modal"
data-bs-target="#modal-delete">
<i class="fas fa-trash-can"></i>
<span class="visually-hidden">Remove Product</span>
</button>
@@ -188,6 +208,34 @@
</div>
</div>
<!-- ▲ /Modal ▲ -->
<!-- Modal Delete -->
<div
class="modal modal-delete fade"
id="modal-delete"
tabindex="-1"
aria-labelledby="modal-delete-label"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modal-delete-label">Delete Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
Do you really want to delete Product:<br />
<strong class="product-name">PRODUCT_NAME</strong>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger button-confirm-delete">Confirm to delete</button>
</div>
</div>
</div>
</div>
<!-- ▲ /Modal Delete ▲ -->
</div>
<!-- ▲ /product-manager ▲ -->
</main>