From 5abfa96dc2cbe03d4165aeb62df7d80e220fa2ed Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 27 Aug 2025 23:54:48 +0200 Subject: [PATCH] implemented editing of times --- .../srsoftware/umbrella/core/model/Time.java | 25 ++++++--- frontend/src/Components/DateTimeEditor.svelte | 24 +++++++++ frontend/src/Components/LineEditor.svelte | 6 ++- frontend/src/routes/time/Index.svelte | 52 ++++++++++++++++++- .../srsoftware/umbrella/time/TimeModule.java | 27 +++++++++- 5 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 frontend/src/Components/DateTimeEditor.svelte 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 57a74d8..639d265 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 @@ -6,20 +6,24 @@ import static de.srsoftware.umbrella.core.Util.*; import static java.time.ZoneOffset.UTC; import de.srsoftware.tools.Mappable; +import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Duration; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; +import org.json.JSONObject; public class Time implements Mappable{ - + private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private final HashSet taskIds = new HashSet<>(); private LocalDateTime end; - private final LocalDateTime start; + private LocalDateTime start; private long id; private final long userId; - private final String description, subject; + private String description; + private String subject; private State state; public enum State{ @@ -49,9 +53,8 @@ public class Time implements Mappable{ default -> throw new IllegalArgumentException(); }; } - - } + public Time(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Collection taskIds){ this.id=id; this.userId = userId; @@ -62,6 +65,7 @@ public class Time implements Mappable{ this.state = state; if (taskIds != null) this.taskIds.addAll(taskIds); } + public String description(){ return description; } @@ -104,6 +108,15 @@ public class Time implements Mappable{ ); } + public Time patch(JSONObject json) { + if (json.has(SUBJECT) && json.get(SUBJECT) instanceof String s) subject = s; + if (json.has(DESCRIPTION) && json.get(DESCRIPTION) instanceof String d) description = d; + if (json.has(START_TIME) && json.get(START_TIME) instanceof String st) start = LocalDateTime.parse(st, DT); + if (json.has(END_TIME) && json.get(END_TIME) instanceof String e) end = LocalDateTime.parse(e,DT); + if (end != null && !start.isBefore(end)) throw UmbrellaException.invalidFieldException(END_TIME,"after start_time"); + return this; + } + public void setId(long newValue) { id = newValue; } @@ -117,7 +130,7 @@ public class Time implements Mappable{ } public State state(){ - return state; + return state; } public Time stop(LocalDateTime endTime) { diff --git a/frontend/src/Components/DateTimeEditor.svelte b/frontend/src/Components/DateTimeEditor.svelte new file mode 100644 index 0000000..b2d23af --- /dev/null +++ b/frontend/src/Components/DateTimeEditor.svelte @@ -0,0 +1,24 @@ + + + + +
+ + + +
\ No newline at end of file diff --git a/frontend/src/Components/LineEditor.svelte b/frontend/src/Components/LineEditor.svelte index 6d614ef..4e925c5 100644 --- a/frontend/src/Components/LineEditor.svelte +++ b/frontend/src/Components/LineEditor.svelte @@ -3,14 +3,15 @@ import { t } from '../translations.svelte.js'; let { - editable = false, + simple = false, + editable = simple, onclick = evt => { startEdit() }, onSet = newVal => {return true;}, type = 'div', value = $bindable(null) } = $props(); - let editing = $state(false); + let editing = $state(simple); let editValue = value; let start = 0; @@ -64,6 +65,7 @@ } activeField.subscribe((val) => resetEdit()); + if (simple) startEdit(); +

{t('timetracking')}

{#if error} {error} @@ -32,11 +60,32 @@ {#if times} - + + + + + + + {#each Object.entries(times) as [tid,time]} + {#if detail == tid} + + + + {:else} + {detail = tid}}> @@ -56,6 +105,7 @@ {t("state_"+time.state.name.toLowerCase())} + {/if} {/each}
{t('start_end')}{t('duration')}{t('subject')}{t('tasks')}{t('state')}
+
+ update(tid,'start_time',dateTime)} /> + … + update(tid,'end_time',dateTime)} /> +
+
+ update(tid,'subject',subject)} /> + update(tid,'description',desc)} /> +
{time.start_time}{#if time.end_time}…{time.end_time}{/if}
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 7c8e7f6..7e05ab2 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java @@ -77,7 +77,24 @@ public class TimeModule extends BaseHandler implements TimeService { return sendContent(ex,time); } - @Override + @Override + public boolean doPatch(Path path, HttpExchange ex) throws IOException { + addCors(ex); + try { + Optional token = SessionToken.from(ex).map(Token::of); + var user = userService().loadUser(token); + if (user.isEmpty()) return unauthorized(ex); + var head = path.pop(); + var timeId = Long.parseLong(head); + return patchTime(user.get(),timeId,ex); + } catch (NumberFormatException e){ + return send(ex,invalidFieldException(TIME_ID,"long value")); + } catch (UmbrellaException e){ + return send(ex,e); + } + } + + @Override public boolean doPost(Path path, HttpExchange ex) throws IOException { addCors(ex); try { @@ -101,6 +118,14 @@ public class TimeModule extends BaseHandler implements TimeService { .max(Comparator.comparing(Time::start)); } + private boolean patchTime(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException { + var time = timeDb.load(timeId); + if (time.userId() != user.id()) throw forbidden("You are not allowed to alter this time!"); + var json = json(ex); + timeDb.save(time.patch(json)); + return sendContent(ex,time); + } + private boolean trackTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException { if (path.empty()) throw missingFieldException(TASK_ID); Task task;