OTC-Overlay im Home

This commit is contained in:
2026-06-15 13:29:20 +02:00
parent 457fa0316f
commit 69a9420c03
4 changed files with 360 additions and 3 deletions
+270 -2
View File
@@ -3,8 +3,25 @@ declare(strict_types=1);
require_once __DIR__ . '/../service.php';
function render_auth_home_page(array $user): void
function render_auth_home_page(array $user, array $otcProducts = []): void
{
$otcProductRows = '';
foreach ($otcProducts as $product) {
$productId = (int) ($product['id'] ?? 0);
$productName = auth_escape_html((string) ($product['name'] ?? ''));
$availableQty = (int) max(0, (int) round((float) ($product['available_qty'] ?? 0)));
$inputId = 'otc-product-' . $productId;
$otcProductRows .= '<div class="sg-form-sections-card__field-group">';
$otcProductRows .= '<label class="sg-label" for="' . $inputId . '">' . $productName . '</label>';
$otcProductRows .= '<input class="sg-interaction-element sg-input-single-line" type="number" id="' . $inputId . '" min="0" max="' . $availableQty . '" step="1" value="0" data-otc-order-product data-product-id="' . $productId . '" data-title="' . $productName . '" data-available-qty="' . $availableQty . '">';
$otcProductRows .= '</div>';
}
if ($otcProductRows === '') {
$otcProductRows = '<p class="sg-body sg-form-sections-card__sentence">Keine verfügbaren Produkte im Lager.</p>';
}
$moduleNavigation = json_encode(
[
'Übersicht' => [],
@@ -16,7 +33,7 @@ function render_auth_home_page(array $user): void
);
$moduleContentCards = json_encode(
[
'Bestellungen' => '<article class="sg-card" data-component="basic-card"><div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body"><div class="sg-component-row sg-basic-card__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" data-component="button" data-component-state="active">OTC-Bestellung erfassen</button></div></div></article>',
'Bestellungen' => '<article class="sg-card" data-component="basic-card"><div class="sg-card-segment sg-card-segment--body sg-card-segment--gray" data-component-part="card-body"><div class="sg-component-row sg-basic-card__actions"><button class="sg-interaction-element sg-button sg-button--active" type="button" aria-haspopup="dialog" aria-expanded="false" data-component="button" data-component-state="active" data-otc-order-open>OTC-Bestellung erfassen</button></div></div></article>',
],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);
@@ -100,6 +117,82 @@ function render_auth_home_page(array $user): void
echo '</div>';
echo '</section>';
echo '</section>';
echo '<section class="sg-otc-order-overlay" data-otc-order-overlay data-open="false" aria-hidden="true">';
echo '<article class="sg-card sg-object-card sg-object-card--variable-height sg-otc-order-overlay__panel" data-pattern="object-card" aria-labelledby="otc-order-modal-title" role="dialog" aria-modal="true">';
echo '<header class="sg-card-segment sg-card-segment--header sg-card-segment--darkblue sg-object-card__header">';
echo '<h2 id="otc-order-modal-title" class="sg-strong sg-otc-order-overlay__title">Neuen OTC-Verkauf erfassen</h2>';
echo '<button class="sg-interaction-element sg-button sg-button--active sg-otc-order-overlay__close" type="button" data-otc-order-close>Schliessen</button>';
echo '</header>';
echo '<div class="sg-card-segment sg-card-segment--gray sg-object-card__content">';
echo '<div class="sg-form-sections-card-wrapper" data-pattern="form-sections" aria-label="Formular mit Abschnitten">';
echo '<form class="sg-form-sections-card" action="#" method="post" data-otc-order-form aria-label="Neuen OTC-Verkauf erfassen">';
echo '<div class="sg-form-sections-card__body" data-pattern-part="form-body">';
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-products-title">';
echo '<h2 id="otc-products-title" class="sg-strong sg-form-sections-card__chapter-title">PRODUKTE</h2>';
echo $otcProductRows;
echo '</section>';
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-total-price-title">';
echo '<h2 id="otc-total-price-title" class="sg-strong sg-form-sections-card__chapter-title">Preis</h2>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-total-price">Preis alle Flaschen brutto (CHF)</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="number" id="otc-total-price" min="0" step="0.01" value="0.00" data-otc-order-total-price>';
echo '<p class="sg-body sg-form-sections-card__sentence">Der Preis wird durch die Anzahl aller Flaschen geteilt und das Ergebnis ist der Preis jeder einzelnen Flasche.</p>';
echo '</div>';
echo '</section>';
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-payment-title">';
echo '<h2 id="otc-payment-title" class="sg-strong sg-form-sections-card__chapter-title">Bezahlung</h2>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-payment-method">Bezahlung</label>';
echo '<select class="sg-interaction-element sg-input-single-line" id="otc-payment-method" data-otc-order-payment-method>';
echo '<option value="twint">Twint</option>';
echo '<option value="cash">Barzahlung</option>';
echo '<option value="paypal">PayPal</option>';
echo '<option value="bank_transfer">Überweisung</option>';
echo '</select>';
echo '</div>';
echo '</section>';
echo '<section class="sg-form-sections-card__chapter" aria-labelledby="otc-billing-title">';
echo '<h2 id="otc-billing-title" class="sg-strong sg-form-sections-card__chapter-title">Rechnungsadresse</h2>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-first-name">Vorname</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-first-name" value="Fabienne" data-otc-order-first-name>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-last-name">Nachname</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-last-name" value="Föhn" data-otc-order-last-name>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-street">Strasse</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-street" value="Im Hochrain" data-otc-order-street>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-house-number">Hausnummer</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-house-number" value="2" data-otc-order-house-number>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-zip">PLZ</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-zip" value="8102" data-otc-order-zip>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<label class="sg-label" for="otc-city">Ort</label>';
echo '<input class="sg-interaction-element sg-input-single-line" type="text" id="otc-city" value="Oberengstringen" data-otc-order-city>';
echo '</div>';
echo '<div class="sg-form-sections-card__field-group">';
echo '<div class="sg-body sg-otc-order-form__status hidden" data-otc-order-success>Bestellung erfolgreich erfasst! Die Bestellnummer wird automatisch generiert.</div>';
echo '<div class="sg-body sg-otc-order-form__status sg-otc-order-form__status--error hidden" data-otc-order-error></div>';
echo '</div>';
echo '</div>';
echo '<footer class="sg-form-sections-card__actions-segment" data-pattern-part="form-actions-segment">';
echo '<div class="sg-form-sections-card__actions" data-pattern-part="form-actions">';
echo '<button class="sg-interaction-element sg-button sg-button--active sg-form-sections-card__action" type="button" data-otc-order-close>Abbrechen</button>';
echo '<button class="sg-interaction-element sg-button sg-button--process sg-button--process-inactive sg-form-sections-card__action" type="submit" disabled aria-disabled="true" data-otc-order-submit>Verkaufen</button>';
echo '</div>';
echo '</footer>';
echo '</form>';
echo '</div>';
echo '</div>';
echo '</article>';
echo '</section>';
echo '<script>';
echo 'const portalModuleNavigation = ' . $moduleNavigation . ';';
echo 'const portalModuleContentCards = ' . $moduleContentCards . ';';
@@ -205,6 +298,181 @@ function render_auth_home_page(array $user): void
echo " syncMode();";
echo " mediaQuery.addEventListener('change', syncMode);";
echo "})();";
echo "(() => {";
echo " const overlay = document.querySelector('[data-otc-order-overlay]');";
echo " if (!overlay) { return; }";
echo " const form = overlay.querySelector('[data-otc-order-form]');";
echo " const productInputs = Array.from(form.querySelectorAll('[data-otc-order-product]'));";
echo " const totalPriceInput = form.querySelector('[data-otc-order-total-price]');";
echo " const paymentMethodInput = form.querySelector('[data-otc-order-payment-method]');";
echo " const firstNameInput = form.querySelector('[data-otc-order-first-name]');";
echo " const lastNameInput = form.querySelector('[data-otc-order-last-name]');";
echo " const streetInput = form.querySelector('[data-otc-order-street]');";
echo " const houseNumberInput = form.querySelector('[data-otc-order-house-number]');";
echo " const zipInput = form.querySelector('[data-otc-order-zip]');";
echo " const cityInput = form.querySelector('[data-otc-order-city]');";
echo " const submitBtn = form.querySelector('[data-otc-order-submit]');";
echo " const errorEl = form.querySelector('[data-otc-order-error]');";
echo " const successEl = form.querySelector('[data-otc-order-success]');";
echo " const successDefaultText = successEl ? successEl.textContent : '';";
echo " const openTriggers = () => document.querySelectorAll('[data-otc-order-open]');";
echo " const setTriggerState = (expanded) => {";
echo " openTriggers().forEach((trigger) => { trigger.setAttribute('aria-expanded', String(expanded)); });";
echo " };";
echo " const clearError = () => {";
echo " if (errorEl) { errorEl.textContent = ''; errorEl.classList.add('hidden'); }";
echo " };";
echo " const clearSuccess = () => {";
echo " if (successEl) { successEl.textContent = successDefaultText; successEl.classList.add('hidden'); }";
echo " };";
echo " const toggleOverlay = (isOpen) => {";
echo " overlay.dataset.open = String(isOpen);";
echo " overlay.setAttribute('aria-hidden', String(!isOpen));";
echo " document.body.classList.toggle('sg-otc-order-overlay-open', isOpen);";
echo " setTriggerState(isOpen);";
echo " if (isOpen) {";
echo " clearError();";
echo " clearSuccess();";
echo " requestAnimationFrame(() => {";
echo " (productInputs[0] || totalPriceInput || submitBtn)?.focus();";
echo " });";
echo " return;";
echo " }";
echo " clearError();";
echo " clearSuccess();";
echo " };";
echo " const getInputValue = (input) => parseInt(input.value || '0', 10) || 0;";
echo " const getFieldValue = (input) => (input ? input.value.trim() : '');";
echo " const updateFormState = () => {";
echo " const totalQty = productInputs.reduce((sum, input) => sum + getInputValue(input), 0);";
echo " const totalPrice = parseFloat(totalPriceInput.value || '0') || 0;";
echo " const overstockInput = productInputs.find((input) => {";
echo " const maxQty = parseInt(input.max || '0', 10);";
echo " return Number.isFinite(maxQty) && maxQty >= 0 && getInputValue(input) > maxQty;";
echo " });";
echo " const isValid = totalQty > 0";
echo " && totalPrice > 0";
echo " && !!getFieldValue(paymentMethodInput)";
echo " && !!getFieldValue(firstNameInput)";
echo " && !!getFieldValue(lastNameInput)";
echo " && !!getFieldValue(streetInput)";
echo " && !!getFieldValue(houseNumberInput)";
echo " && !!getFieldValue(zipInput)";
echo " && !!getFieldValue(cityInput)";
echo " && overstockInput === undefined;";
echo " let errorMsg = '';";
echo " if (overstockInput) {";
echo " errorMsg = 'Menge für ' + overstockInput.dataset.title + ' überschreitet den Lagerbestand.';";
echo " } else if (totalQty === 0) {";
echo " errorMsg = 'Mindestens ein Produkt mit Menge > 0 erforderlich.';";
echo " } else if (totalPrice <= 0) {";
echo " errorMsg = 'Preis muss größer als 0 sein.';";
echo " } else if (!getFieldValue(paymentMethodInput)) {";
echo " errorMsg = 'Zahlungsart auswählen.';";
echo " } else if (!getFieldValue(firstNameInput) || !getFieldValue(lastNameInput) || !getFieldValue(streetInput) || !getFieldValue(houseNumberInput) || !getFieldValue(zipInput) || !getFieldValue(cityInput)) {";
echo " errorMsg = 'Alle Rechnungsadress-Felder ausfüllen.';";
echo " }";
echo " if (errorEl) {";
echo " errorEl.textContent = errorMsg;";
echo " errorEl.classList.toggle('hidden', errorMsg === '');";
echo " }";
echo " submitBtn.disabled = !isValid;";
echo " submitBtn.setAttribute('aria-disabled', String(!isValid));";
echo " submitBtn.classList.toggle('sg-button--process-inactive', !isValid);";
echo " return isValid;";
echo " };";
echo " const resetForm = (preserveSuccess = false) => {";
echo " form.reset();";
echo " productInputs.forEach((input) => { input.value = '0'; });";
echo " if (totalPriceInput) { totalPriceInput.value = '0.00'; }";
echo " clearError();";
echo " if (!preserveSuccess) { clearSuccess(); }";
echo " updateFormState();";
echo " };";
echo " document.addEventListener('click', (event) => {";
echo " const openTrigger = event.target.closest('[data-otc-order-open]');";
echo " if (openTrigger) {";
echo " event.preventDefault();";
echo " toggleOverlay(true);";
echo " return;";
echo " }";
echo " const closeTrigger = event.target.closest('[data-otc-order-close]');";
echo " if (closeTrigger) {";
echo " event.preventDefault();";
echo " toggleOverlay(false);";
echo " }";
echo " });";
echo " overlay.addEventListener('click', (event) => {";
echo " if (event.target === overlay) {";
echo " toggleOverlay(false);";
echo " }";
echo " });";
echo " document.addEventListener('keydown', (event) => {";
echo " if (event.key === 'Escape' && overlay.dataset.open === 'true') {";
echo " toggleOverlay(false);";
echo " }";
echo " });";
echo " [totalPriceInput, paymentMethodInput, firstNameInput, lastNameInput, streetInput, houseNumberInput, zipInput, cityInput].forEach((input) => {";
echo " if (!input) { return; }";
echo " input.addEventListener('input', updateFormState);";
echo " input.addEventListener('change', updateFormState);";
echo " });";
echo " productInputs.forEach((input) => {";
echo " input.addEventListener('input', updateFormState);";
echo " input.addEventListener('change', updateFormState);";
echo " });";
echo " form.addEventListener('submit', async (event) => {";
echo " event.preventDefault();";
echo " if (!updateFormState()) { return; }";
echo " const products = productInputs";
echo " .filter((input) => getInputValue(input) > 0)";
echo " .map((input) => ({ title: input.dataset.title, qty: getInputValue(input), productId: parseInt(input.dataset.productId || '0', 10) }));";
echo " const orderData = {";
echo " products: products,";
echo " totalPrice: parseFloat(totalPriceInput.value || '0') || 0,";
echo " paymentMethod: paymentMethodInput ? paymentMethodInput.value : '',";
echo " billing: {";
echo " firstName: getFieldValue(firstNameInput),";
echo " lastName: getFieldValue(lastNameInput),";
echo " street: getFieldValue(streetInput),";
echo " houseNumber: getFieldValue(houseNumberInput),";
echo " zip: getFieldValue(zipInput),";
echo " city: getFieldValue(cityInput)";
echo " }";
echo " };";
echo " const originalBtnText = submitBtn.innerHTML;";
echo " submitBtn.innerHTML = '<span class=\"loading\"></span> Wird verarbeitet...';";
echo " submitBtn.disabled = true;";
echo " clearError();";
echo " try {";
echo " const response = await fetch('/public/api/otc-order.php', {";
echo " method: 'POST',";
echo " headers: { 'Content-Type': 'application/json' },";
echo " body: JSON.stringify(orderData)";
echo " });";
echo " const result = await response.json();";
echo " if (response.ok && result.ok) {";
echo " if (successEl) {";
echo " successEl.textContent = result.externalRef ? 'Bestellung erfolgreich erfasst! Bestellnummer: ' + result.externalRef : successDefaultText;";
echo " successEl.classList.remove('hidden');";
echo " }";
echo " resetForm(true);";
echo " } else if (errorEl) {";
echo " errorEl.textContent = result.error || 'Unbekannter Fehler';";
echo " errorEl.classList.remove('hidden');";
echo " }";
echo " } catch (error) {";
echo " if (errorEl) {";
echo " errorEl.textContent = 'Netzwerkfehler: ' + error.message;";
echo " errorEl.classList.remove('hidden');";
echo " }";
echo " } finally {";
echo " submitBtn.innerHTML = originalBtnText;";
echo " updateFormState();";
echo " }";
echo " });";
echo " updateFormState();";
echo "})();";
echo "renderMainHeading('ERP');";
echo "renderLeftNavigation('ERP');";
echo '</script>';