implemented search within time tracks
This commit is contained in:
@@ -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';
|
||||||
|
|
||||||
import Bookmark from '../bookmark/Template.svelte';
|
import Bookmark from '../bookmark/Template.svelte';
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
let notes = $state(null);
|
let notes = $state(null);
|
||||||
let projects = $state(null);
|
let projects = $state(null);
|
||||||
let tasks = $state(null);
|
let tasks = $state(null);
|
||||||
|
let times = $state(null);
|
||||||
|
|
||||||
async function setKey(ev){
|
async function setKey(ev){
|
||||||
if (ev) ev.preventDefault();
|
if (ev) ev.preventDefault();
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
fetch(api('notes/search'),options).then(handleNotes);
|
fetch(api('notes/search'),options).then(handleNotes);
|
||||||
fetch(api('project/search'),options).then(handleProjects);
|
fetch(api('project/search'),options).then(handleProjects);
|
||||||
fetch(api('task/search'),options).then(handleTasks);
|
fetch(api('task/search'),options).then(handleTasks);
|
||||||
|
fetch(api('time/search'),options).then(handleTimes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onclick(e){
|
function onclick(e){
|
||||||
@@ -100,6 +103,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleTimes(resp){
|
||||||
|
if (resp.ok){
|
||||||
|
const res = await resp.json();
|
||||||
|
times = Object.keys(res).length ? res : null;
|
||||||
|
} else {
|
||||||
|
error = await resp.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$effect(() => doSearch(key))
|
$effect(() => doSearch(key))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -197,3 +209,21 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if times}
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
{t('timetracks')}
|
||||||
|
</legend>
|
||||||
|
<ul>
|
||||||
|
{#each Object.values(times) as time}
|
||||||
|
<li>
|
||||||
|
<a href="/time/{time.id}/view" {onclick} >
|
||||||
|
{display(time.start_time)}{#if time.end_time}…{display(time.end_time)}{/if}
|
||||||
|
{time.subject}<br/>
|
||||||
|
{@html time.description.rendered}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.Time;
|
import de.srsoftware.umbrella.core.model.Time;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
public class SqliteDb extends BaseDb implements TimeDb {
|
public class SqliteDb extends BaseDb implements TimeDb {
|
||||||
|
|
||||||
@@ -91,6 +89,25 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Long, Time> find(long userId, List<String> keys, boolean fulltext) {
|
||||||
|
try {
|
||||||
|
var query = select(ALL).from(TABLE_TIMES).where(USER_ID,equal(userId));
|
||||||
|
for (var key : keys) query.where(format("CONCAT({0},\" \",{1})",SUBJECT,DESCRIPTION),like("%"+key+"%"));
|
||||||
|
var rs = query.exec(db);
|
||||||
|
var times = new HashMap<Long,Time>();
|
||||||
|
while (rs.next()) {
|
||||||
|
var time = Time.of(rs);
|
||||||
|
times.put(time.id(),time);
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
return times;
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
throw new UmbrellaException("Failed to search for times");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException {
|
public HashMap<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
|||||||
import de.srsoftware.umbrella.core.model.Time;
|
import de.srsoftware.umbrella.core.model.Time;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface TimeDb {
|
public interface TimeDb {
|
||||||
Long delete(long timeId);
|
Long delete(long timeId);
|
||||||
|
|
||||||
HashMap<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException;
|
Map<Long, Time> find(long userId, List<String> keys, boolean fulltext);
|
||||||
|
|
||||||
HashMap<Long,Time> listUserTimes(long userId, boolean showClosed);
|
Map<Long,Time> listTimes(Collection<Long> taskIds, boolean showClosed) throws UmbrellaException;
|
||||||
|
|
||||||
|
Map<Long,Time> listUserTimes(long userId, boolean showClosed);
|
||||||
|
|
||||||
Time load(long timeId);
|
Time load(long timeId);
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
return switch (head) {
|
return switch (head) {
|
||||||
case JOIN -> joinTimes(ex,user.get());
|
case JOIN -> joinTimes(ex,user.get());
|
||||||
case LIST -> listTimes(ex,user.get());
|
case LIST -> listTimes(ex,user.get());
|
||||||
|
case SEARCH -> postSearch(ex, user.get());
|
||||||
case TRACK_TASK -> trackTask(user.get(),path,ex);
|
case TRACK_TASK -> trackTask(user.get(),path,ex);
|
||||||
default -> {
|
default -> {
|
||||||
try {
|
try {
|
||||||
@@ -180,48 +181,6 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
return sendContent(ex,time);
|
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!");
|
|
||||||
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;
|
|
||||||
try {
|
|
||||||
var taskId = Long.parseLong(path.pop());
|
|
||||||
task = taskService().load(List.of(taskId)).get(taskId);
|
|
||||||
if (task == null) throw UmbrellaException.notFound("Failed to load task with id = {0}",taskId);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw invalidFieldException(TASK_ID,"long value");
|
|
||||||
}
|
|
||||||
|
|
||||||
long now;
|
|
||||||
try {
|
|
||||||
now = Long.parseLong(body(ex));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw unprocessable("request body does not contain a timestamp!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var opt = getStartedTime(user);
|
|
||||||
if (opt.isPresent()){
|
|
||||||
var startedTime = opt.get();
|
|
||||||
|
|
||||||
if (startedTime.taskIds().contains(task.id())) {
|
|
||||||
// if the time started last already belongs to the task, there is nothing left to do
|
|
||||||
return sendContent(ex,startedTime);
|
|
||||||
}
|
|
||||||
timeDb.save(startedTime.stop(now));
|
|
||||||
}
|
|
||||||
var track = new Time(0,user.id(),task.name(),task.description(),now,null,Started,List.of(task.id()));
|
|
||||||
timeDb.save(track);
|
|
||||||
|
|
||||||
return sendContent(ex,track);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) return sendContent(ex,startedTime.get());
|
if (startedTime.isPresent()) return sendContent(ex,startedTime.get());
|
||||||
@@ -272,4 +231,55 @@ public class TimeModule extends BaseHandler implements TimeService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 postSearch(HttpExchange ex, UmbrellaUser user) throws IOException {
|
||||||
|
var json = json(ex);
|
||||||
|
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingFieldException(KEY);
|
||||||
|
var keys = Arrays.asList(key.split(" "));
|
||||||
|
var fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val;
|
||||||
|
var notes = timeDb.find(user.id(),keys,fulltext);
|
||||||
|
return sendContent(ex,mapValues(notes));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean trackTask(UmbrellaUser user, Path path, HttpExchange ex) throws IOException {
|
||||||
|
if (path.empty()) throw missingFieldException(TASK_ID);
|
||||||
|
Task task;
|
||||||
|
try {
|
||||||
|
var taskId = Long.parseLong(path.pop());
|
||||||
|
task = taskService().load(List.of(taskId)).get(taskId);
|
||||||
|
if (task == null) throw UmbrellaException.notFound("Failed to load task with id = {0}",taskId);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw invalidFieldException(TASK_ID,"long value");
|
||||||
|
}
|
||||||
|
|
||||||
|
long now;
|
||||||
|
try {
|
||||||
|
now = Long.parseLong(body(ex));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw unprocessable("request body does not contain a timestamp!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt = getStartedTime(user);
|
||||||
|
if (opt.isPresent()){
|
||||||
|
var startedTime = opt.get();
|
||||||
|
|
||||||
|
if (startedTime.taskIds().contains(task.id())) {
|
||||||
|
// if the time started last already belongs to the task, there is nothing left to do
|
||||||
|
return sendContent(ex,startedTime);
|
||||||
|
}
|
||||||
|
timeDb.save(startedTime.stop(now));
|
||||||
|
}
|
||||||
|
var track = new Time(0,user.id(),task.name(),task.description(),now,null,Started,List.of(task.id()));
|
||||||
|
timeDb.save(track);
|
||||||
|
|
||||||
|
return sendContent(ex,track);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user