feat(tags): is_parent-Flag zur Unterscheidung von Gruppierungs-Tags und zuordenbaren Tags
Parent-Tags (is_parent=true) dienen als Gruppierungsknoten und können nicht Gesprächsnotizen zugeordnet werden. Echte Tags (is_parent=false) sind zuordenbar. Die Bearbeitungsmaske bietet eine Checkbox für das Flag, die Eltern-Auswahl ist auf Parent-Tags beschränkt. Die Tag-Liste zeigt Parent-Tags mit Folder-Icon, echte Tags mit Label-Icon. Die Suche filtert den gesamten Teilbaum — nur Knoten mit Self-Match oder einem matchenden Nachfahren werden angezeigt. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
3361ef16dd
commit
b44ea806ef
1 file changed
+142
-25
+142
-25
@@ -22,6 +22,7 @@ export interface iTag {
|
|||||||
description: string; // Beschreibung
|
description: string; // Beschreibung
|
||||||
color: string; // Farbcode (z. B. "#ff0000")
|
color: string; // Farbcode (z. B. "#ff0000")
|
||||||
parentId: number | null; // Referenz auf den Eltern-Tag (null = Wurzelebene)
|
parentId: number | null; // Referenz auf den Eltern-Tag (null = Wurzelebene)
|
||||||
|
is_parent: boolean; // true = Gruppierungs-Knoten (nicht zuordenbar), false = echter Tag (zuordenbar)
|
||||||
icon?: string; // Optional: Icon-Name oder URL
|
icon?: string; // Optional: Icon-Name oder URL
|
||||||
order?: number; // Optional: Sortierreihenfolge innerhalb der Ebene
|
order?: number; // Optional: Sortierreihenfolge innerhalb der Ebene
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Hochpriorisierte Aufgaben",
|
description: "Hochpriorisierte Aufgaben",
|
||||||
color: "#ff0000",
|
color: "#ff0000",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
icon: "priority_high",
|
icon: "priority_high",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -58,6 +60,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Kundenbezogene Tags",
|
description: "Kundenbezogene Tags",
|
||||||
color: "#00ff00",
|
color: "#00ff00",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
icon: "person",
|
icon: "person",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -67,6 +70,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Projektbezogene Tags",
|
description: "Projektbezogene Tags",
|
||||||
color: "#0000ff",
|
color: "#0000ff",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
icon: "folder",
|
icon: "folder",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -76,6 +80,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Nachverfolgung erforderlich",
|
description: "Nachverfolgung erforderlich",
|
||||||
color: "#ffff00",
|
color: "#ffff00",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
icon: "schedule",
|
icon: "schedule",
|
||||||
order: 4,
|
order: 4,
|
||||||
},
|
},
|
||||||
@@ -85,6 +90,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Archivierte Einträge",
|
description: "Archivierte Einträge",
|
||||||
color: "#808080",
|
color: "#808080",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
icon: "archive",
|
icon: "archive",
|
||||||
order: 5,
|
order: 5,
|
||||||
},
|
},
|
||||||
@@ -96,6 +102,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Server-seitige Technologien und Frameworks",
|
description: "Server-seitige Technologien und Frameworks",
|
||||||
color: "#4285F4",
|
color: "#4285F4",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: true,
|
||||||
icon: "server",
|
icon: "server",
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
@@ -105,6 +112,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Client-seitige Technologien und Frameworks",
|
description: "Client-seitige Technologien und Frameworks",
|
||||||
color: "#34A853",
|
color: "#34A853",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: true,
|
||||||
icon: "browser",
|
icon: "browser",
|
||||||
order: 7,
|
order: 7,
|
||||||
},
|
},
|
||||||
@@ -114,6 +122,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Infrastruktur, Deployment und CI/CD",
|
description: "Infrastruktur, Deployment und CI/CD",
|
||||||
color: "#EA4335",
|
color: "#EA4335",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: true,
|
||||||
icon: "cog",
|
icon: "cog",
|
||||||
order: 8,
|
order: 8,
|
||||||
},
|
},
|
||||||
@@ -123,6 +132,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Datenbanken, Analyse und KI",
|
description: "Datenbanken, Analyse und KI",
|
||||||
color: "#FBBC04",
|
color: "#FBBC04",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: true,
|
||||||
icon: "database",
|
icon: "database",
|
||||||
order: 9,
|
order: 9,
|
||||||
},
|
},
|
||||||
@@ -134,6 +144,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Allgemeine Python-Entwicklung",
|
description: "Allgemeine Python-Entwicklung",
|
||||||
color: "#3776AB",
|
color: "#3776AB",
|
||||||
parentId: 100,
|
parentId: 100,
|
||||||
|
is_parent: true,
|
||||||
icon: "python",
|
icon: "python",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -143,6 +154,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Java und JVM-Ökosystem",
|
description: "Java und JVM-Ökosystem",
|
||||||
color: "#007396",
|
color: "#007396",
|
||||||
parentId: 100,
|
parentId: 100,
|
||||||
|
is_parent: false,
|
||||||
icon: "java",
|
icon: "java",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -152,6 +164,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "JavaScript auf dem Server",
|
description: "JavaScript auf dem Server",
|
||||||
color: "#339933",
|
color: "#339933",
|
||||||
parentId: 100,
|
parentId: 100,
|
||||||
|
is_parent: true,
|
||||||
icon: "nodejs",
|
icon: "nodejs",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -163,6 +176,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Mikro-Webframework für Python",
|
description: "Mikro-Webframework für Python",
|
||||||
color: "#000000",
|
color: "#000000",
|
||||||
parentId: 200,
|
parentId: 200,
|
||||||
|
is_parent: false,
|
||||||
icon: "flask",
|
icon: "flask",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -172,6 +186,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Full-Stack Webframework für Python",
|
description: "Full-Stack Webframework für Python",
|
||||||
color: "#092E20",
|
color: "#092E20",
|
||||||
parentId: 200,
|
parentId: 200,
|
||||||
|
is_parent: false,
|
||||||
icon: "django",
|
icon: "django",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -181,6 +196,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Moderne API-Entwicklung mit Python",
|
description: "Moderne API-Entwicklung mit Python",
|
||||||
color: "#005577",
|
color: "#005577",
|
||||||
parentId: 200,
|
parentId: 200,
|
||||||
|
is_parent: false,
|
||||||
icon: "fastapi",
|
icon: "fastapi",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -192,6 +208,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Minimalistisches Webframework für Node.js",
|
description: "Minimalistisches Webframework für Node.js",
|
||||||
color: "#000000",
|
color: "#000000",
|
||||||
parentId: 202,
|
parentId: 202,
|
||||||
|
is_parent: false,
|
||||||
icon: "express",
|
icon: "express",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -201,6 +218,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Progressives Node.js-Framework für Enterprise-Anwendungen",
|
description: "Progressives Node.js-Framework für Enterprise-Anwendungen",
|
||||||
color: "#E0234E",
|
color: "#E0234E",
|
||||||
parentId: 202,
|
parentId: 202,
|
||||||
|
is_parent: false,
|
||||||
icon: "nestjs",
|
icon: "nestjs",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -212,6 +230,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Backend-Datenbank-Systeme",
|
description: "Backend-Datenbank-Systeme",
|
||||||
color: "#336791",
|
color: "#336791",
|
||||||
parentId: 100,
|
parentId: 100,
|
||||||
|
is_parent: true,
|
||||||
icon: "database",
|
icon: "database",
|
||||||
order: 4,
|
order: 4,
|
||||||
},
|
},
|
||||||
@@ -221,6 +240,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Relationale Datenbank mit erweiterten Features",
|
description: "Relationale Datenbank mit erweiterten Features",
|
||||||
color: "#336791",
|
color: "#336791",
|
||||||
parentId: 210,
|
parentId: 210,
|
||||||
|
is_parent: false,
|
||||||
icon: "postgres",
|
icon: "postgres",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -230,6 +250,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Dokumentenorientierte NoSQL-Datenbank",
|
description: "Dokumentenorientierte NoSQL-Datenbank",
|
||||||
color: "#47A248",
|
color: "#47A248",
|
||||||
parentId: 210,
|
parentId: 210,
|
||||||
|
is_parent: false,
|
||||||
icon: "mongodb",
|
icon: "mongodb",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -241,6 +262,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Die Hauptsprache des Web",
|
description: "Die Hauptsprache des Web",
|
||||||
color: "#F7DF1E",
|
color: "#F7DF1E",
|
||||||
parentId: 101,
|
parentId: 101,
|
||||||
|
is_parent: true,
|
||||||
icon: "js",
|
icon: "js",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -250,6 +272,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Typsicheres JavaScript",
|
description: "Typsicheres JavaScript",
|
||||||
color: "#3178C6",
|
color: "#3178C6",
|
||||||
parentId: 101,
|
parentId: 101,
|
||||||
|
is_parent: false,
|
||||||
icon: "ts",
|
icon: "ts",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -261,6 +284,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Komponentenbasiertes UI-Framework",
|
description: "Komponentenbasiertes UI-Framework",
|
||||||
color: "#61DAFB",
|
color: "#61DAFB",
|
||||||
parentId: 220,
|
parentId: 220,
|
||||||
|
is_parent: false,
|
||||||
icon: "react",
|
icon: "react",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -270,6 +294,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Progressives JavaScript-Framework",
|
description: "Progressives JavaScript-Framework",
|
||||||
color: "#4FC08D",
|
color: "#4FC08D",
|
||||||
parentId: 220,
|
parentId: 220,
|
||||||
|
is_parent: false,
|
||||||
icon: "vue",
|
icon: "vue",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -279,6 +304,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Full-Stack Framework von Google",
|
description: "Full-Stack Framework von Google",
|
||||||
color: "#DD1B16",
|
color: "#DD1B16",
|
||||||
parentId: 220,
|
parentId: 220,
|
||||||
|
is_parent: false,
|
||||||
icon: "angular",
|
icon: "angular",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -290,6 +316,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Stildefinition für Webseiten",
|
description: "Stildefinition für Webseiten",
|
||||||
color: "#1572B6",
|
color: "#1572B6",
|
||||||
parentId: 101,
|
parentId: 101,
|
||||||
|
is_parent: true,
|
||||||
icon: "css",
|
icon: "css",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -299,6 +326,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Utility-first CSS-Framework",
|
description: "Utility-first CSS-Framework",
|
||||||
color: "#06B6D4",
|
color: "#06B6D4",
|
||||||
parentId: 230,
|
parentId: 230,
|
||||||
|
is_parent: false,
|
||||||
icon: "tailwind",
|
icon: "tailwind",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -310,6 +338,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Containerisierung von Anwendungen",
|
description: "Containerisierung von Anwendungen",
|
||||||
color: "#2496ED",
|
color: "#2496ED",
|
||||||
parentId: 102,
|
parentId: 102,
|
||||||
|
is_parent: false,
|
||||||
icon: "docker",
|
icon: "docker",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -319,6 +348,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Orchestrierung von Containern",
|
description: "Orchestrierung von Containern",
|
||||||
color: "#326CE5",
|
color: "#326CE5",
|
||||||
parentId: 102,
|
parentId: 102,
|
||||||
|
is_parent: false,
|
||||||
icon: "kubernetes",
|
icon: "kubernetes",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -328,6 +358,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Continuous Integration und Deployment",
|
description: "Continuous Integration und Deployment",
|
||||||
color: "#FFA500",
|
color: "#FFA500",
|
||||||
parentId: 102,
|
parentId: 102,
|
||||||
|
is_parent: false,
|
||||||
icon: "cicd",
|
icon: "cicd",
|
||||||
order: 3,
|
order: 3,
|
||||||
},
|
},
|
||||||
@@ -339,6 +370,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Machine Learning und KI-Technologien",
|
description: "Machine Learning und KI-Technologien",
|
||||||
color: "#8B00FF",
|
color: "#8B00FF",
|
||||||
parentId: 103,
|
parentId: 103,
|
||||||
|
is_parent: false,
|
||||||
icon: "ai",
|
icon: "ai",
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
@@ -348,6 +380,7 @@ const tagDaten: iTag[] = [
|
|||||||
description: "Verarbeitung großer Datenmengen",
|
description: "Verarbeitung großer Datenmengen",
|
||||||
color: "#FF6B6B",
|
color: "#FF6B6B",
|
||||||
parentId: 103,
|
parentId: 103,
|
||||||
|
is_parent: false,
|
||||||
icon: "bigdata",
|
icon: "bigdata",
|
||||||
order: 2,
|
order: 2,
|
||||||
},
|
},
|
||||||
@@ -417,6 +450,45 @@ function getDescendants(tagId: number): iTag[] {
|
|||||||
return descendants;
|
return descendants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert alle echten (zuordenbaren) Tags, d. h. alle Tags, die keine
|
||||||
|
* Parent-Tags sind (is_parent === false).
|
||||||
|
*/
|
||||||
|
function getRealTags(): iTag[] {
|
||||||
|
return tagDaten.filter((tag) => !tag.is_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert alle echten (zuordenbaren) Tags, die Nachfahren eines Parent-Tags
|
||||||
|
* sind (flach, rekursiv). Parent-Tags selbst werden herausgefiltert.
|
||||||
|
*/
|
||||||
|
function getRealDescendants(parentId: number): iTag[] {
|
||||||
|
return getDescendants(parentId).filter((tag) => !tag.is_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Löst eine Tag-Auswahl auf echte (zuordenbare) Tag-IDs auf.
|
||||||
|
* Wurde ein Parent-Tag ausgewählt, werden alle echten Nachfahren geliefert.
|
||||||
|
* Wurde ein echter Tag ausgewählt, wird dessen eigene ID geliefert.
|
||||||
|
* Rückgabe der Tag-IDs (für Filter/Auswertungen).
|
||||||
|
*/
|
||||||
|
function resolveToRealTagIds(tagId: number): number[] {
|
||||||
|
const tag = findTagById(tagId);
|
||||||
|
if (!tag) return [];
|
||||||
|
if (tag.is_parent) {
|
||||||
|
return getRealDescendants(tagId).map((t) => t.id);
|
||||||
|
}
|
||||||
|
return [tagId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob ein Tag zuordenbar ist, d. h. ein echter Tag (kein Parent-Tag).
|
||||||
|
*/
|
||||||
|
function isAssignable(tagId: number): boolean {
|
||||||
|
const tag = findTagById(tagId);
|
||||||
|
return tag ? !tag.is_parent : false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt alle Vorfahren (Eltern, Großeltern, ...) eines Tags zurück.
|
* Gibt alle Vorfahren (Eltern, Großeltern, ...) eines Tags zurück.
|
||||||
*/
|
*/
|
||||||
@@ -444,7 +516,7 @@ function searchTagsByName(query: string): iTag[] {
|
|||||||
/**
|
/**
|
||||||
* Fügt einen neuen Tag hinzu (mit automatischer ID-Vergabe).
|
* Fügt einen neuen Tag hinzu (mit automatischer ID-Vergabe).
|
||||||
*/
|
*/
|
||||||
function addTag(name: string, parentId: number | null = null, description: string = "", color: string = "#808080", icon?: string, order?: number): iTag {
|
function addTag(name: string, parentId: number | null = null, description: string = "", color: string = "#808080", icon?: string, order?: number, is_parent: boolean = false): iTag {
|
||||||
const newId = tagDaten.length > 0 ? Math.max(...tagDaten.map(t => t.id)) + 1 : 1;
|
const newId = tagDaten.length > 0 ? Math.max(...tagDaten.map(t => t.id)) + 1 : 1;
|
||||||
const tag: iTag = {
|
const tag: iTag = {
|
||||||
id: newId,
|
id: newId,
|
||||||
@@ -452,6 +524,7 @@ function addTag(name: string, parentId: number | null = null, description: strin
|
|||||||
description,
|
description,
|
||||||
color,
|
color,
|
||||||
parentId,
|
parentId,
|
||||||
|
is_parent,
|
||||||
icon,
|
icon,
|
||||||
order,
|
order,
|
||||||
};
|
};
|
||||||
@@ -501,29 +574,41 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
|||||||
description: "",
|
description: "",
|
||||||
color: "",
|
color: "",
|
||||||
parentId: null,
|
parentId: null,
|
||||||
|
is_parent: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const setColor = (color: string) => {
|
const setColor = (color: string) => {
|
||||||
currentTag.color = color;
|
currentTag.color = color;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Liste aller Tags für die Eltern-Auswahl
|
// Liste aller Parent-Tags für die Eltern-Auswahl (nur Parent-Tags können übergeordnet sein)
|
||||||
const allTags = tagDaten.filter(t => t.id !== currentTag.id); // Ausschließen des aktuellen Tags
|
let allParentTags: iTag[] = [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param();
|
const routeParams = m.route.param();
|
||||||
if (routeParams) {
|
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1;
|
||||||
|
if (id !== -1) {
|
||||||
|
// Bestehenden Tag aus den Daten laden (frisch, nicht aus Route-Params)
|
||||||
|
const tag = findTagById(id);
|
||||||
|
if (tag) {
|
||||||
|
currentTag = {...tag};
|
||||||
|
}
|
||||||
|
} else if (routeParams) {
|
||||||
|
// Neuer Tag: vorbelegte Werte aus Route-Params übernehmen
|
||||||
currentTag = {
|
currentTag = {
|
||||||
id: routeParams.id ? parseInt(routeParams.id) : -1,
|
id: -1,
|
||||||
name: routeParams.name || "",
|
name: routeParams.name || "",
|
||||||
description: routeParams.description || "",
|
description: routeParams.description || "",
|
||||||
color: routeParams.color || "",
|
color: routeParams.color || "",
|
||||||
parentId: routeParams.parentId ? parseInt(routeParams.parentId) : null,
|
parentId: routeParams.parentId ? parseInt(routeParams.parentId) : null,
|
||||||
|
is_parent: routeParams.is_parent ? routeParams.is_parent === "true" : false,
|
||||||
icon: routeParams.icon || undefined,
|
icon: routeParams.icon || undefined,
|
||||||
order: routeParams.order ? parseInt(routeParams.order) : undefined,
|
order: routeParams.order ? parseInt(routeParams.order) : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Parent-Tag-Liste erst hier berechnen, da currentTag.id jetzt bekannt ist
|
||||||
|
allParentTags = tagDaten.filter(t => t.id !== currentTag.id && t.is_parent);
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
return m("div.w3-container.w3-auto.form-view-container",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
@@ -533,14 +618,29 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
|||||||
m("label", "Tag-Name", m(Input, {name: "name", value: currentTag.name, type: "text"} as iInput)),
|
m("label", "Tag-Name", m(Input, {name: "name", value: currentTag.name, type: "text"} as iInput)),
|
||||||
m("label", "Beschreibung", m(Input, {name: "description", value: currentTag.description, type: "text"} as iInput)),
|
m("label", "Beschreibung", m(Input, {name: "description", value: currentTag.description, type: "text"} as iInput)),
|
||||||
|
|
||||||
|
// Parent-Tag-Umschalter: true = Gruppierungs-Knoten (nicht zuordenbar)
|
||||||
|
m("div", {style: {'margin-bottom': '5px'}},
|
||||||
|
m("label",
|
||||||
|
m("input.w3-check", {
|
||||||
|
type: "checkbox",
|
||||||
|
name: "is_parent",
|
||||||
|
checked: currentTag.is_parent,
|
||||||
|
style: {'margin-right': '8px'},
|
||||||
|
onclick: (e: Event) => {
|
||||||
|
currentTag.is_parent = (e.target as HTMLInputElement).checked;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
"Nur zur Gruppierung, kann nicht zugeordnet werden"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
// Eltern-Tag-Auswahl
|
// Eltern-Tag-Auswahl
|
||||||
m("label", "Übergeordneter Tag (optional)",
|
m("label", "Übergeordneter Tag (optional)"),
|
||||||
m("select.w3-select", {name: "parentId", value: currentTag.parentId || ""},
|
m("select.w3-select", {name: "parentId", value: currentTag.parentId || ""},
|
||||||
m("option", {value: ""}, "-- Kein übergeordneter Tag --"),
|
m("option", {value: ""}, "-- Kein übergeordneter Tag --"),
|
||||||
allTags.map(tag =>
|
allParentTags.map(tag =>
|
||||||
m("option", {value: tag.id, selected: currentTag.parentId === tag.id}, tag.name)
|
m("option", {value: tag.id, selected: currentTag.parentId === tag.id}, tag.name)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
|
|
||||||
m("label", "Farbe"),
|
m("label", "Farbe"),
|
||||||
@@ -559,9 +659,6 @@ export const Tag: m.ClosureComponent = (vnode) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
m(Input, {name: "color", value: currentTag.color, type: "text"} as iInput),
|
|
||||||
m("label", "Icon (optional)", m(Input, {name: "icon", value: currentTag.icon || "", type: "text"} as iInput)),
|
|
||||||
m("label", "Sortierreihenfolge (optional)", m(Input, {name: "order", value: currentTag.order?.toString() || "", type: "number"} as iInput)),
|
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -585,22 +682,26 @@ export const TagList: m.ClosureComponent = (vnode) => {
|
|||||||
|
|
||||||
const editTag = (tag: iTag, e: Event) => {
|
const editTag = (tag: iTag, e: Event) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
m.route.set("/tag", {
|
m.route.set("/tag", {id: tag.id});
|
||||||
id: tag.id,
|
|
||||||
name: tag.name,
|
|
||||||
description: tag.description,
|
|
||||||
color: tag.color,
|
|
||||||
parentId: tag.parentId,
|
|
||||||
icon: tag.icon,
|
|
||||||
order: tag.order
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
||||||
let tag: iTag = node.nodevalue;
|
let tag: iTag = node.nodevalue;
|
||||||
|
|
||||||
|
// Parent-Tags erhalten ein "folder"-Icon, echte Tags ein "label"-Icon
|
||||||
|
const tagIcon = tag.is_parent ? "folder" : "label";
|
||||||
|
const tagTitle = tag.is_parent ? "Parent-Tag (nicht zuordenbar)" : "";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
m("span",
|
m("span",
|
||||||
|
m("i.material-icons", {
|
||||||
|
style: {
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
marginRight: '6px',
|
||||||
|
fontSize: '18px'
|
||||||
|
},
|
||||||
|
title: tagTitle
|
||||||
|
}, tagIcon),
|
||||||
m("span", {
|
m("span", {
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: tag.color,
|
backgroundColor: tag.color,
|
||||||
@@ -610,7 +711,8 @@ export const TagList: m.ClosureComponent = (vnode) => {
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
marginRight: '8px'
|
marginRight: '8px'
|
||||||
}
|
},
|
||||||
|
title: tagTitle
|
||||||
}, tag.name)
|
}, tag.name)
|
||||||
),
|
),
|
||||||
m("span", tag.description),
|
m("span", tag.description),
|
||||||
@@ -625,10 +727,21 @@ export const TagList: m.ClosureComponent = (vnode) => {
|
|||||||
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
|
const filterFunction = (node: iTreenode, searchvalue: string): boolean => {
|
||||||
let tag: iTag = node.nodevalue;
|
let tag: iTag = node.nodevalue;
|
||||||
let searchLower = searchvalue.toLowerCase();
|
let searchLower = searchvalue.toLowerCase();
|
||||||
return tag.name.toLowerCase().includes(searchLower) ||
|
|
||||||
tag.description.toLowerCase().includes(searchLower) ||
|
// Prüft, ob der Tag selbst auf den Suchbegriff passt (nur Name und Beschreibung)
|
||||||
tag.color.toLowerCase().includes(searchLower) ||
|
const selfMatch = tag.name.toLowerCase().includes(searchLower) ||
|
||||||
(tag.icon && tag.icon.toLowerCase().includes(searchLower));
|
tag.description.toLowerCase().includes(searchLower);
|
||||||
|
|
||||||
|
// Wenn der Tag selbst passt, anzeigen
|
||||||
|
if (selfMatch) return true;
|
||||||
|
|
||||||
|
// Wenn der Tag Kinder hat, prüfen, ob ein Nachfahne passt
|
||||||
|
if (searchLower === "") return true;
|
||||||
|
const descendants = getDescendants(tag.id);
|
||||||
|
return descendants.some((d) =>
|
||||||
|
d.name.toLowerCase().includes(searchLower) ||
|
||||||
|
d.description.toLowerCase().includes(searchLower)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -675,4 +788,8 @@ export {
|
|||||||
addTag,
|
addTag,
|
||||||
updateTag,
|
updateTag,
|
||||||
getRootTags,
|
getRootTags,
|
||||||
|
getRealTags,
|
||||||
|
getRealDescendants,
|
||||||
|
resolveToRealTagIds,
|
||||||
|
isAssignable,
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user