From ccf8fc208909af8125c4c267b07a07d46eecec81 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Thu, 14 May 2026 00:32:10 +0200 Subject: [PATCH] implemented selector for parent task Signed-off-by: Stephan Richter --- .../umbrella/core/constants/Field.java | 1 + .../umbrella/core/constants/Path.java | 15 ++++---- .../umbrella/core/model/Project.java | 5 +++ .../src/routes/task/ParentSelector.svelte | 27 ++++++++++++++ frontend/src/routes/task/Tree.svelte | 23 ++++++++++++ frontend/src/routes/task/View.svelte | 37 ++++++++++++++----- .../de/srsoftware/umbrella/task/SqliteDb.java | 3 +- .../srsoftware/umbrella/task/TaskModule.java | 37 +++++++++++++++++-- translations/src/main/resources/de.json | 1 + translations/src/main/resources/en.json | 1 + web/src/main/resources/web/css/default.css | 9 +++++ 11 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 frontend/src/routes/task/ParentSelector.svelte create mode 100644 frontend/src/routes/task/Tree.svelte diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java index 5331e28c..c2e35b0a 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Field.java @@ -15,6 +15,7 @@ public class Field { public static final String BODY = "body"; public static final String CACHE_CONTROL = "Cache-Control"; + public static final String CHILDREN = "Children"; public static final String CODE = "code"; public static final String COMMENT = "comment"; public static final String COMPANY = "company"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java b/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java index 20664737..6312c9d2 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/constants/Path.java @@ -33,13 +33,14 @@ public class Path { public static final String OPTION = "option"; - public static final String PAGE = "page"; - public static final String PASSWORD = "password"; - public static final String PERMISSIONS = "permissions"; - public static final String PROJECT = "project"; - public static final String PROPERTIES = "properties"; - public static final String PROPERTY = "property"; - public static final String PURPOSES = "purposes"; + public static final String PAGE = "page"; + public static final String PARENT_CANDIDATES = "parent_candidates"; + public static final String PASSWORD = "password"; + public static final String PERMISSIONS = "permissions"; + public static final String PROJECT = "project"; + public static final String PROPERTIES = "properties"; + public static final String PROPERTY = "property"; + public static final String PURPOSES = "purposes"; public static final String READ = "read"; public static final String REDIRECT = "redirect"; diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java index 42e81dbb..48a5cce6 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java @@ -144,4 +144,9 @@ public class Project implements Mappable { map.put(TAG_COLORS,tagColors); return map; } + + @Override + public String toString() { + return name(); + } } diff --git a/frontend/src/routes/task/ParentSelector.svelte b/frontend/src/routes/task/ParentSelector.svelte new file mode 100644 index 00000000..65ba8235 --- /dev/null +++ b/frontend/src/routes/task/ParentSelector.svelte @@ -0,0 +1,27 @@ + + +
+

{t('select a new parent for {entity}',{entity:task.name})}

+ {t('project')}: {project.name} + +
\ No newline at end of file diff --git a/frontend/src/routes/task/Tree.svelte b/frontend/src/routes/task/Tree.svelte new file mode 100644 index 00000000..5d97d05e --- /dev/null +++ b/frontend/src/routes/task/Tree.svelte @@ -0,0 +1,23 @@ + + + \ No newline at end of file diff --git a/frontend/src/routes/task/View.svelte b/frontend/src/routes/task/View.svelte index 705baf12..7af1831c 100644 --- a/frontend/src/routes/task/View.svelte +++ b/frontend/src/routes/task/View.svelte @@ -10,6 +10,7 @@ import LineEditor from '../../Components/LineEditor.svelte'; import MarkdownEditor from '../../Components/MarkdownEditor.svelte'; + import ParentSelector from './ParentSelector.svelte'; import PermissionEditor from '../../Components/PermissionEditor.svelte'; import Notes from '../notes/RelatedNotes.svelte'; import StateSelector from '../../Components/StateSelector.svelte'; @@ -22,6 +23,7 @@ let children = $state(null); let dummy = $derived(updateOn(id)); let est_time = $state({sum:0}); + let select_parent = $state(false); let project = $state(null); const router = useTinyRouter(); let showSettings = $state(router.fullPath.endsWith('/edit')); @@ -74,12 +76,6 @@ router.navigate(`/project/${project.id}/kanban`) } - - function gotoParent(){ - if (!task.parent_task_id) return; - router.navigate(`/task/${task.parent_task_id}/view`) - } - function gotoProject(){ if (!project) return; router.navigate(`/project/${project.id}/view`) @@ -136,6 +132,19 @@ } else error(await resp.text()); } + function parentClick(ev){ + ev.preventDefault(); + if (!task.parent_task_id) return; + router.navigate(`/task/${task.parent_task_id}/view`); + return false; + } + + function parentRightClick(ev){ + ev.preventDefault(); + select_parent = true; + return false; + } + function showClosed(){ show_closed = !show_closed; children = null; @@ -182,6 +191,11 @@ loadTask(); } + function update_parent(newVal){ + select_parent = false; + update({parent_task_id:newVal.id}); + } + function updatePermission(user_id,permission){ let members = {}; members[user_id] = permission.code; @@ -207,13 +221,18 @@ {/if} - {#if task.parent}
{t('parent_task')}
- {task.parent.name} + {#if select_parent} + + {:else} + {#if task.parent} + {task.parent.name} + {/if} + + {/if}
- {/if}
{t('task')}
update({name:val})} /> diff --git a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java index b068c832..22926974 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -2,6 +2,7 @@ package de.srsoftware.umbrella.task; +import static de.srsoftware.tools.Optionals.is0; import static de.srsoftware.tools.jdbc.Condition.*; import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; @@ -236,7 +237,7 @@ CREATE TABLE IF NOT EXISTS {0} ( public Map listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException { try { var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId)); - if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId)); + if (!is0(parentTaskId)) query.where(PARENT_TASK_ID,equal(parentTaskId)); if (!noIndex) query.where(NO_INDEX,notIn(1)); var tasks = new HashMap(); var rs = query.exec(db); diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java index 1b013b87..23ea3b12 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -32,6 +32,7 @@ import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.api.*; +import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.*; @@ -110,8 +111,11 @@ public class TaskModule extends BaseHandler implements TaskService { case null -> getUserTasks(user.get(), ex); default -> { var taskId = Long.parseLong(head); - head = path.pop(); - yield head == null ? getTask(ex, taskId, user.get()) : super.doGet(path, ex); + yield switch (path.pop()){ + case null -> getTask(ex,taskId,user.get()); + case PARENT_CANDIDATES -> getParentCandidates(ex,taskId, user.get()); + default -> super.doGet(path,ex); + }; } }; } catch (UmbrellaException e) { @@ -188,6 +192,33 @@ public class TaskModule extends BaseHandler implements TaskService { return sendContent(ex, result); } + private boolean getParentCandidates(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException { + var task = taskDb.load(taskId); + var project = projectService().load(task.projectId()); + var projectTasks = taskDb.listProjectTasks(project.id(),null, false); + var mapped = projectTasks.values().stream().collect(Collectors.toMap(Task::id,Task::toMap)); + var roots = new HashMap>(); + for (var map : mapped.values()){ + if (!(map.get(ID) instanceof Long id)) continue; + if (id == taskId) continue; + if (map.get(PARENT_TASK_ID) instanceof Long parentId) { + var parent = mapped.get(parentId); + if (parent != null) { + var o = parent.get(Field.CHILDREN); + Map children; + if (o == null) { + children = new HashMap<>(); + parent.put(Field.CHILDREN,children); + } else children = (Map) o; + children.put(id, map); + } + } else { + roots.put(id, map); + } + } + return sendContent(ex,roots); + } + private boolean getPermissionList(HttpExchange ex) throws IOException { var map = new HashMap(); for (var permission : Permission.values()) map.put(permission.code(),permission.name()); @@ -293,7 +324,7 @@ public class TaskModule extends BaseHandler implements TaskService { Task parent = taskMap.get(task.parentTaskId()); var trunk = placeInTree(parent, taskTree, taskMap); @SuppressWarnings("unchecked") - ArrayList children = (ArrayList) trunk.computeIfAbsent(CHILDREN, k -> new ArrayList<>()); + ArrayList children = (ArrayList) trunk.computeIfAbsent(Field.CHILDREN, k -> new ArrayList<>()); children.add(mappedTask); return mappedTask; } diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index dfd27de6..5ce57318 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -325,6 +325,7 @@ "save_object": "{object} speichern", "search": "Suche", "searching…": "suche…", + "select a new parent for {entity}": "Neue Über-Aufgabe für „{entity}“ wählen", "select_company" : "Wählen Sie eine ihrer Firmen:", "select_customer": "Kunde auswählen", "select_property": "Eigenschaft auswählen", diff --git a/translations/src/main/resources/en.json b/translations/src/main/resources/en.json index d66f548f..44c938f3 100644 --- a/translations/src/main/resources/en.json +++ b/translations/src/main/resources/en.json @@ -325,6 +325,7 @@ "save_object": "save {object}", "search": "search", "searching…": "searhcing…", + "select a new parent for {entity}": "select a new parent for '{entity}'", "select_company" : "select on of you companies:", "select_customer": "select customer", "select_property": "select property", diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 0f5c9bca..eacb0939 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -761,3 +761,12 @@ fieldset.vcard{ white-space: nowrap; display: inline flow-root; } + +.parent_selector > ul { + position: absolute; + top: 120px; + left: 0; + right: 0; + bottom: 0; + overflow: auto; +} \ No newline at end of file