improved task list
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -94,8 +94,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="state" onclick={() => show(project.id)} >
|
<td class="state" onclick={() => show(project.id)} >
|
||||||
|
{t("state_"+project.allowed_states[project.status]?.toLowerCase())}
|
||||||
{t("state_"+project.allowed_states[project.status].toLowerCase())}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="members" onclick={() => show(project.id)} >
|
<td class="members" onclick={() => show(project.id)} >
|
||||||
{#each Object.entries(project.members) as [uid,member]}
|
{#each Object.entries(project.members) as [uid,member]}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
let projects = $state({});
|
let projects = $state({});
|
||||||
let router = useTinyRouter();
|
let router = useTinyRouter();
|
||||||
let tasks = $state(null);
|
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){
|
async function changeState(tid,state){
|
||||||
const task = tasks[tid];
|
const task = tasks[tid];
|
||||||
@@ -43,25 +47,38 @@
|
|||||||
router.navigate(`/${module}/${id}/view`);
|
router.navigate(`/${module}/${id}/view`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(){
|
async function loadProject(pid){
|
||||||
let url = api('task');
|
const url = api(`project/${pid}`);
|
||||||
let resp = await fetch(url,{credentials:'include'});
|
const resp = await fetch(url,{credentials:'include'});
|
||||||
let project_ids = {};
|
|
||||||
if (resp.ok){
|
|
||||||
tasks = await resp.json();
|
|
||||||
for (let task of (Object.values(tasks))) project_ids[task.project_id] = true;
|
|
||||||
} 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){
|
if (resp.ok){
|
||||||
projects[pid] = await resp.json();
|
projects[pid] = await resp.json();
|
||||||
} else {
|
} else {
|
||||||
error = await resp.text();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function open(tid){
|
function open(tid){
|
||||||
@@ -80,7 +97,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t('task_list')}</legend>
|
<legend>{loading ? t('loading_object',{object:t('task_list')}) : t('task_list')}</legend>
|
||||||
{#if error}
|
{#if error}
|
||||||
<soan class="error">{error}</soan>
|
<soan class="error">{error}</soan>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -97,14 +114,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each Object.entries(tasks) as [tid,task]}
|
{#each tasks as task (task.id)}
|
||||||
{#if task.status < 60}
|
{#if task.status > 10 && task.status < 60 && !task.no_index && projects[task.project_id]?.status < 60 && !hidden[task.id]}
|
||||||
<tr>
|
<tr>
|
||||||
<td onclick={() => go('task',tid)}>{task.name}</td>
|
<td onclick={() => go('task',task.id)}>{task.name}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" onclick={() => go('project',task.project_id)}> {projects[task.project_id]?.name}</a>
|
<a href="#" onclick={() => go('project',task.project_id)}> {projects[task.project_id]?.name}</a>
|
||||||
{#if task.parent_task_id}
|
{#if task.parent_task_id}
|
||||||
: <a href="#" onclick={() => go('task',task.parent_task_id)}>{tasks[task.parent_task_id]?.name}</a>
|
: <a href="#" onclick={() => go('task',task.parent_task_id)}>{map[task.parent_task_id]?.name}</a>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
package de.srsoftware.umbrella.legacy;
|
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 com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
import de.srsoftware.tools.Path;
|
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.ModuleRegistry;
|
||||||
import de.srsoftware.umbrella.core.model.Token;
|
import de.srsoftware.umbrella.core.model.Token;
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
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 {
|
public class CompanyLegacy extends BaseHandler {
|
||||||
private final ModuleRegistry registry;
|
private final ModuleRegistry registry;
|
||||||
private final Configuration config;
|
private final Configuration config;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import static de.srsoftware.tools.Optionals.nullable;
|
|||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Paths.JSON;
|
import static de.srsoftware.umbrella.core.Paths.JSON;
|
||||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
import static de.srsoftware.umbrella.core.Util.mapValues;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
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.BaseHandler;
|
||||||
import de.srsoftware.umbrella.core.ModuleRegistry;
|
import de.srsoftware.umbrella.core.ModuleRegistry;
|
||||||
import de.srsoftware.umbrella.core.model.Token;
|
import de.srsoftware.umbrella.core.model.Token;
|
||||||
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class ProjectLegacy extends BaseHandler {
|
public class ProjectLegacy extends BaseHandler {
|
||||||
private final ModuleRegistry registry;
|
private final ModuleRegistry registry;
|
||||||
private final Configuration config;
|
private final Configuration config;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import static de.srsoftware.tools.jdbc.Query.*;
|
|||||||
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.databaseException;
|
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.project.Constants.*;
|
||||||
import static de.srsoftware.umbrella.task.Constants.*;
|
import static de.srsoftware.umbrella.task.Constants.*;
|
||||||
import static java.lang.System.Logger.Level.*;
|
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 de.srsoftware.umbrella.core.model.*;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
public class SqliteDb extends BaseDb implements TaskDb {
|
public class SqliteDb extends BaseDb implements TaskDb {
|
||||||
@@ -158,7 +156,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
.where(PROJECT_ID,equal(projectId))
|
.where(PROJECT_ID,equal(projectId))
|
||||||
.where(USER_ID,equal(user.id()))
|
.where(USER_ID,equal(user.id()))
|
||||||
.where(PARENT_TASK_ID,isNull());
|
.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);
|
var rs = query.exec(db);
|
||||||
while (rs.next()){
|
while (rs.next()){
|
||||||
var task = Task.of(rs);
|
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)
|
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
|
||||||
.where(PARENT_TASK_ID,equal(parentTaskId))
|
.where(PARENT_TASK_ID,equal(parentTaskId))
|
||||||
.where(USER_ID,equal(user.id()));
|
.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);
|
var rs = query.exec(db);
|
||||||
while (rs.next()){
|
while (rs.next()){
|
||||||
var task = Task.of(rs);
|
var task = Task.of(rs);
|
||||||
@@ -197,7 +195,7 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
try {
|
try {
|
||||||
var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId));
|
var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId));
|
||||||
if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId));
|
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<Long,Task>();
|
var tasks = new HashMap<Long,Task>();
|
||||||
var rs = query.exec(db);
|
var rs = query.exec(db);
|
||||||
while (rs.next()){
|
while (rs.next()){
|
||||||
@@ -212,14 +210,13 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Long, Task> listUserTasks(long userId) {
|
public List<Task> listUserTasks(long userId, Long limit, long offset, boolean showClosed) {
|
||||||
try {
|
try {
|
||||||
var rs = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId)).exec(db);
|
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID).where(USER_ID,equal(userId));
|
||||||
var map = new HashMap<Long,Task>();
|
if (!showClosed) query.where(STATUS,lessThan(COMPLETE.code()));
|
||||||
while (rs.next()) {
|
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 task = Task.of(rs);
|
var map = new ArrayList<Task>();
|
||||||
map.put(task.id(),task);
|
while (rs.next()) map.add(Task.of(rs));
|
||||||
}
|
|
||||||
rs.close();
|
rs.close();
|
||||||
return map;
|
return map;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import de.srsoftware.umbrella.core.model.Task;
|
|||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface TaskDb {
|
public interface TaskDb {
|
||||||
@@ -19,7 +20,7 @@ public interface TaskDb {
|
|||||||
HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException;
|
HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId, boolean noIndex) throws UmbrellaException;
|
||||||
HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed);
|
HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed);
|
||||||
HashMap<Long, Task> listTasks(Collection<Long> projectIds) throws UmbrellaException;
|
HashMap<Long, Task> listTasks(Collection<Long> projectIds) throws UmbrellaException;
|
||||||
HashMap<Long, Task> listUserTasks(long userId);
|
List<Task> listUserTasks(long userId, Long limit, long offset, boolean showClosed);
|
||||||
|
|
||||||
Task load(long taskId) throws UmbrellaException;
|
Task load(long taskId) throws UmbrellaException;
|
||||||
|
|
||||||
|
|||||||
@@ -183,8 +183,25 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean getUserTasks(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean getUserTasks(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
var list = taskDb.listUserTasks(user.id());
|
long offset = 0;
|
||||||
return sendContent(ex,mapValues(list));
|
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<Long> 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
|
@Override
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
"LIST_USERS": "Nutzer auflisten",
|
"LIST_USERS": "Nutzer auflisten",
|
||||||
"loading": "lade…",
|
"loading": "lade…",
|
||||||
"loading_data": "Daten werden geladen…",
|
"loading_data": "Daten werden geladen…",
|
||||||
|
"loading_object": "lade {object}…",
|
||||||
"local_court": "Amtsgericht",
|
"local_court": "Amtsgericht",
|
||||||
"login" : "Anmeldung",
|
"login" : "Anmeldung",
|
||||||
"login_services": "Login-Services",
|
"login_services": "Login-Services",
|
||||||
|
|||||||
Reference in New Issue
Block a user