Dokumentation der Methoden, einfache Verbesserungen, mehr Offline-Kompatibilität durch Base64 für Platzhalter-Bilder

This commit is contained in:
Dennis Heinrich 2025-04-06 13:30:35 +02:00
parent 37b068396e
commit b36ed2dbb5
5 changed files with 381 additions and 142 deletions

View file

@ -1,4 +1,3 @@
import './theme.js'
import './calculator.js' import './calculator.js'
import './../../node_modules/bootstrap/dist/js/bootstrap.min.js' import './../../node_modules/bootstrap/dist/js/bootstrap.min.js'

View file

@ -1,25 +1,116 @@
/**
* Element class provides static methods to get various elements from the DOM.
*/
class Element {
/**
* Get the import/export textarea element
* @returns {HTMLTextAreaElement}
*/
static getImportExportTextarea() {
return document.querySelector('#input-export-import');
}
/**
* Get the product list element
* @returns {HTMLElement}
*/
static getProductListElement() {
return document.querySelector('.product-list');
}
/**
* Get the settings product list element
* @returns {HTMLElement}
*/
static getSettingListElement() {
return document.querySelector('.settings-product-list');
}
/**
* Get the cart element
* @returns {HTMLElement}
*/
static getCartElement() {
return document.querySelector('.cart-items');
}
/**
* Get the cart empty alert element
* @returns {HTMLElement}
*/
static getCartEmptyAlertElement() {
return document.querySelector('.alert.cart-empty');
}
/**
* Get the create new product button element
* @returns {HTMLButtonElement}
*/
static getCreateNewProductButton() {
return document.querySelector('#create-product');
}
/**
* Get the cart button element
* @returns {HTMLButtonElement}
*/
static getCartButton() {
return document.querySelector('button.cart-value');
}
}
/**
* TemplateElement class provides static methods to get various templates from the DOM.
*/
class TemplateElement { class TemplateElement {
/**
* Get the product list element template
* @returns {HTMLElement}
*/
static getProductTemplate() { static getProductTemplate() {
const element = document.querySelector('[data-template=product]').cloneNode(true); const element = document.querySelector('[data-template=product]').cloneNode(true);
return this.removeTemplate(element); return this.removeTemplate(element);
} }
/**
* Get the cart line element template
* @returns {HTMLElement}
*/
static getCartLineTemplate() { static getCartLineTemplate() {
const element = document.querySelector('[data-template=product-line-item]').cloneNode(true); const element = document.querySelector('[data-template=product-line-item]').cloneNode(true);
return this.removeTemplate(element); return this.removeTemplate(element);
} }
/**
* Get the settings product element template
* @returns {HTMLElement}
*/
static getSettingsProductTemplate() { static getSettingsProductTemplate() {
const element = document.querySelector('[data-template=settings-product]').cloneNode(true); const element = document.querySelector('[data-template=settings-product]').cloneNode(true);
return this.removeTemplate(element); return this.removeTemplate(element);
} }
/**
* Remove the template attribute from the element
* @param element {HTMLElement}
* @returns {HTMLElement}
*/
static removeTemplate(element) { static removeTemplate(element) {
element.removeAttribute('data-template'); element.removeAttribute('data-template');
return element; return element;
} }
} }
/**
* Product class represents a product with an id, name, price, and image.
*/
class Product { class Product {
/**
* Product constructor
* @param name {string} Name of the product
* @param price {number} Price of the product
* @param image {string} Image of the product (source url or base64)
*/
constructor(name, price, image) { constructor(name, price, image) {
this.id = Product.generateId(); this.id = Product.generateId();
this.name = name; this.name = name;
@ -27,48 +118,123 @@ class Product {
this.image = image; this.image = image;
} }
/**
* Generate a random id for the product
* @returns {number}
*/
static generateId() { static generateId() {
return Math.floor(Math.random() * 1000000000) * Math.floor(Math.random() * 1000000000); return Math.floor(Math.random() * 1000000000) * Math.floor(Math.random() * 1000000000);
} }
/**
* Get the product html element by template and set the attributes by the product object
* @returns {HTMLElement}
*/
getProductElement() { getProductElement() {
const productElement = TemplateElement.getProductTemplate(); const productElement = TemplateElement.getProductTemplate();
productElement.setAttribute('data-id', this.id); productElement.setAttribute('data-id', this.id);
productElement.querySelector('[data-attr=name]').textContent = this.name; productElement.querySelector('[data-attr=name]').textContent = this.name;
productElement.querySelector('[data-attr=image]').src = this.image; productElement.querySelector('[data-attr=image]').src = this.image;
productElement.addEventListener('click', () => { productElement.addEventListener('click', () => {
cart.addProduct(this); cartManager.addProduct(this);
}) })
return productElement; return productElement;
} }
/**
* Get the settings product html element by template and set the attributes by the product object
* @returns {HTMLElement}
*/
getSettingsProductElement() { getSettingsProductElement() {
const productElement = TemplateElement.getSettingsProductTemplate(); const productElement = TemplateElement.getSettingsProductTemplate();
productElement.setAttribute('data-id', this.id); productElement.setAttribute('data-id', this.id);
productElement.textContent = this.name; productElement.textContent = this.name;
productElement.addEventListener('click', () => { productElement.addEventListener('click', () => {
productList.removeProduct(this); productManager.removeProduct(this);
productList.setExportField(); productManager.setExportFieldJsonValue();
productElement.remove(); productElement.remove();
}) })
return productElement; return productElement;
} }
} }
/**
* CartLine class represents a line in the cart with a product and its quantity.
*/
class CartLine { class CartLine {
/**
* CartLine constructor
* @param product {Product}
* @param quantity {number}
*/
constructor(product, quantity) { constructor(product, quantity) {
this.product = product; this.product = product;
this.quantity = quantity; this.quantity = quantity;
} }
} }
class ProductList { /**
* Base64Image class provides static methods to convert an image URL or file to a base64 string.
*/
class Base64Image {
/**
* Convert an image URL to a base64 string
* @param url
* @returns {Promise<string>}
*/
static fromImageUrl(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const dataUrl = canvas.toDataURL();
resolve(dataUrl);
};
img.onerror = reject;
img.src = url;
});
}
/**
* Convert a file to a base64 string
* @param file
* @returns {Promise<string>}
*/
static fromFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
}
/**
* ProductManager class manages the product list, including loading from JSON, adding, and removing products.
*/
class ProductManager {
constructor() { constructor() {
this.loadFromJson(localStorage.getItem('products') ?? []) this.loadFromJson(localStorage.getItem('products') ?? [])
this.setExportField(); this.setExportFieldJsonValue();
this.getImportExportTextarea().addEventListener('input', (e) => { this.registerSettingsFormEvents();
this.registerImportFormEvents();
}
/**
* Register events for the import/export textarea.
*/
registerImportFormEvents() {
Element.getImportExportTextarea().addEventListener('input', (e) => {
try { try {
const json = e.target.value; localStorage.setItem('products', e.target.value);
localStorage.setItem('products', json);
window.location = window.location; window.location = window.location;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -76,10 +242,17 @@ class ProductList {
}); });
} }
setExportField() { /**
this.getImportExportTextarea().textContent = JSON.stringify(this.products); * Set the JSON value of the export field to the current products.
*/
setExportFieldJsonValue() {
Element.getImportExportTextarea().textContent = JSON.stringify(this.products);
} }
/**
* Load products from JSON string and parse it into Product objects, then render the product list.
* @param json
*/
loadFromJson(json) { loadFromJson(json) {
let parse = []; let parse = [];
try { try {
@ -89,59 +262,110 @@ class ProductList {
this.renderProductList(); this.renderProductList();
} }
getImportExportTextarea() { /**
return document.querySelector('#input-export-import'); * Register events for the create new product button.
*/
registerSettingsFormEvents() {
Element.getCreateNewProductButton().addEventListener('click', (e) => {
e.preventDefault();
const name = document.querySelector('#product-name').value;
const price = document.querySelector('#product-price').value;
const image = document.querySelector('#product-image').files[0];
// Validate price
if(name.length === 0) {
alert('Name muss ausgefüllt sein');
return;
} }
getProductListElement() { // Turn image into base64 or use placeholder image
return document.querySelector('.product-list'); if (image) {
const imageSrcWithBase64 = Base64Image.fromFile(image);
imageSrcWithBase64.then(imageSrcWithBase64 => {
productManager.addProduct(new Product(name, price, imageSrcWithBase64));
ProductManager.resetProductSettingsForm();
});
} else {
productManager.addProduct(new Product(name, price, Base64Image.fromImageUrl('https://placehold.co/250x250?text={${name}}')));
ProductManager.resetProductSettingsForm();
}
});
} }
getSettingListElement() { /**
return document.querySelector('.settings-product-list'); * Reset the product settings form by clearing all input fields.
*/
static resetProductSettingsForm() {
document.querySelectorAll('form input:not([type=submit])').forEach(inputField => inputField.value = '');
} }
/**
* Render the product list by clearing the existing elements and appending new ones.
*/
renderProductList() { renderProductList() {
// Clear the product list element before rendering except the template // Clear the product list element before rendering except the template
this.getProductListElement().querySelectorAll('[data-id]').forEach(e => { Element.getProductListElement().querySelectorAll('[data-id]').forEach(e => {
if (e.getAttribute('data-template') === null) { if (e.getAttribute('data-template') === null) {
e.remove(); e.remove();
} }
}); });
this.products.forEach(e => { this.products.forEach(e => {
this.getProductListElement().appendChild(e.getProductElement()); Element.getProductListElement().appendChild(e.getProductElement());
this.getSettingListElement().appendChild(e.getSettingsProductElement()); Element.getSettingListElement().appendChild(e.getSettingsProductElement());
}) })
} }
/**
* Add a new product to the list and save it to local storage.
* @param product {Product}
*/
addProduct(product) { addProduct(product) {
this.products.push(product); this.products.push(product);
localStorage.setItem('products', JSON.stringify(this.products)); localStorage.setItem('products', JSON.stringify(this.products));
this.getProductListElement().appendChild(product.getProductElement()); Element.getProductListElement().appendChild(product.getProductElement());
this.getSettingListElement().appendChild(product.getSettingsProductElement()); Element.getSettingListElement().appendChild(product.getSettingsProductElement());
this.setExportField(); this.setExportFieldJsonValue();
} }
/**
* Remove a product from the list and local storage.
* @param product {Product}
*/
removeProduct(product) { removeProduct(product) {
this.products = this.products.filter(e => e.id !== product.id); this.products = this.products.filter(e => e.id !== product.id);
const productElement = this.getProductListElement().querySelector(`[data-id="${product.id}"]`); const productElement = Element.getProductListElement().querySelector(`[data-id="${product.id}"]`);
if (productElement) { if (productElement) {
productElement.remove(); productElement.remove();
} }
localStorage.setItem('products', JSON.stringify(this.products)); localStorage.setItem('products', JSON.stringify(this.products));
this.setExportField(); this.setExportFieldJsonValue();
} }
} }
class Cart { /**
* CartManager class manages the cart, including adding, removing products, and rendering the cart.
*/
class CartManager {
constructor() { constructor() {
this.cartLines = []; this.cartLines = [];
this.getCartButton().addEventListener('click', () => { this.registerCartResetEvent();
}
/**
* Register the cart reset event to clear the cart when the button is clicked.
*/
registerCartResetEvent() {
Element.getCartButton().addEventListener('click', () => {
this.cartLines = []; this.cartLines = [];
this.renderCart(); this.renderCart();
}); });
} }
/**
* Add a product to the cart. If the product already exists, increase the quantity.
* @param product {Product}
*/
addProduct(product) { addProduct(product) {
const cartLine = this.cartLines.find(e => e.product.id === product.id); const cartLine = this.cartLines.find(e => e.product.id === product.id);
if (cartLine) { if (cartLine) {
@ -152,6 +376,10 @@ class Cart {
this.renderCart(); this.renderCart();
} }
/**
* Remove a product from the cart. If the quantity is 0, remove the product from the cart.
* @param product {Product}
*/
removeProduct(product) { removeProduct(product) {
const cartLine = this.cartLines.find(e => e.product.id === product.id); const cartLine = this.cartLines.find(e => e.product.id === product.id);
if (cartLine) { if (cartLine) {
@ -163,33 +391,41 @@ class Cart {
this.renderCart(); this.renderCart();
} }
/**
* Render the cart by clearing the existing elements and appending new ones.
*/
renderCart() { renderCart() {
// Clear the cart element before rendering except the template // Clear the cart element before rendering except the template
this.getCartElement().querySelectorAll('[data-id]').forEach(e => { Element.getCartElement().querySelectorAll('[data-id]').forEach(e => {
if (e.getAttribute('data-template') === null) { if (e.getAttribute('data-template') === null) {
e.remove(); e.remove();
} }
}); });
if(this.cartLines.length === 0) { if(this.cartLines.length === 0) {
this.getAlertElement().classList.remove('d-none'); Element.getCartEmptyAlertElement().classList.remove('d-none');
} else { } else {
this.getAlertElement().classList.add('d-none'); Element.getCartEmptyAlertElement().classList.add('d-none');
} }
this.calculateCartValue(); this.calculateCartValue();
// Render each cart line // Render each cart line
this.cartLines.forEach(cartLine => { this.cartLines.forEach(cartLine => {
const cartLineElement = this.getCartLineElement(cartLine); const cartLineElement = this.getCartLineElement(cartLine);
this.getCartElement().appendChild(cartLineElement); Element.getCartElement().appendChild(cartLineElement);
}); });
} }
/**
* Get the cart line html element and set the attributes by the product object
* @param cartLine {CartLine}
* @returns {*}
*/
getCartLineElement(cartLine) { getCartLineElement(cartLine) {
const cartLineElement = TemplateElement.getCartLineTemplate(); const cartLineElement = TemplateElement.getCartLineTemplate();
cartLineElement.setAttribute('data-id', cartLine.product.id); cartLineElement.setAttribute('data-id', cartLine.product.id);
cartLineElement.querySelector('[data-attr=name]').textContent = cartLine.product.name; cartLineElement.querySelector('[data-attr=name]').textContent = cartLine.product.name;
cartLineElement.querySelector('[data-attr=value]').textContent = Cart.getNumberFormatter().format(cartLine.product.price); cartLineElement.querySelector('[data-attr=value]').textContent = CartManager.getNumberFormatter().format(cartLine.product.price);
cartLineElement.querySelector('[data-attr=quantity]').textContent = cartLine.quantity; cartLineElement.querySelector('[data-attr=quantity]').textContent = cartLine.quantity;
cartLineElement.addEventListener('click', () => { cartLineElement.addEventListener('click', () => {
this.removeProduct(cartLine.product); this.removeProduct(cartLine.product);
@ -197,42 +433,60 @@ class Cart {
return cartLineElement; return cartLineElement;
} }
getAlertElement() { /**
return document.querySelector('.alert.cart-empty'); * Calculate the total value of the cart by multiplying the product price with the quantity and setting it to the button.
} */
getCartElement() {
return document.querySelector('.cart-items');
}
calculateCartValue() { calculateCartValue() {
let cartValue = this.cartLines.reduce((acc, cartLine) => { let cartValue = this.cartLines.reduce((acc, cartLine) => {
return acc + (cartLine.product.price * cartLine.quantity); return acc + (cartLine.product.price * cartLine.quantity);
}, 0); }, 0);
this.getCartButton().querySelector('[data-total-value]').textContent = Cart.getNumberFormatter().format(cartValue); Element.getCartButton().querySelector('[data-total-value]').textContent = CartManager.getNumberFormatter().format(cartValue);
} }
/**
* Get the number formatter for the currency.
* @returns {Intl.NumberFormat}
*/
static getNumberFormatter() { static getNumberFormatter() {
return new Intl.NumberFormat('de-DE', { return new Intl.NumberFormat('de-DE', {
currency: 'EUR', currency: 'EUR',
minimumFractionDigits: 2 minimumFractionDigits: 2
}); });
} }
getCartButton() {
return document.querySelector('button.cart-value');
}
} }
class Tab { /**
* TabManager class manages the tabs in the settings page.
*/
class TabManager {
/**
* @type {boolean} If the settings tab is active or not
*/
static isSettingsTabActive = false;
constructor() {
TabManager.isSettingsTabActive = false;
document.querySelector('[data-toggle-tab]').addEventListener('click', (e) => {
TabManager.toggleTab();
});
}
/**
* Toggle the active tab between settings and products.
*/
static toggleTab() { static toggleTab() {
if(!isSettingsTab) { if(!TabManager.isSettingsTabActive) {
this.switchTab('settings'); this.switchTab('settings');
} else { } else {
this.switchTab('products'); this.switchTab('products');
} }
isSettingsTab = !isSettingsTab; TabManager.isSettingsTabActive = !TabManager.isSettingsTabActive;
} }
/**
* Switch the active tab by adding/removing the d-none class.
* @param tab {string} The tab to switch to
*/
static switchTab(tab) { static switchTab(tab) {
const tabs = document.querySelectorAll('[data-tab]'); const tabs = document.querySelectorAll('[data-tab]');
tabs.forEach(e => { tabs.forEach(e => {
@ -243,41 +497,41 @@ class Tab {
} }
} }
let isSettingsTab = false; /**
const cart = new Cart(); * ThemeManager class manages the theme of the application.
const productList = new ProductList(); */
class ThemeManager {
document.querySelector('[data-toggle-tab]').addEventListener('click', (e) => { constructor() {
Tab.toggleTab(); this.setBootstrapTheme(localStorage.getItem('bootstrap-theme') || 'light');
}); this.registerThemeSwitchEvent();
document.querySelector('#create-product').addEventListener('click', (e) => {
e.preventDefault(); // Prevent form submission (if any)
const name = document.querySelector('#product-name').value;
let fieldPrice = document.querySelector('#product-price').value;
const image = document.querySelector('#product-image').files[0];
if(name.length === 0) {
alert('Name muss ausgefüllt sein');
return;
} }
if (image) { /**
const reader = new FileReader(); * Register events for the theme switch button.
reader.onloadend = function () { */
const imageBase64 = reader.result; registerThemeSwitchEvent() {
const imageSrcWithBase64 = `data:${image.type};base64,${imageBase64.split(',')[1]}`; document.querySelectorAll('[data-toggle-bs-theme]').forEach(element => {
productList.addProduct(new Product(name, fieldPrice, imageSrcWithBase64)); element.addEventListener('click', event => {
document.querySelector('#product-name').value = ''; const theme = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
document.querySelector('#product-price').value = '0,00'; this.setBootstrapTheme(theme);
document.querySelector('#product-image').value = ''; })
};
reader.readAsDataURL(image);
} else {
productList.addProduct(new Product(name, fieldPrice, `https://placehold.co/250x250?text={${name}}`));
document.querySelector('#product-name').value = '';
document.querySelector('#product-price').value = '';
document.querySelector('#product-image').value = '';
}
}); });
}
/**
* Set the bootstrap theme by setting the data-bs-theme attribute on the html element and saving it to local storage.
* @param theme {string} The theme to set (light or dark)
*/
setBootstrapTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('bootstrap-theme', theme);
}
}
/**
* Main function to initialize the application.
*/
const cartManager = new CartManager();
const productManager = new ProductManager();
const tabManager = new TabManager();
const themeManager = new ThemeManager();

View file

@ -10,7 +10,6 @@ const FILES_TO_CACHE = [
`${BASE_PATH}/assets/style/stylesheet.css`, `${BASE_PATH}/assets/style/stylesheet.css`,
`${BASE_PATH}/assets/script/all.js`, `${BASE_PATH}/assets/script/all.js`,
`${BASE_PATH}/assets/script/calculator.js`, `${BASE_PATH}/assets/script/calculator.js`,
`${BASE_PATH}/assets/script/theme.js`,
`${BASE_PATH}/node_modules/bootstrap/dist/css/bootstrap.min.css`, `${BASE_PATH}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
`${BASE_PATH}/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js`, `${BASE_PATH}/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js`,
`${BASE_PATH}/node_modules/bootstrap-icons/font/bootstrap-icons.min.css`, `${BASE_PATH}/node_modules/bootstrap-icons/font/bootstrap-icons.min.css`,

View file

@ -1,15 +0,0 @@
function setBootstrapTheme(theme) {
document.documentElement.setAttribute('data-bs-theme', theme);
localStorage.setItem('bootstrap-theme', theme);
}
setBootstrapTheme(localStorage.getItem('bootstrap-theme') || 'light');
document.querySelectorAll('[data-toggle-bs-theme]').forEach(element => {
element.addEventListener('click', event => {
const theme = document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'light' : 'dark';
setBootstrapTheme(theme);
})
});

View file

@ -2,7 +2,7 @@
<html lang="de" data-bs-theme="dark"> <html lang="de" data-bs-theme="dark">
<head> <head>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<meta name="theme-color" content="#2196f3"/> <meta name="theme-color" content="#2196f3"/>
<link rel="manifest" href="assets/manifest.json"> <link rel="manifest" href="assets/manifest.json">
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
@ -18,15 +18,15 @@
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Der Durstrechner</a> <a class="navbar-brand" href="#">Der Durstrechner</a>
<form class="d-flex" role="search" method="dialog"> <form class="d-flex" role="search" method="dialog">
<button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> Tag / Nacht</button> <button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> <span class="d-none d-md-inline-block">Tag / Nacht</span></button>
<button class="btn" data-toggle-tab><i class="bi bi-gear"></i> Einstellungen</button> <button class="btn" data-toggle-tab><i class="bi bi-gear"></i> <span class="d-none d-md-inline-block">Einstellungen</span></button>
</form> </form>
</div> </div>
</nav> </nav>
<main class="container-fluid flex-grow-1"> <main class="container-fluid flex-grow-1">
<div data-tab="products" class="row"> <div data-tab="products" class="row">
<div class="col-12 col-md-4"> <div class="col-12 col-md-4 products">
<li class="list-group-item d-flex justify-content-between" data-template="product-line-item"> <li class="list-group-item d-flex justify-content-between" data-template="product-line-item">
<span class="line-item-details"> <span class="line-item-details">
<span class="amount quantity-value" data-attr="quantity">5</span> <span class="amount quantity-value" data-attr="quantity">5</span>
@ -43,10 +43,10 @@
</button> </button>
</div> </div>
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<div class="row row-cols-4 row-gap-3 product-list h-100 overflow-auto"> <div class="row row-cols-2 row-cols-md-5 row-gap-3 product-list h-100 overflow-auto">
<div class="col product-box" data-template="product" data-id="0"> <div class="col product-box" data-template="product" data-id="0">
<div class="card"> <div class="card">
<img data-attr="image" src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top" loading="lazy"> <img data-attr="image" src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top" loading="eager">
<div class="card-body"> <div class="card-body">
<span data-attr="name">Wasser mit Kohlensäure</span> <span data-attr="name">Wasser mit Kohlensäure</span>
</div> </div>
@ -56,9 +56,8 @@
</div> </div>
</div> </div>
<div data-tab="settings" class="row d-none"> <div data-tab="settings" class="row d-none">
<div class="row"> <div class="col-12 col-md-6">
<div class="col"> <form class="card mb-2" method="dialog" id="new-product">
<form class="card mb-3" method="dialog" id="new-product">
<h5 class="card-header">Neues Produkt</h5> <h5 class="card-header">Neues Produkt</h5>
<div class="card-body"> <div class="card-body">
<div class="mb-2"> <div class="mb-2">
@ -67,7 +66,10 @@
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label for="product-price" class="form-label">Preis</label> <label for="product-price" class="form-label">Preis</label>
<input type="number" class="form-control" step="0.01" value="0.00" id="product-price" required> <div class="input-group">
<span class="input-group-text" id="basic-addon1"></span>
<input type="number" class="form-control" step="0.01" value="" id="product-price" required>
</div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label for="product-image" class="form-label">Bild auswählen</label> <label for="product-image" class="form-label">Bild auswählen</label>
@ -87,10 +89,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col"> <div class="col-12 col-md-6 mt-2 mt-md-0">
<div class="card"> <div class="card">
<h5 class="card-header">Produkte entfernen</h5> <h5 class="card-header">Produkte entfernen</h5>
<div class="card-body"> <div class="card-body">
<p class="mb-2">Hier können Produkte entfernt werden, die nicht mehr benötigt werden.</p>
<li class="list-group-item" data-template="settings-product">Test</li> <li class="list-group-item" data-template="settings-product">Test</li>
<ul class="list-group settings-product-list"> <ul class="list-group settings-product-list">
</ul> </ul>
@ -98,7 +101,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</main> </main>
<script src="assets/script/all.js" type="module"></script> <script src="assets/script/all.js" type="module"></script>