implemented easy-list
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -20,6 +20,7 @@ public class Paths {
|
|||||||
public static final String STARTED = "started";
|
public static final String STARTED = "started";
|
||||||
public static final String STOP = "stop";
|
public static final String STOP = "stop";
|
||||||
public static final String SUBMIT = "submit";
|
public static final String SUBMIT = "submit";
|
||||||
|
public static final String TAGGED = "tagged";
|
||||||
public static final String TOKEN = "token";
|
public static final String TOKEN = "token";
|
||||||
public static final String VIEW = "view";
|
public static final String VIEW = "view";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ package de.srsoftware.umbrella.core.api;
|
|||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface TagService {
|
public interface TagService {
|
||||||
void deleteEntity(String task, long taskId);
|
void deleteEntity(String task, long taskId);
|
||||||
|
|
||||||
|
Map<String, List<Long>> getTagUses(UmbrellaUser user, String tag);
|
||||||
|
|
||||||
Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException;
|
Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import Companies from "./routes/company/Index.svelte";
|
import Companies from "./routes/company/Index.svelte";
|
||||||
import ContactList from "./routes/contact/Index.svelte";
|
import ContactList from "./routes/contact/Index.svelte";
|
||||||
import DocList from "./routes/document/List.svelte";
|
import DocList from "./routes/document/List.svelte";
|
||||||
|
import EasyList from "./routes/task/EasyList.svelte";
|
||||||
import EditService from "./routes/user/EditService.svelte";
|
import EditService from "./routes/user/EditService.svelte";
|
||||||
import EditUser from "./routes/user/EditUser.svelte";
|
import EditUser from "./routes/user/EditUser.svelte";
|
||||||
import FileIndex from "./routes/files/Index.svelte";
|
import FileIndex from "./routes/files/Index.svelte";
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
<Route path="/tags" component={TagList} />
|
<Route path="/tags" component={TagList} />
|
||||||
<Route path="/tags/use/:tag" component={TagUses} />
|
<Route path="/tags/use/:tag" component={TagUses} />
|
||||||
<Route path="/task" component={TaskList} />
|
<Route path="/task" component={TaskList} />
|
||||||
|
<Route path="/tags/easylist/:tag" component={EasyList} />
|
||||||
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
||||||
<Route path="/task/:id/edit" component={ViewTask} />
|
<Route path="/task/:id/edit" component={ViewTask} />
|
||||||
<Route path="/task/:id/view" component={ViewTask} />
|
<Route path="/task/:id/view" component={ViewTask} />
|
||||||
|
|||||||
104
frontend/src/routes/task/EasyList.svelte
Normal file
104
frontend/src/routes/task/EasyList.svelte
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
import { api, get, patch } from '../../urls.svelte';
|
||||||
|
import { error, yikes } from '../../warn.svelte';
|
||||||
|
import { t } from '../../translations.svelte';
|
||||||
|
|
||||||
|
let { tag } = $props();
|
||||||
|
let filter = $state(null);
|
||||||
|
let search = $derived(filter ? filter.toLowerCase() : null);
|
||||||
|
let input;
|
||||||
|
let tasks = $state(null);
|
||||||
|
let router = useTinyRouter();
|
||||||
|
let sorted = $derived(tasks ? Object.values(tasks).filter(noNoIndex).sort(byName) : null);
|
||||||
|
|
||||||
|
function byName(a,b){
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function close(e,task){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
update(task,60);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load(){
|
||||||
|
const url = api(`task/tagged/${tag}`);
|
||||||
|
const res = await get(url);
|
||||||
|
if (res.ok){
|
||||||
|
tasks = await res.json();
|
||||||
|
console.log(Object.values(tasks).map(t => t.name));
|
||||||
|
yikes();
|
||||||
|
input.focus();
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function noNoIndex(task){
|
||||||
|
return !task.no_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function oncontextmenu(e){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
console.log(e);
|
||||||
|
var target = e.target;
|
||||||
|
while (target && !target.href) target=target.parentNode;
|
||||||
|
let href = target.getAttribute('href');
|
||||||
|
if (href) router.navigate(href);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function open(e,task){
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
update(task,20);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update(task,newState){
|
||||||
|
const url = api(`task/${task.id}`);
|
||||||
|
const res = await patch(url,{status:newState});
|
||||||
|
if (res.ok){
|
||||||
|
task.status = newState;
|
||||||
|
yikes();
|
||||||
|
// filter = null; // not sure what is better, resetting or keeping
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(load);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="easylist">
|
||||||
|
<div class="filter">
|
||||||
|
{t('filter')}: <input type="text" bind:value={filter} bind:this={input} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="open">
|
||||||
|
<legend>{t('state_open')}</legend>
|
||||||
|
{#if sorted}
|
||||||
|
{#each sorted as task}
|
||||||
|
{#if task.status == 20 && (!filter || task.name.toLowerCase().includes(search))}
|
||||||
|
<a href={`/task/${task.id}/view`} title={task.description.source} onclick={e => close(e,task)} {oncontextmenu} >
|
||||||
|
{task.name}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="closed">
|
||||||
|
<legend>{t('state_complete')}</legend>
|
||||||
|
{#if sorted}
|
||||||
|
{#each sorted as task}
|
||||||
|
{#if task.status > 20 && (!filter || task.name.toLowerCase().includes(search))}
|
||||||
|
<a href={`/task/${task.id}/view`} title={task.description.source} onclick={e => open(e,task)} {oncontextmenu} >
|
||||||
|
{task.name}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
@@ -9,7 +9,6 @@ import static de.srsoftware.umbrella.core.Field.ITEM;
|
|||||||
import static de.srsoftware.umbrella.core.ModuleRegistry.companyService;
|
import static de.srsoftware.umbrella.core.ModuleRegistry.companyService;
|
||||||
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
||||||
import static de.srsoftware.umbrella.core.Paths.LIST;
|
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||||
import static de.srsoftware.umbrella.stock.Constants.*;
|
import static de.srsoftware.umbrella.stock.Constants.*;
|
||||||
import static java.lang.System.Logger.Level.WARNING;
|
import static java.lang.System.Logger.Level.WARNING;
|
||||||
|
|||||||
@@ -112,8 +112,12 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<Long>> getTagUses(UmbrellaUser user, String tag){
|
||||||
|
return tagDb.getUses(tag,user.id());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
|
private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
|
||||||
return sendContent(ex,tagDb.getUses(tag,user.id()));
|
return sendContent(ex,getTagUses(user,tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
|
public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/* © SRSoftware 2025 */
|
/* © SRSoftware 2025 */
|
||||||
package de.srsoftware.umbrella.task;
|
package de.srsoftware.umbrella.task;
|
||||||
|
|
||||||
import static de.srsoftware.tools.Optionals.is0;
|
import static de.srsoftware.tools.Optionals.*;
|
||||||
import static de.srsoftware.tools.Optionals.isSet;
|
|
||||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
|
import static de.srsoftware.umbrella.core.ModuleRegistry.*;
|
||||||
@@ -92,6 +91,7 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case PERMISSIONS -> getPermissionList(ex);
|
case PERMISSIONS -> getPermissionList(ex);
|
||||||
|
case TAGGED -> getTaggedTasks(path, user.get(), ex);
|
||||||
case null -> getUserTasks(user.get(), ex);
|
case null -> getUserTasks(user.get(), ex);
|
||||||
default -> {
|
default -> {
|
||||||
var taskId = Long.parseLong(head);
|
var taskId = Long.parseLong(head);
|
||||||
@@ -179,6 +179,14 @@ public class TaskModule extends BaseHandler implements TaskService {
|
|||||||
return sendContent(ex, map);
|
return sendContent(ex, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getTaggedTasks(Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
|
var tag = path.toString();
|
||||||
|
var tags = tagService().getTagUses(user,tag);
|
||||||
|
var taskIds = nullable(tags.get(TASK)).orElseGet(ArrayList::new);
|
||||||
|
var tasks = taskDb.load(taskIds);
|
||||||
|
return sendContent(ex, mapValues(tasks));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||||
var task = loadMembers(taskDb.load(taskId));
|
var task = loadMembers(taskDb.load(taskId));
|
||||||
if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name());
|
if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name());
|
||||||
|
|||||||
@@ -295,3 +295,12 @@ tr:hover .taglist .tag button {
|
|||||||
.vcard span.inactive{
|
.vcard span.inactive{
|
||||||
color: #222200;
|
color: #222200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
border-color:orange;
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
.easylist fieldset {
|
||||||
|
border-color: red;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@@ -407,6 +407,15 @@ a.wikilink{
|
|||||||
grid-column-end: span 2;
|
grid-column-end: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 7px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.grid2{
|
.grid2{
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -428,6 +437,13 @@ a.wikilink{
|
|||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.easylist input{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.vcard{
|
fieldset.vcard{
|
||||||
|
|||||||
@@ -285,3 +285,12 @@ tr:hover .taglist .tag button {
|
|||||||
.vcard span.inactive{
|
.vcard span.inactive{
|
||||||
color: #222200;
|
color: #222200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
border-color: orange;
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
.easylist fieldset {
|
||||||
|
border-color: #ff7726;
|
||||||
|
color: #ff7726;
|
||||||
|
}
|
||||||
@@ -485,6 +485,15 @@ a.wikilink{
|
|||||||
grid-column-end: span 2;
|
grid-column-end: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 7px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.grid2{
|
.grid2{
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -506,6 +515,13 @@ a.wikilink{
|
|||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.easylist input{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.vcard{
|
fieldset.vcard{
|
||||||
@@ -551,4 +567,4 @@ fieldset.vcard{
|
|||||||
margin: 0 6px;
|
margin: 0 6px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: inline flow-root;
|
display: inline flow-root;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -273,3 +273,13 @@ tr:hover .taglist .tag button {
|
|||||||
.vcard span.inactive{
|
.vcard span.inactive{
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
border-color: blue;
|
||||||
|
color: blue;
|
||||||
|
background: #dfe4ff;
|
||||||
|
}
|
||||||
|
.easylist fieldset {
|
||||||
|
border-color: blue;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
@@ -407,6 +407,15 @@ a.wikilink{
|
|||||||
grid-column-end: span 2;
|
grid-column-end: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid;
|
||||||
|
margin: 7px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.grid2{
|
.grid2{
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -428,6 +437,13 @@ a.wikilink{
|
|||||||
width: calc(100% - 10px);
|
width: calc(100% - 10px);
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.easylist a {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.easylist input{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.vcard{
|
fieldset.vcard{
|
||||||
|
|||||||
Reference in New Issue
Block a user