Feature: Massenbearbeitung für Lagerorte, QR-Codes für Kisten, Tisch-Leeren Button und Fixes
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 39s
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 39s
This commit is contained in:
@@ -213,6 +213,9 @@ Das Projekt basiert auf bewährten Web-Standards:
|
||||
* Neuer Menüpunkt zur Verwaltung (Bearbeiten, Löschen, Hinzufügen) von Kategorien analog zu den Herstellern implementiert.
|
||||
* Kategorien können nun mit individuellen Farben (Hex-Code per Color-Picker) versehen werden. Diese Farben werden in der Detailansicht von Packlisten sowie in der Kachelansicht von Artikeln als farbige Badges dargestellt.
|
||||
* Das Tortendiagramm auf dem Dashboard (`index.php`) nutzt nun ebenfalls die individuellen Kategorie-Farben.
|
||||
* Dashboard (`index.php`) um zusätzliche Haushalts-Statistiken erweitert (Gesamtzahl Artikel, Packlisten, Vorlagen, Rucksäcke).
|
||||
* Dashboard (`index.php`) um zusätzliche Haushalts-Statistiken erweitert (Gesamtzahl Artikel, Packlisten, Vorlagen, Rucksäcke). Packlistenvorlagen werden aus dem Gewicht-Diagramm ausgeschlossen.
|
||||
* Kachelhöhe in der Artikel-Übersicht und im Packlisten-Editor (`manage_packing_list_items.php`) leicht angepasst. Hover-Bilder in Kacheln entfernt und leere Felder symmetrisch ausgerichtet.
|
||||
* Anzeigename wird nun auch auf dem Dashboard (`index.php`) und in der Kategorienverwaltung korrekt angezeigt.
|
||||
* Massenbearbeitung (Bulk Actions): In der Artikel-Listenansicht können nun mehrere Artikel über Checkboxen ausgewählt und gleichzeitig in einen neuen Lagerort verschoben werden. Die Auswahl bleibt auch bei Filteränderungen bestehen.
|
||||
* Barcode für Lagerorte: In der Lagerort-Verwaltung kann für jeden Lagerort ein QR-Code generiert und angezeigt werden. Dieser führt zu einer für Smartphones optimierten, öffentlichen Ansicht des Kisteninhalts (ohne Login nutzbar).
|
||||
* Packlisten-Editor (`manage_packing_list_items.php`): Ein neuer Button "Tisch leeren" ermöglicht es, alle aktuell auf dem virtuellen Tisch liegenden Artikel mit einem Klick in die Lagerorte zurückzuräumen.
|
||||
|
||||
131
src/articles.php
131
src/articles.php
@@ -121,6 +121,49 @@ $stmt_man_load->execute();
|
||||
$manufacturers_for_filter = $stmt_man_load->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_man_load->close();
|
||||
|
||||
$stmt_loc_load = $conn->prepare("SELECT id, name FROM storage_locations WHERE user_id IN ($placeholders) OR household_id = ? ORDER BY name ASC");
|
||||
$stmt_loc_load->bind_param($all_types, ...$all_params);
|
||||
$stmt_loc_load->execute();
|
||||
$storage_locations_for_bulk = $stmt_loc_load->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_loc_load->close();
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['bulk_move_articles'])) {
|
||||
$new_location_id = intval($_POST['bulk_new_location']);
|
||||
$article_ids_raw = $_POST['bulk_article_ids'] ?? '';
|
||||
if (!empty($article_ids_raw) && $new_location_id > 0) {
|
||||
$article_ids = array_map('intval', explode(',', $article_ids_raw));
|
||||
$article_ids = array_filter($article_ids, fn($id) => $id > 0);
|
||||
if (count($article_ids) > 0) {
|
||||
$id_placeholders = implode(',', array_fill(0, count($article_ids), '?'));
|
||||
$types = str_repeat('i', count($article_ids));
|
||||
$update_sql = "UPDATE articles SET storage_location_id = ? WHERE id IN ($id_placeholders) AND (user_id = ? OR household_id = ?)";
|
||||
$stmt_update = $conn->prepare($update_sql);
|
||||
$params = array_merge([$new_location_id], $article_ids, [$current_user_id, $current_user_household_id]);
|
||||
$param_types = 'i' . $types . 'ii';
|
||||
$stmt_update->bind_param($param_types, ...$params);
|
||||
if ($stmt_update->execute()) {
|
||||
// To display the message, we don't redirect but let it render, or we redirect with a session message.
|
||||
// Since there is no session message system, we just let the page render with the message.
|
||||
$message = '<div class="alert alert-success" role="alert">' . count($article_ids) . ' Artikel erfolgreich verschoben.</div>';
|
||||
|
||||
// Need to reload articles so the view is updated
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param($all_types, ...$all_params);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$articles = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$articles[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
} else {
|
||||
$message = '<div class="alert alert-danger" role="alert">Fehler beim Verschieben: ' . $stmt_update->error . '</div>';
|
||||
}
|
||||
$stmt_update->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
?>
|
||||
|
||||
@@ -151,12 +194,19 @@ $conn->close();
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2" id="bulk-action-container" style="display: none;">
|
||||
<div class="col-12 d-flex align-items-center bg-white border rounded p-2 shadow-sm">
|
||||
<span class="me-3 fw-bold"><span id="bulk-selected-count">0</span> Artikel markiert</span>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#bulkMoveModal"><i class="fas fa-truck-moving me-2"></i>Markierte Artikel verschieben</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-container-list" class="table-responsive" style="max-height: 75vh;">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;"><input type="checkbox" id="bulk-select-all" class="form-check-input" title="Alle sichtbaren auswählen"></th>
|
||||
<th style="width: 60px;">Bild</th>
|
||||
<th style="width: 40px;"><i class="fas fa-link"></i></th>
|
||||
<th>Name</th>
|
||||
@@ -203,6 +253,33 @@ $conn->close();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="bulkMoveModal" tabindex="-1" aria-labelledby="bulkMoveModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="bulkMoveModalLabel"><i class="fas fa-truck-moving me-2"></i>Artikel verschieben</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="articles.php" method="POST">
|
||||
<div class="modal-body">
|
||||
<p>In welchen Lagerort sollen die ausgewählten Artikel verschoben werden?</p>
|
||||
<input type="hidden" name="bulk_article_ids" id="bulk_article_ids_input" value="">
|
||||
<select name="bulk_new_location" class="form-select" required>
|
||||
<option value="">-- Lagerort auswählen --</option>
|
||||
<?php foreach($storage_locations_for_bulk as $loc): ?>
|
||||
<option value="<?php echo $loc['id']; ?>"><?php echo htmlspecialchars($loc['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" name="bulk_move_articles" class="btn btn-primary">Verschieben</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
@@ -302,6 +379,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('view-container-list').style.display = '';
|
||||
document.getElementById('view-container-grid').style.display = 'none';
|
||||
renderTable();
|
||||
updateBulkUI();
|
||||
});
|
||||
document.getElementById('btn-view-grid').addEventListener('click', function() {
|
||||
currentView = 'grid';
|
||||
@@ -310,6 +388,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('view-container-list').style.display = 'none';
|
||||
document.getElementById('view-container-grid').style.display = '';
|
||||
renderTable();
|
||||
updateBulkUI();
|
||||
});
|
||||
|
||||
function renderTable() {
|
||||
@@ -370,7 +449,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const count = items.reduce((acc, item) => acc + 1 + item.children.length, 0);
|
||||
|
||||
tableHTML += `<tr class="category-header ${isCollapsed ? 'collapsed' : ''}" data-category="${catName}" style="cursor: pointer;">
|
||||
<td colspan="12">
|
||||
<td colspan="13">
|
||||
<i class="fas fa-chevron-${isCollapsed ? 'right' : 'down'} me-2"></i> ${catName}
|
||||
<span class="badge bg-light text-dark rounded-pill ms-2">${count}</span>
|
||||
</td>
|
||||
@@ -464,8 +543,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const indentStyle = level > 0 ? `padding-left: ${1.5 * level}rem;` : '';
|
||||
const treePrefix = level > 0 ? `<span class="tree-line" style="left: ${0.5 * level}rem;"></span>` : '';
|
||||
const nameCellContent = `<div class="tree-view-item level-${level}" style="position:relative;">${treePrefix}<span class="item-name-text">${article.name}</span></div>`;
|
||||
const checkboxHtml = (isOwner || isHouseholdArticle) ? `<input type="checkbox" class="form-check-input bulk-select-checkbox" value="${article.id}">` : '';
|
||||
|
||||
html += `<tr>
|
||||
<td class="text-center">${checkboxHtml}</td>
|
||||
<td><img src="${imagePath}" alt="${article.name}" style="width: 40px; height: 40px; object-fit: cover; border-radius: 5px;" class="article-image-trigger" data-preview-url="${imagePath}"></td>
|
||||
<td class="text-center">${productLink}</td>
|
||||
<td>${nameCellContent}</td>
|
||||
@@ -561,4 +642,52 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>tAll.checked = false;
|
||||
newSelectAll.addEventListener('change', function() {
|
||||
const isChecked = this.checked;
|
||||
document.querySelectorAll('.bulk-select-checkbox').forEach(cb => {
|
||||
cb.checked = isChecked;
|
||||
if (isChecked) bulkSelectedArticles.add(cb.value);
|
||||
else bulkSelectedArticles.delete(cb.value);
|
||||
});
|
||||
updateBulkUI();
|
||||
});
|
||||
}
|
||||
updateBulkUI();
|
||||
}
|
||||
|
||||
const bulkSelectedArticles = new Set();
|
||||
function updateBulkUI() {
|
||||
const count = bulkSelectedArticles.size;
|
||||
document.getElementById('bulk-selected-count').textContent = count;
|
||||
const container = document.getElementById('bulk-action-container');
|
||||
if (count > 0 && currentView === 'list') {
|
||||
container.style.display = '';
|
||||
const idsInput = document.getElementById('bulk_article_ids_input');
|
||||
if(idsInput) idsInput.value = Array.from(bulkSelectedArticles).join(',');
|
||||
} else {
|
||||
container.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
filterText.addEventListener('input', renderTable);
|
||||
filterCategory.addEventListener('change', renderTable);
|
||||
filterManufacturer.addEventListener('change', renderTable);
|
||||
|
||||
document.getElementById('btn-expand-all').addEventListener('click', function() {
|
||||
collapsedCategories.clear();
|
||||
renderTable();
|
||||
});
|
||||
|
||||
document.getElementById('btn-collapse-all').addEventListener('click', function() {
|
||||
// Find all currently rendered categories
|
||||
const visibleCategories = Array.from(document.querySelectorAll('.category-header')).map(el => el.getAttribute('data-category'));
|
||||
visibleCategories.forEach(c => collapsedCategories.add(c));
|
||||
renderTable();
|
||||
});
|
||||
|
||||
renderTable();
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
@@ -62,4 +62,10 @@ $check_category_color = $conn->query("SHOW COLUMNS FROM categories LIKE 'color'"
|
||||
if ($check_category_color && $check_category_color->num_rows == 0) {
|
||||
$conn->query("ALTER TABLE categories ADD COLUMN color VARCHAR(7) DEFAULT '#e2e8f0'");
|
||||
}
|
||||
|
||||
// Ensure public_token exists in storage_locations
|
||||
$check_loc_token = $conn->query("SHOW COLUMNS FROM storage_locations LIKE 'public_token'");
|
||||
if ($check_loc_token && $check_loc_token->num_rows == 0) {
|
||||
$conn->query("ALTER TABLE storage_locations ADD COLUMN public_token VARCHAR(64) UNIQUE DEFAULT NULL");
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($stmt_articles_by_category) {
|
||||
}
|
||||
|
||||
$packing_lists_stats = [];
|
||||
$sql_lists = "SELECT pl.id, pl.name, SUM(pli.quantity * a.weight_grams) AS total_weight FROM packing_lists pl LEFT JOIN packing_list_items pli ON pl.id = pli.packing_list_id LEFT JOIN articles a ON pli.article_id = a.id WHERE pl.user_id IN ($placeholders) OR pl.household_id = ? GROUP BY pl.id, pl.name ORDER BY total_weight DESC";
|
||||
$sql_lists = "SELECT pl.id, pl.name, SUM(pli.quantity * a.weight_grams) AS total_weight FROM packing_lists pl LEFT JOIN packing_list_items pli ON pl.id = pli.packing_list_id LEFT JOIN articles a ON pli.article_id = a.id WHERE pl.is_template = 0 AND (pl.user_id IN ($placeholders) OR pl.household_id = ?) GROUP BY pl.id, pl.name ORDER BY total_weight DESC";
|
||||
$stmt_lists = $conn->prepare($sql_lists);
|
||||
if ($stmt_lists) {
|
||||
$all_params = array_merge($household_member_ids, [$current_user_household_id]);
|
||||
|
||||
@@ -38,6 +38,17 @@ if ($is_owner || $is_in_same_household) { $can_edit = true; }
|
||||
if (!$can_edit) { die("Zugriff verweigert."); }
|
||||
|
||||
$phase = isset($_GET['phase']) ? intval($_GET['phase']) : 0;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['empty_table'])) {
|
||||
// Delete items that are "on the table" (not assigned to any backpack or carrier)
|
||||
$stmt_del = $conn->prepare("DELETE FROM packing_list_items WHERE packing_list_id = ? AND carrier_user_id IS NULL AND backpack_id IS NULL AND backpack_compartment_id IS NULL");
|
||||
$stmt_del->bind_param("i", $packing_list_id);
|
||||
$stmt_del->execute();
|
||||
$stmt_del->close();
|
||||
header("Location: manage_packing_list_items.php?id=" . $packing_list_id . "&phase=" . $phase);
|
||||
exit;
|
||||
}
|
||||
|
||||
$col_class_lager = ($phase == 1) ? 'col-phase1-lager' : (($phase == 2) ? 'd-none' : 'col-lg-4');
|
||||
$col_class_table = ($phase == 1) ? 'col-phase1-table' : (($phase == 2) ? 'col-lg-6' : 'col-lg-4');
|
||||
$col_class_rucksack = ($phase == 2) ? 'col-lg-6' : (($phase == 1) ? 'd-none' : 'col-lg-4');
|
||||
|
||||
130
src/public_location.php
Normal file
130
src/public_location.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
// public_location.php - Öffentliche Ansicht für einen Lagerort per Token (QR-Code)
|
||||
|
||||
require_once 'db_connect.php';
|
||||
|
||||
$token = isset($_GET['token']) ? trim($_GET['token']) : '';
|
||||
|
||||
if (empty($token)) {
|
||||
die("Ungültiger Link.");
|
||||
}
|
||||
|
||||
$stmt_loc = $conn->prepare("SELECT id, name, parent_id FROM storage_locations WHERE public_token = ?");
|
||||
$stmt_loc->bind_param("s", $token);
|
||||
$stmt_loc->execute();
|
||||
$result_loc = $stmt_loc->get_result();
|
||||
|
||||
if ($result_loc->num_rows === 0) {
|
||||
die("Lagerort nicht gefunden oder Link abgelaufen.");
|
||||
}
|
||||
|
||||
$location = $result_loc->fetch_assoc();
|
||||
$stmt_loc->close();
|
||||
$location_id = $location['id'];
|
||||
|
||||
// Lade ggf. die Ebene 1, falls dies Ebene 2 ist
|
||||
$parent_name = '';
|
||||
if ($location['parent_id']) {
|
||||
$stmt_parent = $conn->prepare("SELECT name FROM storage_locations WHERE id = ?");
|
||||
$stmt_parent->bind_param("i", $location['parent_id']);
|
||||
$stmt_parent->execute();
|
||||
$res_parent = $stmt_parent->get_result();
|
||||
if ($res_parent->num_rows > 0) {
|
||||
$parent_name = $res_parent->fetch_assoc()['name'];
|
||||
}
|
||||
$stmt_parent->close();
|
||||
}
|
||||
|
||||
$location_display_name = htmlspecialchars($location['name']);
|
||||
if (!empty($parent_name)) {
|
||||
$location_display_name = htmlspecialchars($parent_name) . ' <i class="fas fa-chevron-right fa-xs mx-1"></i> ' . $location_display_name;
|
||||
}
|
||||
|
||||
// Lade Artikel in diesem Lagerort (und in Unterorten, falls Ebene 1)
|
||||
$sql_articles = "SELECT a.name, a.weight_grams, a.quantity_owned, a.image_url, a.product_designation, m.name as manufacturer_name, c.name as category_name, c.color as category_color
|
||||
FROM articles a
|
||||
LEFT JOIN manufacturers m ON a.manufacturer_id = m.id
|
||||
LEFT JOIN categories c ON a.category_id = c.id
|
||||
LEFT JOIN storage_locations l ON a.storage_location_id = l.id
|
||||
WHERE a.storage_location_id = ? OR l.parent_id = ?
|
||||
ORDER BY c.name ASC, a.name ASC";
|
||||
$stmt_articles = $conn->prepare($sql_articles);
|
||||
$stmt_articles->bind_param("ii", $location_id, $location_id);
|
||||
$stmt_articles->execute();
|
||||
$result_articles = $stmt_articles->get_result();
|
||||
$articles = $result_articles->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_articles->close();
|
||||
$conn->close();
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>Inhalt: <?php echo strip_tags($location_display_name); ?></title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;600;700&family=Poppins:wght@500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background-color: #f8f9fa; font-family: 'Nunito Sans', sans-serif; }
|
||||
h1, h2, h3, h4, h5, h6 { font-family: 'Poppins', sans-serif; }
|
||||
.mobile-header { background-color: #3b4a23; color: white; padding: 15px; position: sticky; top: 0; z-index: 1000; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||
.article-card { background: white; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); padding: 12px; margin-bottom: 12px; display: flex; align-items: center; }
|
||||
.article-img { width: 60px; height: 60px; object-fit: cover; border-radius: 6px; margin-right: 15px; background-color: #f0f0f0; }
|
||||
.article-info { flex-grow: 1; min-width: 0; }
|
||||
.article-title { font-weight: 600; font-size: 1rem; margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.article-meta { font-size: 0.8rem; color: #6c757d; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.qty-badge { font-weight: bold; background: #e9ecef; padding: 4px 8px; border-radius: 12px; font-size: 0.85rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="mobile-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="small text-white-50 text-uppercase fw-bold" style="letter-spacing: 1px; font-size: 0.7rem;">Lagerort</div>
|
||||
<h5 class="mb-0"><?php echo $location_display_name; ?></h5>
|
||||
</div>
|
||||
<div><i class="fas fa-box-open fa-2x opacity-50"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="container py-3 pb-5">
|
||||
<?php if (empty($articles)): ?>
|
||||
<div class="text-center text-muted mt-5 pt-5">
|
||||
<i class="fas fa-box-open fa-3x mb-3 opacity-25"></i>
|
||||
<p>Dieser Lagerort ist leer.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="text-muted small mb-3"><?php echo count($articles); ?> Artikel gefunden</p>
|
||||
|
||||
<?php foreach ($articles as $article):
|
||||
$img = !empty($article['image_url']) ? htmlspecialchars($article['image_url']) : 'assets/images/keinbild.png';
|
||||
$meta = array_filter([$article['manufacturer_name'], $article['product_designation']]);
|
||||
$meta_text = !empty($meta) ? htmlspecialchars(implode(' - ', $meta)) : 'Keine Details';
|
||||
$catColor = !empty($article['category_color']) ? htmlspecialchars($article['category_color']) : '#e2e8f0';
|
||||
$catName = !empty($article['category_name']) ? htmlspecialchars($article['category_name']) : 'Ohne Kategorie';
|
||||
?>
|
||||
<div class="article-card">
|
||||
<img src="<?php echo $img; ?>" class="article-img" alt="Bild">
|
||||
<div class="article-info">
|
||||
<div class="article-title"><?php echo htmlspecialchars($article['name']); ?></div>
|
||||
<div class="article-meta mb-1"><?php echo $meta_text; ?></div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge rounded-pill border" style="background-color: <?php echo $catColor; ?>; color: #fff; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; font-size: 0.7rem;"><?php echo $catName; ?></span>
|
||||
<span class="text-muted" style="font-size: 0.75rem;"><i class="fas fa-weight-hanging me-1"></i><?php echo $article['weight_grams']; ?>g</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qty-badge"><?php echo $article['quantity_owned']; ?>x</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4 mb-4">
|
||||
<a href="index.php" class="text-muted small text-decoration-none"><i class="fas fa-home me-1"></i>Trekking Packliste App</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -112,8 +112,22 @@ elseif (isset($_GET['action']) && $_GET['action'] == 'delete' && isset($_GET['id
|
||||
}
|
||||
|
||||
|
||||
// AKTION: BARCODE GENERIEREN
|
||||
elseif (isset($_GET['action']) && $_GET['action'] == 'generate_barcode' && isset($_GET['id'])) {
|
||||
$location_id = intval($_GET['id']);
|
||||
$token = bin2hex(random_bytes(16));
|
||||
$stmt_token = $conn->prepare("UPDATE storage_locations SET public_token = ? WHERE id = ? AND user_id IN ($placeholders)");
|
||||
$all_params = array_merge([$token, $location_id], $household_member_ids);
|
||||
$all_types = 'si' . $types;
|
||||
$stmt_token->bind_param($all_types, ...$all_params);
|
||||
$stmt_token->execute();
|
||||
$stmt_token->close();
|
||||
header("Location: storage_locations.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Lade alle Orte des Haushalts
|
||||
$sql = "SELECT sl.id, sl.name, sl.parent_id, sl.user_id FROM storage_locations sl WHERE sl.user_id IN ($placeholders) ORDER BY sl.parent_id ASC, sl.name ASC";
|
||||
$sql = "SELECT sl.id, sl.name, sl.parent_id, sl.user_id, sl.public_token FROM storage_locations sl WHERE sl.user_id IN ($placeholders) ORDER BY sl.parent_id ASC, sl.name ASC";
|
||||
$stmt_load = $conn->prepare($sql);
|
||||
$stmt_load->bind_param($types, ...$household_member_ids);
|
||||
$stmt_load->execute();
|
||||
@@ -180,6 +194,11 @@ foreach ($all_locations as $location) {
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0"><i class="fas fa-box-open me-2"></i><?php echo htmlspecialchars($loc1['name']); ?></h6>
|
||||
<div class="btn-group">
|
||||
<?php if(empty($loc1['public_token'])): ?>
|
||||
<a href="storage_locations.php?action=generate_barcode&id=<?php echo $loc1['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Barcode erstellen"><i class="fas fa-qrcode"></i></a>
|
||||
<?php else: ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-success" onclick="showBarcode('<?php echo htmlspecialchars($loc1['public_token']); ?>', '<?php echo htmlspecialchars(addslashes($loc1['name'])); ?>')" title="Barcode anzeigen"><i class="fas fa-qrcode"></i></button>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editLocationModal" data-id="<?php echo $loc1['id']; ?>" data-name="<?php echo htmlspecialchars($loc1['name']); ?>"><i class="fas fa-edit"></i></button>
|
||||
<a href="storage_locations.php?action=delete&id=<?php echo $loc1['id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Sicher, dass du diesen Ort löschen willst?');"><i class="fas fa-trash"></i></a>
|
||||
</div>
|
||||
@@ -249,3 +268,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
ationName;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
|
||||
Reference in New Issue
Block a user