diff --git a/frontend/src/Components/TimeRecordEditor.svelte b/frontend/src/Components/TimeRecordEditor.svelte index 269091a..9b0f88b 100644 --- a/frontend/src/Components/TimeRecordEditor.svelte +++ b/frontend/src/Components/TimeRecordEditor.svelte @@ -4,7 +4,17 @@ import MarkdownEditor from './MarkdownEditor.svelte'; import TimeStampInput from './TimeStampInput.svelte'; - let { record = null, onSet = time => {} } = $props(); + let { record = null, onAbort = () => {}, onDrop = time_id => {}, onSet = time => {} } = $props(); + + function cancel(e){ + e.preventDefault(); + onAbort(); + } + + function drop(e){ + e.preventDefault(); + if (confirm(t('confirm_delete',{element:record.subject}))) onDrop(record.id); + } function onsubmit(e){ e.preventDefault(); @@ -14,7 +24,7 @@ {#if record}
-
+
{t('edit_object',{object:t('record')})} + +
{record.description.source} diff --git a/frontend/src/routes/time/Index.svelte b/frontend/src/routes/time/Index.svelte index d32bc1c..f7b69dd 100644 --- a/frontend/src/routes/time/Index.svelte +++ b/frontend/src/routes/time/Index.svelte @@ -64,6 +64,24 @@ } } + function onAbort(){ + detail = null; + } + + async function onDrop(time_id){ + const url = api(`time/${time_id}`); + const res = await fetch(url,{ + credentials:'include', + method:'DELETE' + }); + if (res.ok){ + delete times[time_id]; + error = false; + } else { + error = await res.text(); + } + } + function openTask(tid){ router.navigate(`/task/${tid}/view`); } @@ -148,7 +166,7 @@ {/if} {#if detail == time.id} - + {:else} toggleSelect(time.id)}> 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 e040063..c2bda8c 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/SqliteDb.java @@ -10,6 +10,7 @@ import static de.srsoftware.umbrella.time.Constants.*; import static java.lang.System.Logger.Level.ERROR; import static java.text.MessageFormat.format; +import de.srsoftware.tools.jdbc.Query; import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.model.Time; @@ -77,7 +78,20 @@ CREATE TABLE IF NOT EXISTS {0} ( } } - @Override + @Override + public Long delete(long timeId) { + try { + db.setAutoCommit(false); + Query.delete().from(TABLE_TASK_TIMES).where(TIME_ID,equal(timeId)).execute(db); + Query.delete().from(TABLE_TIMES).where(ID,equal(timeId)).execute(db); + db.setAutoCommit(false); + return timeId; + } catch (SQLException e) { + throw UmbrellaException.databaseException("Failed to delete time with id = {0}",timeId); + } + } + + @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); 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 f259b84..9207e1f 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 { + Long delete(long timeId); + 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 97ff2b6..eed2351 100644 --- a/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java +++ b/time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java @@ -34,6 +34,32 @@ public class TimeModule extends BaseHandler implements TimeService { timeDb = new SqliteDb(connect(dbFile)); } + + private boolean deleteTime(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 delete this time!"); + timeDb.delete(timeId); + return sendContent(ex,time); + } + + + @Override + public boolean doDelete(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 deleteTime(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 doGet(Path path, HttpExchange ex) throws IOException { addCors(ex); diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index f7e73a8..d70498c 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -15,6 +15,7 @@ "bookmarks": "Lesezeichen", "by": "von", + "cancel": "abbrechen", "client_id": "Client-ID", "client_secret": "Client-Geheimnis", "close_settings": "Einstellungen schließen", diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 644b4a1..e716d0f 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -320,4 +320,8 @@ li > a > p:nth-child(1){ .timetracks ul{ margin: 0 +} + +.time.record button.delete{ + float: right; } \ No newline at end of file