diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java index d7848b0..43f1bc9 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Time.java @@ -92,6 +92,10 @@ public class Time implements Mappable{ return start; } + public State state(){ + return state; + } + public Collection taskIds(){ return taskIds; } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 9f0d21a..7e171da 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -28,6 +28,7 @@ import TagUses from "./routes/tags/TagUses.svelte"; import TaskList from "./routes/task/Index.svelte"; import Times from "./routes/time/Index.svelte"; + import TimeTask from "./routes/time/AddTask.svelte"; import User from "./routes/user/User.svelte"; import ViewDoc from "./routes/document/View.svelte"; import ViewPrj from "./routes/project/View.svelte"; @@ -76,6 +77,7 @@ + diff --git a/frontend/src/routes/task/ListTask.svelte b/frontend/src/routes/task/ListTask.svelte index ed4093a..7fcaada 100644 --- a/frontend/src/routes/task/ListTask.svelte +++ b/frontend/src/routes/task/ListTask.svelte @@ -26,6 +26,10 @@ router.navigate(`/task/${task.id}/add_subtask`); } + function addTime(){ + router.navigate(`/time/add_task/${task.id}`); + } + async function deleteTask(){ if (confirm(t('confirm_delete',{element:task.name}))){ const url = api(`task/${task.id}`); @@ -147,7 +151,7 @@ {/if} - + {#if error} {error} {/if} diff --git a/frontend/src/routes/time/AddTask.svelte b/frontend/src/routes/time/AddTask.svelte new file mode 100644 index 0000000..9718fc2 --- /dev/null +++ b/frontend/src/routes/time/AddTask.svelte @@ -0,0 +1,16 @@ + + +{t('timetracking')} \ No newline at end of file diff --git a/time/src/main/java/de/srsoftware/umbrella/time/Constants.java b/time/src/main/java/de/srsoftware/umbrella/time/Constants.java index 8526d5c..91d249e 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/Constants.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/Constants.java @@ -5,6 +5,7 @@ public class Constants { private Constants(){} + public static final String ADD_TASK = "add_task"; public static final String CONFIG_DATABASE = "umbrella.modules.time.database"; public static final String TABLE_TASK_TIMES = "task_times"; diff --git a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java index e6851d5..6552dd2 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -5,9 +5,13 @@ import static de.srsoftware.tools.jdbc.Condition.*; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.core.Constants.*; +import static de.srsoftware.umbrella.core.model.Status.OPEN; import static de.srsoftware.umbrella.core.model.Time.State.Complete; import static de.srsoftware.umbrella.time.Constants.*; +import static java.lang.System.Logger.Level.ERROR; +import static java.text.MessageFormat.format; +import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Time; import java.sql.Connection; @@ -16,15 +20,70 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -public class SqliteDb implements TimeDb { +public class SqliteDb extends BaseDb implements TimeDb { - private final Connection db; - public SqliteDb(Connection connection) { - db = connection; - } + public SqliteDb(Connection connection) { + super(connection); + } - @Override + @Override + public Time createNew(long userId, String subject, String description) { + return null; + } + + @Override + protected int createTables() { + int currentVersion = createSettingsTable(); + switch (currentVersion){ + case 0: + createTimesTable(); + createTaskTimesTable(); + } + return setCurrentVersion(1); + } + + private void createTaskTimesTable() { + var sql = """ +CREATE TABLE IF NOT EXISTS {0} ( + {1} INT NOT NULL, + {2} INT NOT NULL, + PRIMARY KEY({1}, {2}) +)"""; + sql = format(sql,TABLE_TASK_TIMES,TASK_ID,TIME_ID); + try { + var stmt = db.prepareStatement(sql); + stmt.execute(); + stmt.close(); + } catch (SQLException e) { + LOG.log(ERROR, ERROR_FAILED_CREATE_TABLE, TABLE_TASK_TIMES, e); + throw new RuntimeException(e); + } + } + + private void createTimesTable() { + var sql = """ +CREATE TABLE IF NOT EXISTS {0} ( + {1} INTEGER PRIMARY KEY, + {2} INTEGER NOT NULL, + {3} VARCHAR(255) NOT NULL, + {4} TEXT, + {5} TIMESTAMP, + {6} TIMESTAMP, + {7} INT NOT NULL DEFAULT {8} +)"""; + sql = format(sql,TABLE_TIMES,ID,USER_ID,SUBJECT,DESCRIPTION,START_TIME,END_TIME,STATE, Time.State.Started.code()); + try { + var stmt = db.prepareStatement(sql); + stmt.execute(); + stmt.close(); + } catch (SQLException e) { + LOG.log(ERROR, ERROR_FAILED_CREATE_TABLE, TABLE_TIMES, e); + throw new RuntimeException(e); + } + } + + @Override public HashMap listTimes(Collection taskIds, boolean showClosed) throws UmbrellaException { try { var rs = select(ALL).from(TABLE_TASK_TIMES).where(TASK_ID,in(taskIds.toArray())).exec(db); @@ -47,6 +106,7 @@ public class SqliteDb implements TimeDb { rs.close(); return times; } catch (Exception e) { + throw new UmbrellaException("Failed to load times for task list"); } } @@ -67,7 +127,6 @@ public class SqliteDb implements TimeDb { while (rs.next()){ var time = times.get(rs.getLong(TIME_ID)); time.taskIds().add(rs.getLong(TASK_ID)); - System.out.println(time); } rs.close(); return times; diff --git a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java index 8815039..05d1963 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeDb.java @@ -7,6 +7,8 @@ import java.util.Collection; import java.util.HashMap; public interface TimeDb { + Time createNew(long userId, String subject, String description); + HashMap listTimes(Collection taskIds, boolean showClosed) throws UmbrellaException; HashMap listUserTimes(long userId, boolean showClosed); diff --git a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java index 116ce1e..86ea140 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java @@ -4,9 +4,10 @@ package de.srsoftware.umbrella.time; import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.Constants.*; import static de.srsoftware.umbrella.core.Paths.LIST; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden; -import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException; +import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; +import static de.srsoftware.umbrella.core.model.Time.State.Started; import static de.srsoftware.umbrella.time.Constants.*; +import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import com.sun.net.httpserver.HttpExchange; @@ -21,6 +22,7 @@ import de.srsoftware.umbrella.core.model.*; import java.io.IOException; import java.time.LocalDateTime; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; public class TimeModule extends BaseHandler implements TimeService { @@ -55,6 +57,7 @@ public class TimeModule extends BaseHandler implements TimeService { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head) { + case ADD_TASK -> getAddTask(user.get(),path,ex); case null -> getUserTimes(user.get(),ex); default -> super.doGet(path,ex); }; @@ -63,7 +66,7 @@ public class TimeModule extends BaseHandler implements TimeService { } } - @Override + @Override public boolean doPost(Path path, HttpExchange ex) throws IOException { addCors(ex); try { @@ -80,7 +83,31 @@ public class TimeModule extends BaseHandler implements TimeService { } } - private boolean getUserTimes(UmbrellaUser user, HttpExchange ex) throws IOException { + private boolean getAddTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException { + if (path.empty()) throw missingFieldException(TASK_ID); + long taskId; + try { + taskId = Long.parseLong(path.pop()); + } catch (NumberFormatException e) { + throw invalidFieldException(TASK_ID,"long value"); + } + var taskIds = new HashSet(); + var times = timeDb.listUserTimes(user.id(), false).values().stream() + .filter(time -> time.state() == Started) + .peek(time -> taskIds.addAll(time.taskIds())) + .collect(Collectors.toMap(Time::id, t -> t)); + taskIds.add(taskId); + var tasks = taskService().load(taskIds); + if (times.isEmpty()){ + var task = tasks.get(taskId); + if (task == null) throw UmbrellaException.notFound("Failed to find task with id = {0}",taskId); + var time = timeDb.createNew(user.id(),task.name(),task.description()); + times.put(time.id(),time); + } + return sendContent(ex,Map.of(TIMES,times,TASKS,tasks)); + } + + private boolean getUserTimes(UmbrellaUser user, HttpExchange ex) throws IOException { Set taskIds = new HashSet<>(); Map projects = projectService().listUserProjects(user.id(), true); for (var pid : projects.keySet()) taskIds.addAll(taskService().listProjectTasks(pid).keySet());