loading tasks
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
import EditService from "./routes/user/EditService.svelte";
|
||||
import EditUser from "./routes/user/EditUser.svelte";
|
||||
import Footer from "./Components/Footer.svelte";
|
||||
import Kanban from "./routes/project/Kanban.svelte";
|
||||
import Login from "./Components/Login.svelte";
|
||||
import Messages from "./routes/message/Messages.svelte";
|
||||
import Menu from "./Components/Menu.svelte";
|
||||
@@ -52,6 +53,7 @@
|
||||
<Route path="/project" component={ProjectList} />
|
||||
<Route path="/project/add" component={ProjectAdd} />
|
||||
<Route path="/project/:project_id/add_task" component={AddTask} />
|
||||
<Route path="/project/:id/kanban" component={Kanban} />
|
||||
<Route path="/project/:id/view" component={ViewPrj} />
|
||||
<Route path="/search" component={Search} />
|
||||
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import {t} from '../translations.svelte.js';
|
||||
let { caption = t('select_state'), selected = $bindable(0), onchange = (val) => console.log('changed to '+val)} = $props();
|
||||
import {api} from '../urls.svelte.js';
|
||||
let {
|
||||
caption = t('select_state'),
|
||||
selected = $bindable(0),
|
||||
onchange = (val) => console.log('changed to '+val),
|
||||
project_id = '?'
|
||||
} = $props();
|
||||
|
||||
let message = $state(t('loading'));
|
||||
let states = $state(null);
|
||||
|
||||
async function loadStates(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/states`;
|
||||
const url = api(`project/${project_id}/states`);
|
||||
var resp = await fetch(url,{credentials: 'include'});
|
||||
if (resp.ok){
|
||||
states = await resp.json();
|
||||
|
||||
109
frontend/src/routes/project/Kanban.svelte
Normal file
109
frontend/src/routes/project/Kanban.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<script>
|
||||
import { api } from '../../urls.svelte.js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../../translations.svelte.js';
|
||||
import { useTinyRouter } from 'svelte-tiny-router';
|
||||
|
||||
let { id } = $props();
|
||||
|
||||
let error = $state(null);
|
||||
let project = $state(null);
|
||||
let router = useTinyRouter();
|
||||
let states = $state(null);
|
||||
let tasks = $state({});
|
||||
|
||||
let columns = $derived(states?Object.keys(states).length+1:1);
|
||||
|
||||
async function load(){
|
||||
loadProject();
|
||||
loadStates();
|
||||
await loadTasks({project_id:+id,parent_task_id:0});
|
||||
console.log(tasks);
|
||||
}
|
||||
|
||||
async function loadProject(){
|
||||
const url = api(`project/${id}`);
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
project = await resp.json();
|
||||
error = null;
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStates(){
|
||||
const url = api(`project/${id}/states`);
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
states = await resp.json();
|
||||
error = null;
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTasks(selector){
|
||||
const url = api('task/list');
|
||||
selector.show_closed = true;
|
||||
var resp = await fetch(url,{
|
||||
credentials: 'include',
|
||||
method:'POST',
|
||||
body:JSON.stringify(selector)
|
||||
});
|
||||
if (resp.ok){
|
||||
var json = await resp.json();
|
||||
for (var task_id of Object.keys(json)) tasks[task_id] = json[task_id];
|
||||
} else {
|
||||
error = await resp.text();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(load);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.grid > *{
|
||||
min-height: 50px;
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
background: orange;
|
||||
color: black;
|
||||
}
|
||||
.head{
|
||||
background: lime;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if project}
|
||||
<h1>{project.name} : {t('kanban_view')}</h1>
|
||||
{/if}
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
|
||||
<p>Columns: {columns}</p>
|
||||
|
||||
<div class="grid" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
|
||||
<div class="head">{t('user')}</div>
|
||||
{#if states}
|
||||
{#each Object.entries(states) as [sid,state]}
|
||||
<div class="head">{t(state)}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#each Object.entries(tasks) as [tid,task]}
|
||||
<div onclick={() => router.navigate(`/task/${tid}/view`)}>
|
||||
<p>
|
||||
{task.name}
|
||||
</p>
|
||||
<p>
|
||||
{#each Object.entries(task.members) as [uid,member]}
|
||||
{#if member.permission.name=='OWNER'}
|
||||
{member.user.name}
|
||||
{/if}
|
||||
{/each}
|
||||
</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -51,6 +51,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function kanban(){
|
||||
router.navigate(`/project/${id}/kanban`);
|
||||
}
|
||||
|
||||
async function loadProject(){
|
||||
const url = api(`project/${id}`);
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
@@ -132,7 +136,7 @@
|
||||
<tr>
|
||||
<th>{t('state')}</th>
|
||||
<td>
|
||||
<StateSelector selected={project.status.code} onchange={val => update({status:val})}/>
|
||||
<StateSelector selected={project.status.code} onchange={val => update({status:val})} project_id={id} />
|
||||
</td>
|
||||
</tr>
|
||||
{#if project.company}
|
||||
@@ -185,7 +189,8 @@
|
||||
<tr>
|
||||
<th>
|
||||
{t('tasks')}
|
||||
<button onclick={addTask}>{t('add_new',t('task'))}</button>
|
||||
<button onclick={addTask}>{t('add_task')}</button>
|
||||
<button onclick={kanban}>{t('show_kanban')}</button>
|
||||
</th>
|
||||
<td class="tasks">
|
||||
{#if tasks}
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
<tr>
|
||||
<th>{t('state')}</th>
|
||||
<td>
|
||||
<StateSelector selected={task.status.code} onchange={val => update({status:val})}/>
|
||||
<StateSelector selected={task.status.code} onchange={val => update({status:val})} project_id={task.project_id} />
|
||||
</td>
|
||||
</tr>
|
||||
{#if task.description.rendered}
|
||||
|
||||
@@ -4,6 +4,7 @@ package de.srsoftware.umbrella.project;
|
||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||
import static de.srsoftware.umbrella.core.Paths.STATES;
|
||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||
import static de.srsoftware.umbrella.core.model.Permission.*;
|
||||
@@ -65,7 +66,11 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
default -> {
|
||||
var projectId = Long.parseLong(head);
|
||||
head = path.pop();
|
||||
yield head == null ? getProject(ex,projectId,user.get()) : super.doGet(path,ex);
|
||||
yield switch (head) {
|
||||
case null -> getProject(ex, projectId, user.get());
|
||||
case STATES -> getStateList(ex);
|
||||
default -> super.doGet(path, ex);
|
||||
};
|
||||
}
|
||||
};
|
||||
} catch (UmbrellaException e){
|
||||
@@ -128,6 +133,12 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
return sendContent(ex,map);
|
||||
}
|
||||
|
||||
private boolean getStateList(HttpExchange ex) throws IOException {
|
||||
var map = new HashMap<Integer,String>();
|
||||
for (var status : Status.values()) map.put(status.code(),status.name());
|
||||
return sendContent(ex,map);
|
||||
}
|
||||
|
||||
public Map<Long, Project> listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||
var projectList = projects.ofCompany(companyId, includeClosed);
|
||||
loadMembers(projectList.values());
|
||||
|
||||
@@ -200,6 +200,24 @@ CREATE TABLE IF NOT EXISTS {0} (
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId) throws UmbrellaException {
|
||||
try {
|
||||
var query = select(ALL).from(TABLE_TASKS).where(PROJECT_ID,equal(projectId));
|
||||
if (parentTaskId != 0) query.where(PARENT_TASK_ID,equal(parentTaskId));
|
||||
var tasks = new HashMap<Long,Task>();
|
||||
var rs = query.exec(db);
|
||||
while (rs.next()){
|
||||
var task = Task.of(rs);
|
||||
tasks.put(task.id(),task);
|
||||
}
|
||||
rs.close();
|
||||
return tasks;
|
||||
} catch (SQLException e){
|
||||
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project {0}",projectId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task load(long taskId) throws UmbrellaException {
|
||||
try {
|
||||
|
||||
@@ -2,19 +2,27 @@
|
||||
package de.srsoftware.umbrella.task;
|
||||
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import de.srsoftware.umbrella.core.model.Permission;
|
||||
import de.srsoftware.umbrella.core.model.Task;
|
||||
import de.srsoftware.umbrella.core.model.UmbrellaUser;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public interface TaskDb {
|
||||
|
||||
void dropMember(long projectId, long userId);
|
||||
Map<Long, Permission> getMembers(Task task);
|
||||
HashMap<Long, Task> listChildrenOf(Long parentTaskId, UmbrellaUser user, boolean showClosed);
|
||||
HashMap<Long, Task> listProjectTasks(Long projectId, Long parentTaskId) throws UmbrellaException;
|
||||
HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed);
|
||||
HashMap<Long, Task> listTasks(Collection<Long> projectIds) throws UmbrellaException;
|
||||
|
||||
Task load(long taskId) throws UmbrellaException;
|
||||
|
||||
Map<Long, Permission> getMembers(Task task);
|
||||
|
||||
Task save(Task task);
|
||||
|
||||
void setMember(long taskId, long userId, Permission permission);
|
||||
|
||||
@@ -12,6 +12,7 @@ import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
|
||||
import static de.srsoftware.umbrella.core.model.Permission.*;
|
||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
import static java.lang.System.Logger.Level.WARNING;
|
||||
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
|
||||
import static java.net.HttpURLConnection.HTTP_OK;
|
||||
|
||||
@@ -36,7 +37,7 @@ import java.util.*;
|
||||
|
||||
public class TaskModule extends BaseHandler implements TaskService {
|
||||
|
||||
private final SqliteDb taskDb;
|
||||
private final TaskDb taskDb;
|
||||
private final ProjectService projects;
|
||||
private final UserService users;
|
||||
private final CompanyService companies;
|
||||
@@ -71,7 +72,6 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
var head = path.pop();
|
||||
return switch (head) {
|
||||
case PERMISSIONS -> getPermissionList(ex);
|
||||
case STATES -> getStateList(ex);
|
||||
case null -> super.doGet(path,ex);
|
||||
default -> {
|
||||
var taskId = Long.parseLong(head);
|
||||
@@ -164,12 +164,6 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
return sendContent(ex,map);
|
||||
}
|
||||
|
||||
private boolean getStateList(HttpExchange ex) throws IOException {
|
||||
var map = new HashMap<Integer,String>();
|
||||
for (var status : Status.values()) map.put(status.code(),status.name());
|
||||
return sendContent(ex,map);
|
||||
}
|
||||
|
||||
private boolean getTask(HttpExchange ex, long taskId, UmbrellaUser user) throws IOException {
|
||||
var task = loadMembers(taskDb.load(taskId));
|
||||
if (!task.hasMember(user)) throw forbidden("You are not a member of {0}",task.name());
|
||||
@@ -277,10 +271,16 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
|
||||
private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||
var json = json(ex);
|
||||
LOG.log(WARNING,"Missing permission check in {0}.postTaskList!",getClass().getSimpleName());
|
||||
var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false;
|
||||
var projectId = json.has(PROJECT_ID) && json.get(PROJECT_ID) instanceof Number number ? number.longValue() : null;
|
||||
if (isSet(projectId)) return sendContent(ex,mapValues(taskDb.listRootTasks(projectId, user,showClosed)));
|
||||
var parentTaskId = json.has(PARENT_TASK_ID) && json.get(PARENT_TASK_ID) instanceof Number number ? number.longValue() : null;
|
||||
if (isSet(projectId)) {
|
||||
if (parentTaskId == null) return sendContent(ex,mapValues(taskDb.listRootTasks(projectId, user,showClosed)));
|
||||
var projectTasks = taskDb.listProjectTasks(projectId,parentTaskId);
|
||||
loadMembers(projectTasks.values());
|
||||
return sendContent(ex,mapValues(projectTasks));
|
||||
}
|
||||
if (isSet(parentTaskId)) return sendContent(ex,mapValues(taskDb.listChildrenOf(parentTaskId,user,showClosed)));
|
||||
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED,ex);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"abort": "abbrechen",
|
||||
"actions": "Aktionen",
|
||||
"add_login_service": "Login-Service anlegen",
|
||||
"add_new": "{0} hinzufügen",
|
||||
"add_member": "Mitarbeiter hinzufügen",
|
||||
"add_position": "hinzufügen",
|
||||
"add_subtask": "Unteraufgabe hinzufügen",
|
||||
@@ -185,6 +184,7 @@
|
||||
"settings" : "Einstellungen",
|
||||
"show": "anzeigen",
|
||||
"show_closed": "geschlossene anzeigen",
|
||||
"show_kanban": "Kanban-Ansicht",
|
||||
"start_date": "Startdatum",
|
||||
"state": "Status",
|
||||
"state_cancelled": "abgebrochen",
|
||||
|
||||
Reference in New Issue
Block a user