Feature: Dashboard-Statistiken, Display-Names und UI-Feinschliff
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:
@@ -212,4 +212,7 @@ Das Projekt basiert auf bewährten Web-Standards:
|
||||
* Anzeige in der Haushaltsverwaltung nutzt nun den Anzeigenamen (Display Name) anstatt des Loginnamens, sofern vorhanden.
|
||||
* 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.
|
||||
* Kachelhöhe in der Artikel-Übersicht und im Packlisten-Editor (`manage_packing_list_items.php`) leicht angepasst.
|
||||
* 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).
|
||||
* 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.
|
||||
|
||||
@@ -416,20 +416,21 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
<form action="articles.php" method="post" class="d-inline" onsubmit="return confirm('Sind Sie sicher?');"><input type="hidden" name="delete_article_id" value="${article.id}"><button type="submit" class="btn btn-sm btn-outline-danger px-1 py-0" title="Löschen"><i class="fas fa-trash"></i></button></form>`;
|
||||
}
|
||||
|
||||
const metaText = [article.manufacturer_name, article.product_designation].filter(Boolean).join(' - ');
|
||||
const metaTextRaw = [article.manufacturer_name, article.product_designation].filter(Boolean).join(' - ');
|
||||
const metaTextDisplay = metaTextRaw || ' ';
|
||||
const catName = article.category_name || 'Ohne Kat.';
|
||||
const catColor = article.category_color || '#e2e8f0';
|
||||
|
||||
return `
|
||||
<div class="lager-card article-card" title="${article.name}${metaText ? ' (' + metaText + ')' : ''}">
|
||||
<div class="lager-card article-card" title="${article.name}${metaTextRaw ? ' (' + metaTextRaw + ')' : ''}">
|
||||
<div class="position-absolute top-0 end-0 p-1" style="z-index: 10;">
|
||||
${householdBadge} ${consumableIcon}
|
||||
</div>
|
||||
<div class="lager-img-wrapper">
|
||||
<img src="${imagePath}" class="lager-card-img article-image-trigger" data-preview-url="${imagePath}" alt="${article.name}" style="cursor: pointer;">
|
||||
<img src="${imagePath}" class="lager-card-img" alt="${article.name}">
|
||||
</div>
|
||||
<div class="lager-title">${article.name}</div>
|
||||
<div class="lager-meta">${metaText}</div>
|
||||
<div class="lager-meta">${metaTextDisplay}</div>
|
||||
<div class="text-muted d-block mb-1" style="font-size:0.75em;">${new Intl.NumberFormat('de-DE').format(article.weight_grams)} g | ${quantityBadge}</div>
|
||||
<div class="lager-meta"><span class="badge border w-100 text-truncate p-1" style="background-color: ${catColor}; color: #fff; text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; font-weight: 600;">${catName}</span></div>
|
||||
<div class="lager-controls d-flex justify-content-center gap-1 mt-auto">
|
||||
|
||||
@@ -121,7 +121,7 @@ elseif (isset($_GET['action']) && $_GET['action'] == 'delete' && isset($_GET['id
|
||||
$stmt_check->close();
|
||||
}
|
||||
|
||||
$categories_query = $conn->prepare("SELECT c.id, c.name, c.color, c.user_id, u.username as creator_name FROM categories c JOIN users u ON c.user_id = u.id WHERE c.user_id IN ($placeholders) ORDER BY c.name ASC");
|
||||
$categories_query = $conn->prepare("SELECT c.id, c.name, c.color, c.user_id, COALESCE(NULLIF(u.display_name, ''), u.username) as creator_name FROM categories c JOIN users u ON c.user_id = u.id WHERE c.user_id IN ($placeholders) ORDER BY c.name ASC");
|
||||
$categories_query->bind_param($types, ...$household_member_ids);
|
||||
$categories_query->execute();
|
||||
$categories_list = $categories_query->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
@@ -44,7 +44,7 @@ $types = str_repeat('i', count($household_member_ids));
|
||||
|
||||
// --- Daten für die Statistik-Boxen laden ---
|
||||
$articles_by_category = [];
|
||||
$sql_articles_by_category = "SELECT c.name AS category_name, COUNT(a.id) AS article_count FROM articles a LEFT JOIN categories c ON a.category_id = c.id WHERE a.user_id IN ($placeholders) OR a.household_id = ? GROUP BY c.name ORDER BY article_count DESC";
|
||||
$sql_articles_by_category = "SELECT c.name AS category_name, c.color AS category_color, COUNT(a.id) AS article_count FROM articles a LEFT JOIN categories c ON a.category_id = c.id WHERE a.user_id IN ($placeholders) OR a.household_id = ? GROUP BY c.name, c.color ORDER BY article_count DESC";
|
||||
$stmt_articles_by_category = $conn->prepare($sql_articles_by_category);
|
||||
if ($stmt_articles_by_category) {
|
||||
$all_params = array_merge($household_member_ids, [$current_user_household_id]);
|
||||
@@ -55,6 +55,7 @@ if ($stmt_articles_by_category) {
|
||||
while ($row = $result_articles_by_category->fetch_assoc()) {
|
||||
$articles_by_category[] = [
|
||||
'category_name' => $row['category_name'] ?: 'Ohne Kategorie',
|
||||
'category_color' => $row['category_color'] ?: '#e2e8f0',
|
||||
'article_count' => $row['article_count']
|
||||
];
|
||||
}
|
||||
@@ -79,11 +80,42 @@ if ($stmt_lists) {
|
||||
}
|
||||
$stmt_lists->close();
|
||||
}
|
||||
|
||||
// Weitere Statistiken sammeln
|
||||
$total_articles = 0;
|
||||
$stmt_count_articles = $conn->prepare("SELECT COUNT(*) as count FROM articles WHERE user_id IN ($placeholders) OR household_id = ?");
|
||||
$stmt_count_articles->bind_param($all_types, ...$all_params);
|
||||
$stmt_count_articles->execute();
|
||||
$total_articles = $stmt_count_articles->get_result()->fetch_assoc()['count'];
|
||||
$stmt_count_articles->close();
|
||||
|
||||
$total_lists = 0;
|
||||
$stmt_count_lists = $conn->prepare("SELECT COUNT(*) as count FROM packing_lists WHERE is_template = 0 AND (user_id IN ($placeholders) OR household_id = ?)");
|
||||
$stmt_count_lists->bind_param($all_types, ...$all_params);
|
||||
$stmt_count_lists->execute();
|
||||
$total_lists = $stmt_count_lists->get_result()->fetch_assoc()['count'];
|
||||
$stmt_count_lists->close();
|
||||
|
||||
$total_templates = 0;
|
||||
$stmt_count_templates = $conn->prepare("SELECT COUNT(*) as count FROM packing_lists WHERE is_template = 1 AND (user_id IN ($placeholders) OR household_id = ?)");
|
||||
$stmt_count_templates->bind_param($all_types, ...$all_params);
|
||||
$stmt_count_templates->execute();
|
||||
$total_templates = $stmt_count_templates->get_result()->fetch_assoc()['count'];
|
||||
$stmt_count_templates->close();
|
||||
|
||||
$total_backpacks = 0;
|
||||
$stmt_count_bp = $conn->prepare("SELECT COUNT(*) as count FROM backpacks WHERE user_id IN ($placeholders) OR household_id = ?");
|
||||
$stmt_count_bp->bind_param($all_types, ...$all_params);
|
||||
$stmt_count_bp->execute();
|
||||
$total_backpacks = $stmt_count_bp->get_result()->fetch_assoc()['count'];
|
||||
$stmt_count_bp->close();
|
||||
|
||||
$conn->close();
|
||||
|
||||
// Daten für die Diagramme vorbereiten
|
||||
$category_chart_labels = json_encode(array_column($articles_by_category, 'category_name'));
|
||||
$category_chart_data = json_encode(array_column($articles_by_category, 'article_count'));
|
||||
$category_chart_colors = json_encode(array_column($articles_by_category, 'category_color'));
|
||||
|
||||
$list_chart_labels = json_encode(array_column($packing_lists_stats, 'packing_list_name'));
|
||||
$list_chart_data = json_encode(array_column($packing_lists_stats, 'total_weight_grams'));
|
||||
@@ -94,7 +126,7 @@ $random_quote = $quotes[array_rand($quotes)];
|
||||
|
||||
<div class="card p-4 border-0 shadow-sm">
|
||||
<div class="welcome-section">
|
||||
<h1>Willkommen zurück, <strong><?php echo htmlspecialchars($current_username); ?></strong>!</h1>
|
||||
<h1>Willkommen zurück, <strong><?php echo htmlspecialchars($display_name); ?></strong>!</h1>
|
||||
<p class="lead">Organisiere deine Ausrüstung und bereite dich optimal auf dein nächstes Abenteuer vor.</p>
|
||||
|
||||
<div class="welcome-image-container">
|
||||
@@ -105,10 +137,37 @@ $random_quote = $quotes[array_rand($quotes)];
|
||||
|
||||
<hr class="my-5" style="opacity: 0.1;">
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card h-100 bg-light border-0 text-center p-3 shadow-sm">
|
||||
<h3 class="display-6 text-primary mb-0"><?php echo $total_articles; ?></h3>
|
||||
<span class="text-muted">Artikel im Haushalt</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card h-100 bg-light border-0 text-center p-3 shadow-sm">
|
||||
<h3 class="display-6 text-success mb-0"><?php echo $total_lists; ?></h3>
|
||||
<span class="text-muted">Packlisten</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card h-100 bg-light border-0 text-center p-3 shadow-sm">
|
||||
<h3 class="display-6 text-warning mb-0"><?php echo $total_templates; ?></h3>
|
||||
<span class="text-muted">Packvorlagen</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-sm-6">
|
||||
<div class="card h-100 bg-light border-0 text-center p-3 shadow-sm">
|
||||
<h3 class="display-6 text-info mb-0"><?php echo $total_backpacks; ?></h3>
|
||||
<span class="text-muted">Rucksäcke</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><h3 class="h5 mb-0"><i class="fas fa-chart-pie me-2"></i>Artikelverteilung im Haushalt</h3></div>
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-header bg-white"><h3 class="h5 mb-0"><i class="fas fa-chart-pie me-2"></i>Artikelverteilung im Haushalt</h3></div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center">
|
||||
<?php if (empty($articles_by_category)): ?>
|
||||
<p class="text-center text-muted p-4">Füge Artikel hinzu, um hier eine Übersicht zu sehen.</p>
|
||||
@@ -122,8 +181,8 @@ $random_quote = $quotes[array_rand($quotes)];
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header"><h3 class="h5 mb-0"><i class="fas fa-chart-bar me-2"></i>Gewicht pro Packliste im Haushalt (g)</h3></div>
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-header bg-white"><h3 class="h5 mb-0"><i class="fas fa-chart-bar me-2"></i>Gewicht pro Packliste im Haushalt (g)</h3></div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center">
|
||||
<?php if (empty($packing_lists_stats)): ?>
|
||||
<p class="text-center text-muted p-4">Erstelle eine Packliste, um hier eine Analyse zu sehen.</p>
|
||||
@@ -141,12 +200,6 @@ $random_quote = $quotes[array_rand($quotes)];
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// KORREKTUR: Neue, kontrastreichere Farbpalette
|
||||
const chartColors = [
|
||||
'#4CAF50', '#2196F3', '#FFC107', '#E91E63', '#9C27B0',
|
||||
'#00BCD4', '#FF5722', '#795548', '#607D8B', '#F44336'
|
||||
];
|
||||
|
||||
const categoryCtx = document.getElementById('categoryChart');
|
||||
if (categoryCtx && <?php echo $category_chart_data; ?>.length > 0) {
|
||||
new Chart(categoryCtx, {
|
||||
@@ -156,9 +209,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
datasets: [{
|
||||
label: 'Anzahl Artikel',
|
||||
data: <?php echo $category_chart_data; ?>,
|
||||
backgroundColor: chartColors,
|
||||
backgroundColor: <?php echo $category_chart_colors; ?>,
|
||||
hoverOffset: 4,
|
||||
borderWidth: 0
|
||||
borderWidth: 1,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user