diff --git a/frontend/src/routes/project/List.svelte b/frontend/src/routes/project/List.svelte index b8312c0..cfdc338 100644 --- a/frontend/src/routes/project/List.svelte +++ b/frontend/src/routes/project/List.svelte @@ -94,8 +94,7 @@ {/if} show(project.id)} > - - {t("state_"+project.allowed_states[project.status].toLowerCase())} + {t("state_"+project.allowed_states[project.status]?.toLowerCase())} show(project.id)} > {#each Object.entries(project.members) as [uid,member]} diff --git a/frontend/src/routes/task/Index.svelte b/frontend/src/routes/task/Index.svelte index 5cf5ae8..1188b08 100644 --- a/frontend/src/routes/task/Index.svelte +++ b/frontend/src/routes/task/Index.svelte @@ -9,6 +9,10 @@ let projects = $state({}); let router = useTinyRouter(); let tasks = $state(null); + let bounds = $state({offset:0,limit:256}); + let loading = $state(true); + let map = $state({}); + let hidden = $state({}); async function changeState(tid,state){ const task = tasks[tid]; @@ -43,24 +47,37 @@ router.navigate(`/${module}/${id}/view`); } - async function load(){ - let url = api('task'); - let resp = await fetch(url,{credentials:'include'}); - let project_ids = {}; + async function loadProject(pid){ + const url = api(`project/${pid}`); + const resp = await fetch(url,{credentials:'include'}); if (resp.ok){ - tasks = await resp.json(); - for (let task of (Object.values(tasks))) project_ids[task.project_id] = true; + projects[pid] = await resp.json(); } else { error = await resp.text(); } - for (let pid of Object.keys(project_ids)){ - url = api(`project/${pid}`); - resp = await fetch(url,{credentials:'include'}); - if (resp.ok){ - projects[pid] = await resp.json(); - } else { - error = await resp.text(); + } + + async function load(){ + const url = api(`task?offset=${bounds.offset}&limit=${bounds.limit}`); + const resp = await fetch(url,{credentials:'include'}); + if (resp.ok){ + let newTasks = await resp.json(); + if (bounds.offset == 0) { + tasks = newTasks; + } else tasks = tasks.concat(newTasks); + loading = newTasks.length; + if (loading){ + for (let task of newTasks) { + map[task.id] = task; + if (task.parent_task_id) hidden[task.parent_task_id] = true; + let pid = task.project_id; + if (pid && !projects[pid]) await loadProject(pid); + } + bounds.offset += bounds.limit; + load(); } + } else { + error = await resp.text(); } } @@ -80,7 +97,7 @@
- {t('task_list')} + {loading ? t('loading_object',{object:t('task_list')}) : t('task_list')} {#if error} {error} {/if} @@ -97,14 +114,14 @@ - {#each Object.entries(tasks) as [tid,task]} - {#if task.status < 60} + {#each tasks as task (task.id)} + {#if task.status > 10 && task.status < 60 && !task.no_index && projects[task.project_id]?.status < 60 && !hidden[task.id]} - go('task',tid)}>{task.name} + go('task',task.id)}>{task.name} go('project',task.project_id)}> {projects[task.project_id]?.name} {#if task.parent_task_id} - : go('task',task.parent_task_id)}>{tasks[task.parent_task_id]?.name} + : go('task',task.parent_task_id)}>{map[task.parent_task_id]?.name} {/if} diff --git a/legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java b/legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java index 1bd6384..6ebc4a6 100644 --- a/legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java +++ b/legacy/src/main/java/de/srsoftware/umbrella/legacy/CompanyLegacy.java @@ -2,6 +2,11 @@ package de.srsoftware.umbrella.legacy; +import static de.srsoftware.tools.Optionals.nullable; +import static de.srsoftware.umbrella.core.Constants.TOKEN; +import static de.srsoftware.umbrella.core.Paths.JSON; +import static de.srsoftware.umbrella.core.Util.mapValues; + import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; import de.srsoftware.tools.Path; @@ -10,17 +15,10 @@ import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.UmbrellaUser; - import java.io.IOException; import java.util.Map; import java.util.Optional; -import static de.srsoftware.tools.Optionals.nullable; -import static de.srsoftware.umbrella.core.Constants.TOKEN; -import static de.srsoftware.umbrella.core.Constants.USERS; -import static de.srsoftware.umbrella.core.Paths.JSON; -import static de.srsoftware.umbrella.core.Util.mapValues; - public class CompanyLegacy extends BaseHandler { private final ModuleRegistry registry; private final Configuration config; diff --git a/legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java b/legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java index 11e1fae..5d1e14d 100644 --- a/legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java +++ b/legacy/src/main/java/de/srsoftware/umbrella/legacy/ProjectLegacy.java @@ -6,7 +6,6 @@ import static de.srsoftware.tools.Optionals.nullable; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Paths.JSON; import static de.srsoftware.umbrella.core.Util.mapValues; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException; import com.sun.net.httpserver.HttpExchange; import de.srsoftware.configuration.Configuration; @@ -15,13 +14,11 @@ import de.srsoftware.tools.SessionToken; import de.srsoftware.umbrella.core.BaseHandler; import de.srsoftware.umbrella.core.ModuleRegistry; import de.srsoftware.umbrella.core.model.Token; +import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.io.IOException; import java.util.Map; import java.util.Optional; -import de.srsoftware.umbrella.core.model.UmbrellaUser; -import org.json.JSONObject; - public class ProjectLegacy extends BaseHandler { private final ModuleRegistry registry; private final Configuration config; 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 5fde7a2..722fd98 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java @@ -7,7 +7,7 @@ import static de.srsoftware.tools.jdbc.Query.*; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException; -import static de.srsoftware.umbrella.core.model.Status.OPEN; +import static de.srsoftware.umbrella.core.model.Status.*; import static de.srsoftware.umbrella.project.Constants.*; import static de.srsoftware.umbrella.task.Constants.*; import static java.lang.System.Logger.Level.*; @@ -20,9 +20,7 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.*; import java.sql.Connection; import java.sql.SQLException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; public class SqliteDb extends BaseDb implements TaskDb { @@ -158,7 +156,7 @@ CREATE TABLE IF NOT EXISTS {0} ( .where(PROJECT_ID,equal(projectId)) .where(USER_ID,equal(user.id())) .where(PARENT_TASK_ID,isNull()); - if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code())); + if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code())); var rs = query.exec(db); while (rs.next()){ var task = Task.of(rs); @@ -178,7 +176,7 @@ CREATE TABLE IF NOT EXISTS {0} ( var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID) .where(PARENT_TASK_ID,equal(parentTaskId)) .where(USER_ID,equal(user.id())); - if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code())); + if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code())); var rs = query.exec(db); while (rs.next()){ var task = Task.of(rs); @@ -197,7 +195,7 @@ CREATE TABLE IF NOT EXISTS {0} ( try { var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId)); if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId)); - if (noIndex) query.where(NO_INDEX,equal(false)); + if (!noIndex) query.where(NO_INDEX,notIn(1)); var tasks = new HashMap(); var rs = query.exec(db); while (rs.next()){ @@ -212,14 +210,13 @@ CREATE TABLE IF NOT EXISTS {0} ( } @Override - public HashMap listUserTasks(long userId) { + public List listUserTasks(long userId, Long limit, long offset, boolean showClosed) { try { - var rs = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId)).exec(db); - var map = new HashMap(); - while (rs.next()) { - var task = Task.of(rs); - map.put(task.id(),task); - } + var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId)); + if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code())); + var rs = query.sort("(CASE due_date WHEN \"\" THEN '9999-99-99' ELSE IFNULL(due_date,'9999-99-99') END), status COLLATE NOCASE").limit(limit).skip(offset).exec(db); + var map = new ArrayList(); + while (rs.next()) map.add(Task.of(rs)); rs.close(); return map; } catch (SQLException e) { diff --git a/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java b/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java index 96e3b72..89a6ee5 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskDb.java @@ -8,6 +8,7 @@ import de.srsoftware.umbrella.core.model.Task; import de.srsoftware.umbrella.core.model.UmbrellaUser; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; public interface TaskDb { @@ -19,7 +20,7 @@ public interface TaskDb { HashMap listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException; HashMap listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed); HashMap listTasks(Collection projectIds) throws UmbrellaException; - HashMap listUserTasks(long userId); + List listUserTasks(long userId, Long limit, long offset, boolean showClosed); Task load(long taskId) throws UmbrellaException; 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 c203470..c09bf76 100644 --- a/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java +++ b/task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java @@ -183,8 +183,25 @@ public class TaskModule extends BaseHandler implements TaskService { } private boolean getUserTasks(UmbrellaUser user, HttpExchange ex) throws IOException { - var list = taskDb.listUserTasks(user.id()); - return sendContent(ex,mapValues(list)); + long offset = 0; + Long limit = null; + var params = queryParam(ex); + if (params.get(OFFSET) instanceof String o) try { + offset = Long.parseLong(o); + } catch (NumberFormatException e) { + throw invalidFieldException(OFFSET,"number"); + } + if (params.get(LIMIT) instanceof String l) try { + limit = Long.parseLong(l); + } catch (NumberFormatException e) { + throw invalidFieldException(LIMIT,"number"); + } + Set projectIds = registry.projectService().listUserProjects(user.id(), true).keySet(); + var list = taskDb.listUserTasks(user.id(), limit, offset, false).stream() + .filter(task -> projectIds.contains(task.projectId())) // drop tasks assigned to project we are not member of + .map(Task::toMap) + .toList(); + return sendContent(ex,list); } @Override diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index b908391..8ae5a2a 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -103,6 +103,7 @@ "LIST_USERS": "Nutzer auflisten", "loading": "lade…", "loading_data": "Daten werden geladen…", + "loading_object": "lade {object}…", "local_court": "Amtsgericht", "login" : "Anmeldung", "login_services": "Login-Services",