Browse Source

working on project view

kanban
Stephan Richter 4 months ago
parent
commit
8ab3e8792a
  1. 27
      core/src/main/java/de/srsoftware/umbrella/core/model/Project.java
  2. 31
      core/src/main/java/de/srsoftware/umbrella/core/model/Status.java
  3. 6
      core/src/main/java/de/srsoftware/umbrella/core/model/Task.java
  4. 56
      frontend/src/Components/ListTask.svelte
  5. 5
      frontend/src/Components/TaskList.svelte
  6. 7
      frontend/src/routes/project/View.svelte
  7. 3
      project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java
  8. 9
      project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java
  9. 30
      task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java
  10. 8
      task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java

27
core/src/main/java/de/srsoftware/umbrella/core/model/Project.java

@ -10,34 +10,7 @@ import java.sql.SQLException; @@ -10,34 +10,7 @@ import java.sql.SQLException;
import java.util.*;
public record Project(long id, String name, String description, Status status, Long companyId, boolean showClosed, Collection<Member> members) implements Mappable {
public enum Status{
Open(10),
Started(20),
Pending(40),
Complete(60),
Cancelled(100);
private int code;
Status(int code){
this.code = code;
}
public int code(){
return code;
}
public static Status of(int code){
return switch (code){
case 10 -> Open;
case 20 -> Started;
case 40 -> Pending;
case 60 -> Complete;
case 100 -> Cancelled;
default -> throw new IllegalArgumentException();
};
}
}
public boolean hasMember(UmbrellaUser user) {
for (var member : members){

31
core/src/main/java/de/srsoftware/umbrella/core/model/Status.java

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;
public enum Status{
OPEN(10),
STARTED(20),
PENDING(40),
COMPLETE(60),
CANCELLED(100);
private int code;
Status(int code){
this.code = code;
}
public int code(){
return code;
}
public static Status of(int code){
return switch (code){
case 10 -> OPEN;
case 20 -> STARTED;
case 40 -> PENDING;
case 60 -> COMPLETE;
case 100 -> CANCELLED;
default -> throw new IllegalArgumentException();
};
}
}

6
core/src/main/java/de/srsoftware/umbrella/core/model/Task.java

@ -12,7 +12,7 @@ import java.time.LocalDate; @@ -12,7 +12,7 @@ import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
public record Task(long id, long projectId, Long parentTaskId, String name, String description, int status, Double estimatedTime, LocalDate start, LocalDate dueDate,boolean showClosed, boolean noIndex) implements Mappable {
public record Task(long id, long projectId, Long parentTaskId, String name, String description, Status status, Double estimatedTime, LocalDate start, LocalDate dueDate, boolean showClosed, boolean noIndex) implements Mappable {
public static Task of(ResultSet rs) throws SQLException {
var estTime = rs.getDouble(EST_TIME);
var parentTaskId = rs.getLong(PARENT_TASK_ID);
@ -24,7 +24,7 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri @@ -24,7 +24,7 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri
parentTaskId == 0d ? null : parentTaskId,
rs.getString(NAME),
rs.getString(DESCRIPTION),
rs.getInt(STATUS),
Status.of(rs.getInt(STATUS)),
estTime == 0d ? null : estTime,
startDate != null ? LocalDate.parse(startDate) : null,
dueDate != null ? LocalDate.parse(dueDate) : null,
@ -41,7 +41,7 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri @@ -41,7 +41,7 @@ public record Task(long id, long projectId, Long parentTaskId, String name, Stri
map.put(PARENT_TASK_ID, parentTaskId);
map.put(NAME, name);
map.put(DESCRIPTION, Map.of(SOURCE,description,RENDERED,markdown(description)));
map.put(STATUS, status);
map.put(STATUS, Map.of(NAME,status.name(),STATUS_CODE,status.code()));
map.put(ESTIMATED_TIME, estimatedTime);
map.put(START_DATE,start);
map.put(DUE_DATE,dueDate);

56
frontend/src/Components/ListTask.svelte

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
<script>
import { t } from '../translations.svelte.js';
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import TaskList from './TaskList.svelte';
const router = useTinyRouter();
let { estimated_time, task } = $props();
let children = $state(null);
let error = $state(null);
async function loadChildren(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/list`;
var data = {parent_task_id:+task.id};
if (task.show_closed) data.show_closed = true;
const resp = await fetch(url,{
credentials:'include',
method:'POST',
body:JSON.stringify(data)
});
if (resp.ok){
children = await resp.json();
error = null;
} else {
error = await resp.text();
}
}
function openTask(evt){
evt.preventDefault();
router.navigate(`/task/${task.id}/view`);
}
if (task.estimated_time){
estimated_time.sum += task.estimated_time;
console.log(estimated_time)
}
onMount(loadChildren);
</script>
<li>
<span onclick={openTask} class={task.status.name.toLowerCase()}>
{task.name}
{#if task.estimated_time}
<span class="error">({+task.estimated_time}&nbsp;h)</span>
{/if}
</span>
{#if error}
<span class="error">{error}</span>
{/if}
{#if children}
<TaskList tasks={children} bind:estimated_time={estimated_time} />
{/if}
</li>

5
frontend/src/Components/TaskList.svelte

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
<script>
import { t } from '../translations.svelte.js';
import ListTask from './ListTask.svelte';
let { tasks } = $props();
let { estimated_time, tasks } = $props();
let sortedTasks = $derived.by(() => Object.values(tasks).sort((a, b) => a.name.localeCompare(b.name)));
@ -10,6 +11,6 @@ @@ -10,6 +11,6 @@
<ul>
{#each sortedTasks as task}
<li>{task.name}</li>
<ListTask {task} bind:estimated_time={estimated_time} />
{/each}
</ul>

7
frontend/src/routes/project/View.svelte

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
let project = $state(null);
let error = $state(null);
let tasks = $state(null);
let estimated_time = $state({sum:0});
async function loadProject(){
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${id}`;
@ -67,15 +68,17 @@ @@ -67,15 +68,17 @@
<th>{t('description')}</th>
<td>{@html project.description.rendered}</td>
</tr>
{#if estimated_time.sum}
<tr>
<th>{t('estimated_time')}</th>
<td class="error">TODO</td>
<td>{estimated_time.sum} h</td>
</tr>
{/if}
<tr>
<th>{t('tasks')}</th>
<td>
{#if tasks}
<TaskList {tasks} />
<TaskList {tasks} bind:estimated_time={estimated_time} />
{/if}
</td>
</tr>

3
project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java

@ -7,6 +7,7 @@ import static de.srsoftware.umbrella.core.Constants.*; @@ -7,6 +7,7 @@ import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.LIST;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static de.srsoftware.umbrella.core.model.Permission.OWNER;
import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
import static java.lang.Boolean.TRUE;
import static java.util.Comparator.comparing;
@ -158,7 +159,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { @@ -158,7 +159,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
if (json.has(SETTINGS) && json.get(SETTINGS) instanceof JSONObject settingsJson){
showClosed = settingsJson.has(SHOW_CLOSED) && settingsJson.get(SHOW_CLOSED) == TRUE;
}
var prj = new Project(0,name,description,Project.Status.Open,companyId,showClosed, List.of(new Member(user.id(), OWNER)));
var prj = new Project(0,name,description, OPEN,companyId,showClosed, List.of(new Member(user.id(), OWNER)));
prj = projects.save(prj);
return sendContent(ex,prj);
}

9
project/src/main/java/de/srsoftware/umbrella/project/SqliteDb.java

@ -8,7 +8,8 @@ import static de.srsoftware.tools.jdbc.Query.select; @@ -8,7 +8,8 @@ import static de.srsoftware.tools.jdbc.Query.select;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.TABLE_SETTINGS;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
import static de.srsoftware.umbrella.core.model.Project.Status.Open;
import static de.srsoftware.umbrella.core.model.Status.COMPLETE;
import static de.srsoftware.umbrella.core.model.Status.OPEN;
import static de.srsoftware.umbrella.project.Constants.*;
import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.INFO;
@ -65,7 +66,7 @@ CREATE TABLE IF NOT EXISTS {0} ( @@ -65,7 +66,7 @@ CREATE TABLE IF NOT EXISTS {0} (
`{7}` BOOLEAN DEFAULT 0
)""";
try {
var stmt = db.prepareStatement(format(createTable,TABLE_PROJECTS, ID, COMPANY_ID, NAME, DESCRIPTION, STATUS, Open.code(), SHOW_CLOSED));
var stmt = db.prepareStatement(format(createTable,TABLE_PROJECTS, ID, COMPANY_ID, NAME, DESCRIPTION, STATUS, OPEN.code(), SHOW_CLOSED));
stmt.execute();
stmt.close();
} catch (SQLException e) {
@ -154,7 +155,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) @@ -154,7 +155,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
try {
var projects = new HashMap<Long,Project>();
var query = select(ALL).from(TABLE_PROJECTS).where(COMPANY_ID, equal(companyId));
if (!includeClosed) query = query.where(STATUS,lessThan(Project.Status.Complete.code()));
if (!includeClosed) query = query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
var project = Project.of(rs);
@ -174,7 +175,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) @@ -174,7 +175,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
try {
var projects = new HashMap<Long,Project>();
var query = select(ALL).from(TABLE_PROJECTS).leftJoin(ID,TABLE_PROJECT_USERS,PROJECT_ID).where(USER_ID, equal(userId));
if (!includeClosed) query = query.where(STATUS,lessThan(Project.Status.Complete.code()));
if (!includeClosed) query = query.where(STATUS,lessThan(COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
var project = Project.of(rs);

30
task/src/main/java/de/srsoftware/umbrella/task/SqliteDb.java

@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.task.Constants.*; @@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.task.Constants.*;
import static java.lang.System.Logger.Level.WARNING;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Status;
import de.srsoftware.umbrella.core.model.Task;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.sql.Connection;
@ -42,14 +43,15 @@ public class SqliteDb implements TaskDb { @@ -42,14 +43,15 @@ public class SqliteDb implements TaskDb {
}
}
public HashMap<Long, Task> listRootTasks(UmbrellaUser user, Long projectId) {
public HashMap<Long, Task> listRootTasks(Long projectId, UmbrellaUser user, boolean showClosed) {
try {
var tasks = new HashMap<Long,Task>();
var rs = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
.where(PROJECT_ID,equal(projectId))
.where(USER_ID,equal(user.id()))
.where(PARENT_TASK_ID,isNull())
.exec(db);
.where(PARENT_TASK_ID,isNull());
if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code()));
var rs = query.exec(db);
while (rs.next()){
var task = Task.of(rs);
tasks.put(task.id(),task);
@ -61,4 +63,24 @@ public class SqliteDb implements TaskDb { @@ -61,4 +63,24 @@ public class SqliteDb implements TaskDb {
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project id");
}
}
public HashMap<Long, Task> listChildrenOf(Long parentTaskId, UmbrellaUser user, boolean showClosed) {
try {
var tasks = new HashMap<Long,Task>();
var query = select(ALL).from(TABLE_TASKS).leftJoin(ID,TABLE_TASKS_USERS,TASK_ID)
.where(PARENT_TASK_ID,equal(parentTaskId))
.where(USER_ID,equal(user.id()));
if (!showClosed) query.where(STATUS,lessThan(Status.COMPLETE.code()));
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){
LOG.log(WARNING,"Failed to load child tasks (parentTaskId: {0}, user_id: {1}",parentTaskId,user.id(),e);
throw new UmbrellaException(HTTP_SERVER_ERROR,"Failed to load tasks for project id");
}
}
}

8
task/src/main/java/de/srsoftware/umbrella/task/TaskModule.java

@ -4,8 +4,7 @@ package de.srsoftware.umbrella.task; @@ -4,8 +4,7 @@ package de.srsoftware.umbrella.task;
import static de.srsoftware.tools.Optionals.is0;
import static de.srsoftware.tools.Optionals.isSet;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.Constants.COMPANY_ID;
import static de.srsoftware.umbrella.core.Constants.PROJECT_ID;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Paths.LIST;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED;
import static de.srsoftware.umbrella.core.Util.mapValues;
@ -117,8 +116,11 @@ public class TaskModule extends BaseHandler implements TaskService { @@ -117,8 +116,11 @@ public class TaskModule extends BaseHandler implements TaskService {
private boolean postTaskList(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
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(user,projectId)));
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(parentTaskId)) return sendContent(ex,mapValues(taskDb.listChildrenOf(parentTaskId,user,showClosed)));
return sendEmptyResponse(HTTP_NOT_IMPLEMENTED,ex);
}

Loading…
Cancel
Save