refactored timetracking to only use client-supplied times
This commit is contained in:
@@ -3,7 +3,6 @@ package de.srsoftware.umbrella.core.model;
|
|||||||
|
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Util.*;
|
import static de.srsoftware.umbrella.core.Util.*;
|
||||||
import static java.time.ZoneOffset.UTC;
|
|
||||||
|
|
||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
@@ -18,8 +17,8 @@ import org.json.JSONObject;
|
|||||||
public class Time implements Mappable{
|
public class Time implements Mappable{
|
||||||
private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
private final HashSet<Long> taskIds = new HashSet<>();
|
private final HashSet<Long> taskIds = new HashSet<>();
|
||||||
private LocalDateTime end;
|
private long start;
|
||||||
private LocalDateTime start;
|
private Long end;
|
||||||
private long id;
|
private long id;
|
||||||
private final long userId;
|
private final long userId;
|
||||||
private String description;
|
private String description;
|
||||||
@@ -33,7 +32,7 @@ public class Time implements Mappable{
|
|||||||
Complete(60),
|
Complete(60),
|
||||||
Cancelled(100);
|
Cancelled(100);
|
||||||
|
|
||||||
private int code;
|
private final int code;
|
||||||
|
|
||||||
State(int code){
|
State(int code){
|
||||||
this.code = code;
|
this.code = code;
|
||||||
@@ -55,7 +54,7 @@ public class Time implements Mappable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Time(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Collection<Long> taskIds){
|
public Time(long id, long userId, String subject, String description, long start, Long end, State state, Collection<Long> taskIds){
|
||||||
this.id=id;
|
this.id=id;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.subject = subject;
|
this.subject = subject;
|
||||||
@@ -70,14 +69,10 @@ public class Time implements Mappable{
|
|||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalDateTime end(){
|
public Long end(){
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long endSecond(){
|
|
||||||
return end == null ? null : end.toEpochSecond(UTC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long id(){
|
public long id(){
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -90,11 +85,10 @@ public class Time implements Mappable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Time of(ResultSet rs) throws SQLException {
|
public static Time of(ResultSet rs) throws SQLException {
|
||||||
var startTimestamp = rs.getLong(START_TIME);
|
var start = rs.getLong(START_TIME);
|
||||||
var start = startTimestamp == 0 ? null : LocalDateTime.ofEpochSecond(startTimestamp,0,UTC);
|
Long end = rs.getLong(END_TIME);
|
||||||
var endTimestamp = rs.getLong(END_TIME);
|
|
||||||
|
|
||||||
var end = endTimestamp == 0 ? null : LocalDateTime.ofEpochSecond(endTimestamp,0,UTC);
|
if (end == 0) end = null;
|
||||||
|
|
||||||
return new Time(
|
return new Time(
|
||||||
rs.getLong(ID),
|
rs.getLong(ID),
|
||||||
@@ -120,9 +114,9 @@ public class Time implements Mappable{
|
|||||||
if (o instanceof JSONObject nested && nested.get(SOURCE) instanceof String src) o = src;
|
if (o instanceof JSONObject nested && nested.get(SOURCE) instanceof String src) o = src;
|
||||||
if (o instanceof String d) description = d;
|
if (o instanceof String d) description = d;
|
||||||
}
|
}
|
||||||
if (json.has(START_TIME) && json.get(START_TIME) instanceof String st) start = parse(st);
|
if (json.has(START_TIME) && json.get(START_TIME) instanceof Number st) start = st.longValue();
|
||||||
if (json.has(END_TIME) && json.get(END_TIME) instanceof String e) end = parse(e);
|
if (json.has(END_TIME) && json.get(END_TIME) instanceof Number e) end = e.longValue();
|
||||||
if (end != null && end.isBefore(start)) throw UmbrellaException.invalidFieldException(END_TIME,"after start_time");
|
if (end != null && end < start) throw UmbrellaException.invalidFieldException(END_TIME,"after start_time");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,21 +124,18 @@ public class Time implements Mappable{
|
|||||||
id = newValue;
|
id = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalDateTime start(){
|
public long start(){
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long startSecond(){
|
|
||||||
return start == null ? null : start.toEpochSecond(UTC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public State state(){
|
public State state(){
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Time stop(LocalDateTime endTime) {
|
public Time stop(long endTime) {
|
||||||
end = endTime.withSecond(0).withNano(0);
|
end = endTime;
|
||||||
start = start.withSecond(0).withNano(0);
|
|
||||||
state = State.Open;
|
state = State.Open;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -164,10 +155,10 @@ public class Time implements Mappable{
|
|||||||
map.put(USER_ID,userId);
|
map.put(USER_ID,userId);
|
||||||
map.put(SUBJECT,subject);
|
map.put(SUBJECT,subject);
|
||||||
map.put(DESCRIPTION,mapMarkdown(description));
|
map.put(DESCRIPTION,mapMarkdown(description));
|
||||||
map.put(START_TIME,start.toString().replace("T"," "));
|
map.put(START_TIME,start);
|
||||||
map.put(END_TIME,end == null ? null : end.toString().replace("T"," "));
|
map.put(END_TIME,end);
|
||||||
map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name()));
|
map.put(STATE,Map.of(STATUS_CODE,state.code,NAME,state.name()));
|
||||||
map.put(DURATION,end == null ? null : Duration.between(start,end).toMinutes()/60d);
|
map.put(DURATION,end == null ? null : (end - start)/3600d);
|
||||||
map.put(TASK_IDS,taskIds);
|
map.put(TASK_IDS,taskIds);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
|
||||||
import { api } from '../urls.svelte.js';
|
|
||||||
import { logout, user } from '../user.svelte.js';
|
import { logout, user } from '../user.svelte.js';
|
||||||
import { t } from '../translations.svelte.js';
|
import { t } from '../translations.svelte.js';
|
||||||
import { timetrack } from '../user.svelte.js';
|
|
||||||
|
import TimeRecorder from './TimeRecorder.svelte';
|
||||||
|
|
||||||
let key = $state(null);
|
let key = $state(null);
|
||||||
const router = useTinyRouter();
|
const router = useTinyRouter();
|
||||||
@@ -30,56 +30,12 @@ function go(path){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function msToTime(ms) {
|
|
||||||
const days = Math.floor(ms / (24 * 60 * 60 * 1000));
|
|
||||||
ms %= 24 * 60 * 60 * 1000;
|
|
||||||
const hours = Math.floor(ms / (60 * 60 * 1000));
|
|
||||||
ms %= 60 * 60 * 1000;
|
|
||||||
const minutes = Math.floor(ms / (60 * 1000));
|
|
||||||
ms %= 60 * 1000;
|
|
||||||
const seconds = Math.floor(ms / 1000);
|
|
||||||
|
|
||||||
let daysStr = days > 0 ? padTo2Digits(days) + ':' : '';
|
|
||||||
return `${daysStr}${padTo2Digits(hours)}:${padTo2Digits(minutes)}:${padTo2Digits(seconds)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function padTo2Digits(num) {
|
|
||||||
return num.toString().padStart(2, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function search(e){
|
async function search(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.navigate(`/search?key=${key}`);
|
router.navigate(`/search?key=${key}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopTrack(){
|
|
||||||
if (timetrack.running.id){
|
|
||||||
const url = api(`time/${timetrack.running.id}/stop`);
|
|
||||||
const res = await fetch(url,{credentials:'include'});
|
|
||||||
if (res.ok){
|
|
||||||
timetrack.running = null;
|
|
||||||
timetrack.elapsed = null;
|
|
||||||
timetrack.start = null;
|
|
||||||
router.navigate('/time');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let interval = null;
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (timetrack.running) {
|
|
||||||
console.log('effect!');
|
|
||||||
timetrack.start = Date.parse(timetrack.running.start_time);
|
|
||||||
interval = setInterval(() => { timetrack.elapsed = msToTime(Date.now() - timetrack.start); },1000);
|
|
||||||
} else {
|
|
||||||
clearInterval(interval);
|
|
||||||
timetrack.elapsed = null;
|
|
||||||
interval = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMount(fetchModules);
|
onMount(fetchModules);
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -113,9 +69,5 @@ onDestroy(() => {
|
|||||||
{#if user.name }
|
{#if user.name }
|
||||||
<a onclick={logout}>{t('logout')}</a>
|
<a onclick={logout}>{t('logout')}</a>
|
||||||
{/if}
|
{/if}
|
||||||
{#if timetrack.running}
|
<TimeRecorder />
|
||||||
<span class="timetracking">{timetrack.elapsed} {timetrack.running.subject}
|
|
||||||
<button onclick={stopTrack} title={t('stop')} class="symbol"></button>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { display } from '../time.svelte.js';
|
||||||
import { t } from '../translations.svelte.js';
|
import { t } from '../translations.svelte.js';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
|
import TimeStampInput from './TimeStampInput.svelte';
|
||||||
|
|
||||||
let { record = null, onSet = time => {} } = $props();
|
let { record = null, onSet = time => {} } = $props();
|
||||||
|
|
||||||
@@ -20,11 +22,11 @@
|
|||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
{t('start')}
|
{t('start')}
|
||||||
<input type="datetime-local" bind:value={record.start_time} />
|
<TimeStampInput bind:timestamp={record.start_time} />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
{t('end')}
|
{t('end')}
|
||||||
<input type="datetime-local" bind:value={record.end_time} />
|
<TimeStampInput bind:timestamp={record.end_time} />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
{t('description')}
|
{t('description')}
|
||||||
|
|||||||
55
frontend/src/Components/TimeRecorder.svelte
Normal file
55
frontend/src/Components/TimeRecorder.svelte
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<script>
|
||||||
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
|
||||||
|
import { api } from '../urls.svelte';
|
||||||
|
import { t } from '../translations.svelte';
|
||||||
|
import { msToTime, now } from '../time.svelte';
|
||||||
|
import { timetrack } from '../user.svelte';
|
||||||
|
|
||||||
|
let interval = null;
|
||||||
|
const router = useTinyRouter();
|
||||||
|
|
||||||
|
async function stopTrack(){
|
||||||
|
if (timetrack.running.id){
|
||||||
|
const url = api(`time/${timetrack.running.id}/stop`);
|
||||||
|
const resp = await fetch(url,{
|
||||||
|
credentials : 'include',
|
||||||
|
method : 'POST',
|
||||||
|
body : now()
|
||||||
|
});
|
||||||
|
if (resp.ok){
|
||||||
|
timetrack.running = null;
|
||||||
|
timetrack.elapsed = null;
|
||||||
|
timetrack.start = null;
|
||||||
|
go();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(){
|
||||||
|
router.navigate('/time');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateElapsed(){
|
||||||
|
timetrack.elapsed = msToTime(Date.now() - timetrack.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (timetrack.running) {
|
||||||
|
timetrack.start = new Date(timetrack.running.start_time*1000);
|
||||||
|
interval = setInterval(updateElapsed,1000);
|
||||||
|
} else {
|
||||||
|
clearInterval(interval);
|
||||||
|
timetrack.elapsed = null;
|
||||||
|
interval = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{#if timetrack.running}
|
||||||
|
<span class="timetracking">
|
||||||
|
<span onclick={go} >{timetrack.running.subject} {#if timetrack.elapsed}({timetrack.elapsed}){/if}</span>
|
||||||
|
<button onclick={stopTrack} title={t('stop')} class="symbol"></button>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
16
frontend/src/Components/TimeStampInput.svelte
Normal file
16
frontend/src/Components/TimeStampInput.svelte
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script>
|
||||||
|
import { display, to_secs } from '../time.svelte.js';
|
||||||
|
let { timestamp = $bindable() } = $props();
|
||||||
|
|
||||||
|
let datetime = $state(null);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
datetime = display(timestamp);
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
timestamp = to_secs(datetime);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input type="datetime-local" bind:value={datetime} />
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
|
||||||
import { dragged } from './dragndrop.svelte.js';
|
import { dragged } from './dragndrop.svelte';
|
||||||
import { api } from '../../urls.svelte.js';
|
import { api } from '../../urls.svelte';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte';
|
||||||
import { timetrack } from '../../user.svelte.js';
|
import { timetrack } from '../../user.svelte';
|
||||||
|
import { now } from '../../time.svelte';
|
||||||
|
|
||||||
import TaskList from './TaskList.svelte';
|
import TaskList from './TaskList.svelte';
|
||||||
import LineEditor from '../../Components/LineEditor.svelte';
|
import LineEditor from '../../Components/LineEditor.svelte';
|
||||||
@@ -29,11 +30,14 @@
|
|||||||
|
|
||||||
async function addTime(){
|
async function addTime(){
|
||||||
const url = api(`time/track_task/${task.id}`);
|
const url = api(`time/track_task/${task.id}`);
|
||||||
const resp = await fetch(url,{credentials:'include'}); // create new time or return time with assigned tasks
|
const resp = await fetch(url,{
|
||||||
|
credentials : 'include',
|
||||||
|
method : 'POST',
|
||||||
|
body : now()
|
||||||
|
}); // create new time or return time with assigned tasks
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const track = await resp.json();
|
const track = await resp.json();
|
||||||
timetrack.running = track;
|
timetrack.running = track;
|
||||||
console.log(track);
|
|
||||||
} else {
|
} else {
|
||||||
error = await resp.text();
|
error = await resp.text();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { useTinyRouter } from 'svelte-tiny-router';
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
import { api } from '../../urls.svelte.js';
|
import { api } from '../../urls.svelte.js';
|
||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
|
import { display } from '../../time.svelte.js';
|
||||||
|
|
||||||
import TimeEditor from '../../Components/TimeRecordEditor.svelte';
|
import TimeEditor from '../../Components/TimeRecordEditor.svelte';
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@
|
|||||||
let router = useTinyRouter();
|
let router = useTinyRouter();
|
||||||
let times = $state(null);
|
let times = $state(null);
|
||||||
let detail = $state(null);
|
let detail = $state(null);
|
||||||
let sortedTimes = $derived.by(() => Object.values(times).sort((b, a) => a.start_time.localeCompare(b.start_time)));
|
let sortedTimes = $derived.by(() => Object.values(times).sort((b, a) => a.start_time - b.start_time));
|
||||||
let selected = $state({});
|
let selected = $state({});
|
||||||
let ranges = {};
|
let ranges = {};
|
||||||
let timeMap = $derived.by(calcYearMap);
|
let timeMap = $derived.by(calcYearMap);
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
let monthIndex = 0;
|
let monthIndex = 0;
|
||||||
for (let idx in sortedTimes){
|
for (let idx in sortedTimes){
|
||||||
const time = sortedTimes[idx];
|
const time = sortedTimes[idx];
|
||||||
const start = time.start_time;
|
const start = display(time.start_time);
|
||||||
const year = start.substring(0,4);
|
const year = start.substring(0,4);
|
||||||
const month = start.substring(5,7);
|
const month = start.substring(5,7);
|
||||||
if (year != lastYear){
|
if (year != lastYear){
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function loadTimes(){
|
async function loadTimes(){
|
||||||
const url = api('time');
|
const url = api('time');
|
||||||
const resp = await fetch(url,{credentials:'include'});
|
const resp = await fetch(url,{credentials:'include'});
|
||||||
@@ -88,8 +90,6 @@
|
|||||||
|
|
||||||
async function update(time){
|
async function update(time){
|
||||||
const url = api(`time/${time.id}`);
|
const url = api(`time/${time.id}`);
|
||||||
time.start_time = time.start_time.split(':').slice(0,2).join(':');
|
|
||||||
time.end_time = time.end_time.split(':').slice(0,2).join(':');
|
|
||||||
const res = await fetch(url,{
|
const res = await fetch(url,{
|
||||||
credentials:'include',
|
credentials:'include',
|
||||||
method:'PATCH',
|
method:'PATCH',
|
||||||
@@ -137,13 +137,13 @@
|
|||||||
{#each sortedTimes as time,line}
|
{#each sortedTimes as time,line}
|
||||||
<tr class={selected[time.id]?'selected':''}>
|
<tr class={selected[time.id]?'selected':''}>
|
||||||
{#if timeMap.years[line]}
|
{#if timeMap.years[line]}
|
||||||
<td class="year" rowspan={timeMap.years[line]} onclick={e => toggleRange(time.start_time.substring(0,4))}>
|
<td class="year" rowspan={timeMap.years[line]} onclick={e => toggleRange(display(time.start_time).substring(0,4))}>
|
||||||
{time.start_time.substring(0,4)}
|
{display(time.start_time).substring(0,4)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if timeMap.months[line]}
|
{#if timeMap.months[line]}
|
||||||
<td class="month" rowspan={timeMap.months[line]} onclick={e => toggleRange(time.start_time.substring(0,7))}>
|
<td class="month" rowspan={timeMap.months[line]} onclick={e => toggleRange(display(time.start_time).substring(0,7))}>
|
||||||
{time.start_time.substring(5,7)}
|
{display(time.start_time).substring(5,7)}
|
||||||
</td>
|
</td>
|
||||||
{/if}
|
{/if}
|
||||||
{#if detail == time.id}
|
{#if detail == time.id}
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
</td>
|
</td>
|
||||||
{:else}
|
{:else}
|
||||||
<td class="start_end" onclick={e => toggleSelect(time.id)}>
|
<td class="start_end" onclick={e => toggleSelect(time.id)}>
|
||||||
{time.start_time}{#if time.end_time}<wbr>…<wbr>{time.end_time}{/if}
|
{display(time.start_time)}{#if time.end_time}<wbr>…<wbr>{display(time.end_time)}{/if}
|
||||||
</td>
|
</td>
|
||||||
<td class="duration" onclick={e => {detail = time.id}}>
|
<td class="duration" onclick={e => {detail = time.id}}>
|
||||||
{#if time.duration}
|
{#if time.duration}
|
||||||
|
|||||||
34
frontend/src/time.svelte.js
Normal file
34
frontend/src/time.svelte.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export function display(timestamp){
|
||||||
|
const date = new Date(timestamp * 1000);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function padTo2Digits(num) {
|
||||||
|
return num.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function msToTime(ms) {
|
||||||
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
|
const days = Math.floor(totalSeconds / 86400);
|
||||||
|
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
|
||||||
|
let timestring = '';
|
||||||
|
if (days) timestring += days.toString().padStart(2, '0') + ":";
|
||||||
|
if (days||hours) timestring += hours.toString().padStart(2, '0') + ":";
|
||||||
|
return timestring + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function now(){
|
||||||
|
return Math.floor(Date.now()/1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function to_secs(datetime){
|
||||||
|
return Math.floor(new Date(datetime).getTime()/1000);
|
||||||
|
}
|
||||||
@@ -152,14 +152,14 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
try {
|
try {
|
||||||
if (track.id() == 0) { // create new
|
if (track.id() == 0) { // create new
|
||||||
var rs = insertInto(TABLE_TIMES, USER_ID, SUBJECT, DESCRIPTION, START_TIME, END_TIME, STATE)
|
var rs = insertInto(TABLE_TIMES, USER_ID, SUBJECT, DESCRIPTION, START_TIME, END_TIME, STATE)
|
||||||
.values(track.userId(), track.subject(), track.description(), track.startSecond(), track.endSecond(), track.state().code())
|
.values(track.userId(), track.subject(), track.description(), track.start(), track.end(), track.state().code())
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.getGeneratedKeys();
|
.getGeneratedKeys();
|
||||||
if (rs.next()) track.setId(rs.getLong(1));
|
if (rs.next()) track.setId(rs.getLong(1));
|
||||||
rs.close();
|
rs.close();
|
||||||
} else { // update
|
} else { // update
|
||||||
replaceInto(TABLE_TIMES, ID, USER_ID, SUBJECT, DESCRIPTION, START_TIME, END_TIME, STATE)
|
replaceInto(TABLE_TIMES, ID, USER_ID, SUBJECT, DESCRIPTION, START_TIME, END_TIME, STATE)
|
||||||
.values(track.id(), track.userId(), track.subject(), track.description(), track.startSecond(), track.endSecond(), track.state().code())
|
.values(track.id(), track.userId(), track.subject(), track.description(), track.start(), track.end(), track.state().code())
|
||||||
.execute(db).close();
|
.execute(db).close();
|
||||||
}
|
}
|
||||||
var query = replaceInto(TABLE_TASK_TIMES,TIME_ID,TASK_ID);
|
var query = replaceInto(TABLE_TASK_TIMES,TIME_ID,TASK_ID);
|
||||||
|
|||||||
@@ -21,25 +21,13 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.*;
|
import de.srsoftware.umbrella.core.model.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class TimeModule extends BaseHandler implements TimeService {
|
public class TimeModule extends BaseHandler implements TimeService {
|
||||||
|
|
||||||
private class ExtendedTime extends Time{
|
|
||||||
|
|
||||||
|
|
||||||
private final Collection<Task> tasks;
|
|
||||||
|
|
||||||
public ExtendedTime(long id, long userId, String subject, String description, LocalDateTime start, LocalDateTime end, State state, Collection<Task> tasks) {
|
|
||||||
super(id, userId, subject, description, start, end, state, tasks.stream().map(Task::id).toList());
|
|
||||||
this.tasks = tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final TimeDb timeDb;
|
private final TimeDb timeDb;
|
||||||
|
|
||||||
|
|
||||||
public TimeModule(ModuleRegistry registry, Configuration config) throws UmbrellaException {
|
public TimeModule(ModuleRegistry registry, Configuration config) throws UmbrellaException {
|
||||||
super(registry);
|
super(registry);
|
||||||
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE));
|
||||||
@@ -55,16 +43,9 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case TRACK_TASK -> trackTask(user.get(),path,ex);
|
|
||||||
case STARTED -> getStartedTime(user.get(),ex);
|
case STARTED -> getStartedTime(user.get(),ex);
|
||||||
case null -> getUserTimes(user.get(),ex);
|
case null -> getUserTimes(user.get(),ex);
|
||||||
default -> {
|
default -> super.doGet(path,ex);
|
||||||
try {
|
|
||||||
long timeId = Long.parseLong(head);
|
|
||||||
if (STOP.equals(path.pop())) yield getStoppedTask(user.get(),timeId,ex);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
yield super.doGet(path,ex);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
return send(ex,e);
|
return send(ex,e);
|
||||||
@@ -72,8 +53,14 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean getStoppedTask(UmbrellaUser user, long timeId, HttpExchange ex) throws IOException {
|
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);
|
var time = timeDb.load(timeId);
|
||||||
timeDb.save(time.stop(LocalDateTime.now()));
|
timeDb.save(time.stop(now));
|
||||||
return sendContent(ex,time);
|
return sendContent(ex,time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +91,14 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case LIST -> listTimes(ex,user.get());
|
case LIST -> listTimes(ex,user.get());
|
||||||
default -> super.doPost(path,ex);
|
case TRACK_TASK -> trackTask(user.get(),path,ex);
|
||||||
|
default -> {
|
||||||
|
try {
|
||||||
|
long timeId = Long.parseLong(head);
|
||||||
|
if (STOP.equals(path.pop())) yield getStoppedTask(user.get(),timeId,ex);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
yield super.doGet(path,ex);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
return send(ex,e);
|
return send(ex,e);
|
||||||
@@ -136,7 +130,13 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw invalidFieldException(TASK_ID,"long value");
|
throw invalidFieldException(TASK_ID,"long value");
|
||||||
}
|
}
|
||||||
var now = LocalDateTime.now().withNano(0);
|
|
||||||
|
long now;
|
||||||
|
try {
|
||||||
|
now = Long.parseLong(body(ex));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw unprocessable("request body does not contain a timestamp!");
|
||||||
|
}
|
||||||
|
|
||||||
var opt = getStartedTime(user);
|
var opt = getStartedTime(user);
|
||||||
if (opt.isPresent()){
|
if (opt.isPresent()){
|
||||||
@@ -156,9 +156,7 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
|
|
||||||
private boolean getStartedTime(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean getStartedTime(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
var startedTime = getStartedTime(user);
|
var startedTime = getStartedTime(user);
|
||||||
if (startedTime.isPresent()){
|
if (startedTime.isPresent()) return sendContent(ex,startedTime.get());
|
||||||
return sendContent(ex,startedTime.get());
|
|
||||||
}
|
|
||||||
return send(ex,UmbrellaException.notFound("no started time"));
|
return send(ex,UmbrellaException.notFound("no started time"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user