diff --git a/README.md b/README.md
index 334ba29..f5dfdd6 100644
--- a/README.md
+++ b/README.md
@@ -230,7 +230,8 @@ Das Projekt basiert auf bewährten Web-Standards:
* Die störende JavaScript `confirm()`-Meldung beim Löschen von Artikeln in der Übersicht (`articles.php`) wurde durch ein einheitliches, modernes Bootstrap-Modal ersetzt.
### 19.06.2026
-* **Features:**
- * **Notizen:** Neue Funktion "Notizen" hinzugefügt inkl. Rich-Text-Editor.
- * **Haushaltsfreigabe:** Notizen können mit dem Haushalt geteilt werden.
+ * **Features:**
+ * **Notizen:** Neue Funktion "Notizen" hinzugefügt inkl. Rich-Text-Editor.
+ * **Haushaltsfreigabe:** Notizen können mit dem Haushalt geteilt werden.
+ * **Tags:** Artikel können nun mit Tags versehen werden, die im modernen Badge-Design angezeigt werden und nach denen in der Übersicht gefiltert werden kann.
* **Design-Update:** Die Notizen-Übersichtsseite wurde an das Design der ToDo-Listen angepasst (Split-View, List-Groups, weiße Notizhintergründe). Der Texteditor bietet nun deutsche Tooltips. Beim Speichern einer Notiz wird nun besser zurück in die Übersicht navigiert. Das Layout der Metainformationen sowie der Buttons wurde weiter optimiert. Die Notizen werden in der Liste alphabetisch sortiert. Der Editor wurde aktualisiert: Er besitzt nun eine feste Höhe mit internem Scrollbereich, sodass die Formatierungsleiste immer sichtbar bleibt. Zudem wurde eine Tabellen-Funktion hinzugefügt.
diff --git a/src/add_article.php b/src/add_article.php
index e788e40..3bd9996 100644
--- a/src/add_article.php
+++ b/src/add_article.php
@@ -58,6 +58,15 @@ $stmt_man_load->execute();
$manufacturers = $stmt_man_load->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt_man_load->close();
+// Lade Tags
+$stmt_tags_load = $conn->prepare("SELECT name FROM tags WHERE household_id = ? ORDER BY name ASC");
+$stmt_tags_load->bind_param("i", $household_id_for_user);
+$stmt_tags_load->execute();
+$existing_tags = [];
+$result_tags = $stmt_tags_load->get_result();
+while($row = $result_tags->fetch_assoc()) { $existing_tags[] = $row['name']; }
+$stmt_tags_load->close();
+
// Lade Lagerorte
$stmt_loc_load = $conn->prepare("SELECT id, name, parent_id FROM storage_locations WHERE user_id IN ($placeholders) ORDER BY parent_id, name");
$stmt_loc_load->bind_param($types, ...$household_member_ids);
@@ -188,6 +197,40 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
if ($stmt_insert_article) {
$stmt_insert_article->bind_param("iisiiisssisii", $current_user_id, $is_household_item, $name, $weight_grams, $quantity_owned, $category_id, $consumable, $image_url_for_db, $product_url_for_db, $manufacturer_id, $product_designation_for_db, $storage_location_id, $parent_article_id);
if ($stmt_insert_article->execute()) {
+ $new_article_id = $conn->insert_id;
+
+ // Tags verarbeiten
+ $tags_json = $_POST['tags'] ?? '';
+ if (!empty($tags_json)) {
+ $tags_arr = json_decode($tags_json, true);
+ if (is_array($tags_arr)) {
+ foreach ($tags_arr as $t) {
+ $tag_name = trim($t['value']);
+ if (empty($tag_name)) continue;
+
+ $stmt_check_tag = $conn->prepare("SELECT id FROM tags WHERE household_id = ? AND name = ?");
+ $stmt_check_tag->bind_param("is", $household_id_for_user, $tag_name);
+ $stmt_check_tag->execute();
+ $res = $stmt_check_tag->get_result();
+ if ($res->num_rows > 0) {
+ $tag_id = $res->fetch_assoc()['id'];
+ } else {
+ $stmt_add_tag = $conn->prepare("INSERT INTO tags (household_id, name) VALUES (?, ?)");
+ $stmt_add_tag->bind_param("is", $household_id_for_user, $tag_name);
+ $stmt_add_tag->execute();
+ $tag_id = $conn->insert_id;
+ $stmt_add_tag->close();
+ }
+ $stmt_check_tag->close();
+
+ $stmt_link_tag = $conn->prepare("INSERT IGNORE INTO article_tags (article_id, tag_id) VALUES (?, ?)");
+ $stmt_link_tag->bind_param("ii", $new_article_id, $tag_id);
+ $stmt_link_tag->execute();
+ $stmt_link_tag->close();
+ }
+ }
+ }
+
if ($household_id_for_user) {
$log_message = htmlspecialchars($_SESSION['username']) . " hat den Artikel '" . htmlspecialchars($name) . "' hinzugefügt.";
log_household_action($conn, $household_id_for_user, $current_user_id, $log_message);
@@ -209,6 +252,29 @@ $conn->close();
+
+
+
+
+
Neuen Artikel erstellen
@@ -251,6 +317,7 @@ $conn->close();
+
@@ -464,6 +531,20 @@ document.addEventListener('DOMContentLoaded', function() {
}
if(document.getElementById('parent_article_id')) new TomSelect('#parent_article_id', tsOptionsBase);
if(document.getElementById('storage_location_id')) new TomSelect('#storage_location_id', tsOptionsBase);
+
+ // Initialize Tagify
+ const tagInput = document.querySelector('input[name="tags"]');
+ if(tagInput) {
+ new Tagify(tagInput, {
+ whitelist: ,
+ dropdown: {
+ maxItems: 20,
+ classname: "tags-look",
+ enabled: 0,
+ closeOnSelect: false
+ }
+ });
+ }
});
\ No newline at end of file
diff --git a/src/articles.php b/src/articles.php
index a8b44ca..71ba4c6 100644
--- a/src/articles.php
+++ b/src/articles.php
@@ -109,12 +109,40 @@ if ($stmt === false) {
$stmt->close();
}
+// Lade Tags für die geladenen Artikel
+$tags_by_article = [];
+$article_ids = array_column($articles, 'id');
+if (!empty($article_ids)) {
+ $id_placeholders = implode(',', array_fill(0, count($article_ids), '?'));
+ $stmt_tags = $conn->prepare("SELECT at.article_id, t.name, t.color FROM tags t JOIN article_tags at ON t.id = at.tag_id WHERE at.article_id IN ($id_placeholders) ORDER BY t.name ASC");
+ if ($stmt_tags) {
+ $types_tags = str_repeat('i', count($article_ids));
+ $stmt_tags->bind_param($types_tags, ...$article_ids);
+ $stmt_tags->execute();
+ $res_tags = $stmt_tags->get_result();
+ while($row = $res_tags->fetch_assoc()) {
+ $tags_by_article[$row['article_id']][] = ['name' => $row['name'], 'color' => $row['color']];
+ }
+ $stmt_tags->close();
+ }
+}
+foreach ($articles as &$article) {
+ $article['tags'] = $tags_by_article[$article['id']] ?? [];
+}
+unset($article);
+
$stmt_cat_load = $conn->prepare("SELECT id, name FROM categories WHERE user_id IN ($placeholders) ORDER BY name ASC");
$stmt_cat_load->bind_param($types, ...$household_member_ids);
$stmt_cat_load->execute();
$categories_for_filter = $stmt_cat_load->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt_cat_load->close();
+$stmt_all_tags = $conn->prepare("SELECT id, name FROM tags WHERE household_id = ? ORDER BY name ASC");
+$stmt_all_tags->bind_param("i", $current_user_household_id);
+$stmt_all_tags->execute();
+$all_filter_tags = $stmt_all_tags->get_result()->fetch_all(MYSQLI_ASSOC);
+$stmt_all_tags->close();
+
$stmt_man_load = $conn->prepare("SELECT id, name FROM manufacturers WHERE user_id IN ($placeholders) ORDER BY name ASC");
$stmt_man_load->bind_param($types, ...$household_member_ids);
$stmt_man_load->execute();
@@ -172,6 +200,28 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST['bulk_move_articles']))
$articles[] = $row;
}
$stmt->close();
+
+ // Fetch tags for reloaded articles
+ $tags_by_article = [];
+ $article_ids = array_column($articles, 'id');
+ if (!empty($article_ids)) {
+ $id_placeholders = implode(',', array_fill(0, count($article_ids), '?'));
+ $stmt_tags = $conn->prepare("SELECT at.article_id, t.name, t.color FROM tags t JOIN article_tags at ON t.id = at.tag_id WHERE at.article_id IN ($id_placeholders) ORDER BY t.name ASC");
+ if ($stmt_tags) {
+ $types_tags = str_repeat('i', count($article_ids));
+ $stmt_tags->bind_param($types_tags, ...$article_ids);
+ $stmt_tags->execute();
+ $res_tags = $stmt_tags->get_result();
+ while($row = $res_tags->fetch_assoc()) {
+ $tags_by_article[$row['article_id']][] = ['name' => $row['name'], 'color' => $row['color']];
+ }
+ $stmt_tags->close();
+ }
+ }
+ foreach ($articles as &$article) {
+ $article['tags'] = $tags_by_article[$article['id']] ?? [];
+ }
+ unset($article);
} else {
$message = '
Fehler beim Verschieben: ' . $stmt_update->error . '