Browse Source

implemented joining of times

feature/join_times
Stephan Richter 2 months ago
parent
commit
45c2f3978f
  1. 15
      core/src/main/java/de/srsoftware/umbrella/core/model/Time.java
  2. 23
      frontend/src/routes/time/Index.svelte
  3. 2
      time/src/main/java/de/srsoftware/umbrella/time/Constants.java
  4. 70
      time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java
  5. 3
      translations/src/main/resources/de.json
  6. 13
      web/src/main/resources/web/css/default.css

15
core/src/main/java/de/srsoftware/umbrella/core/model/Time.java

@ -68,10 +68,20 @@ public class Time implements Mappable{ @@ -68,10 +68,20 @@ public class Time implements Mappable{
return description;
}
public Time description(String newVal) {
description = newVal;
return this;
}
public Long end(){
return end;
}
public Time end(Long newVal) {
end = newVal;
return this;
}
public long id(){
return id;
}
@ -143,6 +153,11 @@ public class Time implements Mappable{ @@ -143,6 +153,11 @@ public class Time implements Mappable{
return subject;
}
public Time subject(String newValue) {
subject = newValue;
return this;
}
public Collection<Long> taskIds(){
return taskIds;
}

23
frontend/src/routes/time/Index.svelte

@ -59,6 +59,26 @@ @@ -59,6 +59,26 @@
return result;
}
async function joinTimes(evt, id1, id2){
evt.preventDefault();
evt.stopPropagation();
const url = api('time/join');
const res = await fetch(url,{
credentials : 'include',
method : 'POST',
body : `${id1}+${id2}`
});
if (res.ok){
error = null;
let json = await res.json();
delete times[id1];
delete times[id2];
times[json.id] = json;
} else {
error = await res.text();
}
return false;
}
async function loadTimes(){
const url = api('time');
@ -189,6 +209,9 @@ @@ -189,6 +209,9 @@
{:else}
<td class="start_end" onclick={e => toggleSelect(time.id)}>
{time.start}{#if time.end_time}<wbr><wbr>{time.start.startsWith(time.end_date)?time.end.substring(11):time.end}{/if}
{#if line>0 && Math.abs(sortedTimes[line-1].start_time - time.end_time)<100}
<button class="symbol join" title={t('join_objects',{objects:t('times')})} onclick={e => joinTimes(e, time.id, sortedTimes[line-1].id)} ></button>
{/if}
</td>
<td class="duration" onclick={e => {detail = time.id}}>
{#if time.duration}

2
time/src/main/java/de/srsoftware/umbrella/time/Constants.java

@ -7,7 +7,7 @@ public class Constants { @@ -7,7 +7,7 @@ public class Constants {
public static final String CHILDREN = "children";
public static final String CONFIG_DATABASE = "umbrella.modules.time.database";
public static final String JOIN = "join";
public static final String PROJECTS = "projects";
public static final String TABLE_TASK_TIMES = "task_times";
public static final String TABLE_TIMES = "times";

70
time/src/main/java/de/srsoftware/umbrella/time/TimeModule.java

@ -6,6 +6,7 @@ import static de.srsoftware.umbrella.core.Constants.*; @@ -6,6 +6,7 @@ import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.*;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Time.State.Open;
import static de.srsoftware.umbrella.core.model.Time.State.Started;
import static de.srsoftware.umbrella.time.Constants.*;
import static java.util.stream.Collectors.toSet;
@ -76,18 +77,6 @@ public class TimeModule extends BaseHandler implements TimeService { @@ -76,18 +77,6 @@ public class TimeModule extends BaseHandler implements TimeService {
}
}
private boolean getStoppedTask(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException {
long now;
try {
now = Long.parseLong(body(ex));
} catch (NumberFormatException e) {
throw unprocessable("request body does not contain a timestamp!");
}
var time = timeDb.load(timeId);
timeDb.save(time.stop(now));
return sendContent(ex,time);
}
@Override
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
addCors(ex);
@ -114,6 +103,7 @@ public class TimeModule extends BaseHandler implements TimeService { @@ -114,6 +103,7 @@ public class TimeModule extends BaseHandler implements TimeService {
if (user.isEmpty()) return unauthorized(ex);
var head = path.pop();
return switch (head) {
case JOIN -> joinTimes(ex,user.get());
case LIST -> listTimes(ex,user.get());
case TRACK_TASK -> trackTask(user.get(),path,ex);
default -> {
@ -129,13 +119,67 @@ public class TimeModule extends BaseHandler implements TimeService { @@ -129,13 +119,67 @@ public class TimeModule extends BaseHandler implements TimeService {
}
}
private Optional<Time> getStartedTime(UmbrellaUser user){
private boolean joinTimes(HttpExchange ex, UmbrellaUser user) throws IOException {
var parts = body(ex).split("\\+");
if (parts.length != 2) throw unprocessable("Expected two time ids as body");
long id1, id2;
try {
id1 = Long.parseLong(parts[0]);
id2 = Long.parseLong(parts[1]);
} catch (NumberFormatException e) {
throw unprocessable("Expected two time ids as body");
}
var time1 = timeDb.load(id1);
if (time1.userId() != user.id()) throw forbidden("You are not owner of time {0}",time1.id());
if (time1.state() != Open) throw forbidden("Time is not editable");
var time2 = timeDb.load(id2);
if (time2.userId() != user.id()) throw forbidden("You are not owner of time {0}",time2.id());
if (time2.state() != Open) throw forbidden("Time is not editable");
if (time1.start() > time2.start()) {
var dummy = time1;
time1 = time2;
time2 = dummy;
}
if (Math.abs(time1.end() - time2.start())>100L) throw unprocessable("Times are not adjacent");
var subject = time1.subject();
if (time2.subject().contains(subject)) {
subject = time2.subject();
} else if (!subject.contains(time2.subject())) subject += " / "+time2.subject();
var description = time1.description();
if (time2.description().contains(description)) {
description = time2.description();
} else if (!description.contains(time2.description())) description += "\n"+time2.description();
time1.end(time2.end());
time1.taskIds().addAll(time2.taskIds());
time1.subject(subject);
time1.description(description);
var result = timeDb.save(time1);
timeDb.delete(time2.id());
return sendContent(ex,result);
}
private Optional<Time> getStartedTime(UmbrellaUser user){
return timeDb.listUserTimes(user.id(), false).values()
.stream()
.filter(time -> time.state() == Started)
.max(Comparator.comparing(Time::start));
}
private boolean getStoppedTask(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException {
long now;
try {
now = Long.parseLong(body(ex));
} catch (NumberFormatException e) {
throw unprocessable("request body does not contain a timestamp!");
}
var time = timeDb.load(timeId);
timeDb.save(time.stop(now));
return sendContent(ex,time);
}
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!");

3
translations/src/main/resources/de.json

@ -100,6 +100,7 @@ @@ -100,6 +100,7 @@
"invoice": "Rechnung",
"items": "Artikel",
"join_objects" : "{objects} zusammenführen",
"key": "Suchbegriff",
"language": "Sprache",
@ -237,7 +238,7 @@ @@ -237,7 +238,7 @@
"tax_rate": "Steuersatz",
"template": "Vorlage",
"theme": "Design",
"times": "Zeiterfassung",
"times": "Zeiten",
"timetracking": "Zeiterfassung",
"title_or_desc": "Titel/Beschreibung",
"tutorial": "Tutorial",

13
web/src/main/resources/web/css/default.css

@ -344,4 +344,17 @@ li > a > p:nth-child(1){ @@ -344,4 +344,17 @@ li > a > p:nth-child(1){
}
table{
width: 100vw;
}
.start_end{
position: relative;
}
.start_end button.join{
position: absolute;
right: 0;
top: -12px;
border: 0 none;
background: none;
color: orange;
}
Loading…
Cancel
Save