working on custom states for projects
This commit is contained in:
@@ -10,6 +10,7 @@ public class Constants {
|
|||||||
private Constants(){}
|
private Constants(){}
|
||||||
|
|
||||||
public static final String ADDRESS = "address";
|
public static final String ADDRESS = "address";
|
||||||
|
public static final String ALLOWED_STATES = "allowed_states";
|
||||||
public static final String ATTACHMENTS = "attachments";
|
public static final String ATTACHMENTS = "attachments";
|
||||||
public static final String AUTHORIZATION = "Authorization";
|
public static final String AUTHORIZATION = "Authorization";
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package de.srsoftware.umbrella.core.model;
|
|||||||
import static de.srsoftware.tools.Optionals.nullable;
|
import static de.srsoftware.tools.Optionals.nullable;
|
||||||
import static de.srsoftware.umbrella.core.Constants.*;
|
import static de.srsoftware.umbrella.core.Constants.*;
|
||||||
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
|
import static de.srsoftware.umbrella.core.Util.mapMarkdown;
|
||||||
|
import static de.srsoftware.umbrella.core.model.Status.PREDEFINED;
|
||||||
|
|
||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
@@ -13,6 +14,7 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
public class Project implements Mappable {
|
public class Project implements Mappable {
|
||||||
private final Map<Long,Member> members;
|
private final Map<Long,Member> members;
|
||||||
|
private final Collection<Status> allowedStates;
|
||||||
private boolean showClosed;
|
private boolean showClosed;
|
||||||
private final Long companyId;
|
private final Long companyId;
|
||||||
private int status;
|
private int status;
|
||||||
@@ -21,7 +23,7 @@ public class Project implements Mappable {
|
|||||||
private String description;
|
private String description;
|
||||||
private final Set<String> dirtyFields = new HashSet<>();
|
private final Set<String> dirtyFields = new HashSet<>();
|
||||||
|
|
||||||
public Project(long id, String name, String description, int status, Long companyId, boolean showClosed, Map<Long,Member> members) {
|
public Project(long id, String name, String description, int status, Long companyId, boolean showClosed, Map<Long,Member> members, Collection<Status> allowedStates) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@@ -29,6 +31,11 @@ public class Project implements Mappable {
|
|||||||
this.companyId = companyId;
|
this.companyId = companyId;
|
||||||
this.showClosed = showClosed;
|
this.showClosed = showClosed;
|
||||||
this.members = members;
|
this.members = members;
|
||||||
|
this.allowedStates = allowedStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Status> allowedStates(){
|
||||||
|
return allowedStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Project clean() {
|
public Project clean() {
|
||||||
@@ -81,7 +88,7 @@ public class Project implements Mappable {
|
|||||||
|
|
||||||
public static Project of(ResultSet rs) throws SQLException {
|
public static Project of(ResultSet rs) throws SQLException {
|
||||||
var companyId = rs.getLong(COMPANY_ID);
|
var companyId = rs.getLong(COMPANY_ID);
|
||||||
return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),rs.getInt(STATUS),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new HashMap<>());
|
return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),rs.getInt(STATUS),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new HashMap<>(),PREDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Project patch(JSONObject json) {
|
public Project patch(JSONObject json) {
|
||||||
@@ -120,6 +127,9 @@ public class Project implements Mappable {
|
|||||||
map.put(COMPANY_ID,companyId);
|
map.put(COMPANY_ID,companyId);
|
||||||
map.put(SHOW_CLOSED,showClosed);
|
map.put(SHOW_CLOSED,showClosed);
|
||||||
map.put(MEMBERS,memberMap);
|
map.put(MEMBERS,memberMap);
|
||||||
|
var stateMap = new HashMap<Integer,String>();
|
||||||
|
for (var state : allowedStates) stateMap.put(state.code(),state.name());
|
||||||
|
map.put(ALLOWED_STATES,stateMap);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
/* © SRSoftware 2025 */
|
/* © SRSoftware 2025 */
|
||||||
package de.srsoftware.umbrella.core.model;
|
package de.srsoftware.umbrella.core.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public record Status(String name, int code){
|
public record Status(String name, int code){
|
||||||
public static final Status OPEN = new Status("OPEN",10);
|
public static final Status OPEN = new Status("OPEN",10);
|
||||||
public static final Status STARTED = new Status("STARTED",20);
|
public static final Status STARTED = new Status("STARTED",20);
|
||||||
public static final Status PENDING = new Status("PENDING", 40);
|
public static final Status PENDING = new Status("PENDING", 40);
|
||||||
public static final Status COMPLETE = new Status("COMPLETE",60);
|
public static final Status COMPLETE = new Status("COMPLETE",60);
|
||||||
public static final Status CANCELLED = new Status("CANCELLED", 100);
|
public static final Status CANCELLED = new Status("CANCELLED", 100);
|
||||||
public static final Status[] PREDEFINED = {OPEN, STARTED, PENDING, COMPLETE, CANCELLED};
|
public static final List<Status> PREDEFINED = new ArrayList<>(List.of(OPEN, STARTED, PENDING, COMPLETE, CANCELLED));
|
||||||
|
|
||||||
public static Status of(int code){
|
public static Status of(int code){
|
||||||
return switch (code){
|
return switch (code){
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="state" onclick={() => show(project.id)} >
|
<td class="state" onclick={() => show(project.id)} >
|
||||||
|
|
||||||
{t("state_"+project.status.name.toLowerCase())}
|
{t("state_"+project.allowed_states[project.status].toLowerCase())}
|
||||||
</td>
|
</td>
|
||||||
<td class="members" onclick={() => show(project.id)} >
|
<td class="members" onclick={() => show(project.id)} >
|
||||||
{#each Object.entries(project.members) as [uid,member]}
|
{#each Object.entries(project.members) as [uid,member]}
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
<button class="edit symbol" title={t('edit')}></button>
|
<button class="edit symbol" title={t('edit')}></button>
|
||||||
{#if project.status.code < 60}
|
{#if project.status < 60}
|
||||||
<button class="complete symbol" title={t('complete')} onclick={() => setState(project.id,'COMPLETE')} ></button>
|
<button class="complete symbol" title={t('complete')} onclick={() => setState(project.id,'COMPLETE')} ></button>
|
||||||
<button class="abort symbol" title={t('abort')} onclick={() => setState(project.id,'CANCELLED')} ></button>
|
<button class="abort symbol" title={t('abort')} onclick={() => setState(project.id,'CANCELLED')} ></button>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{t('state')}</th>
|
<th>{t('state')}</th>
|
||||||
<td>
|
<td>
|
||||||
<StateSelector selected={project.status.code} onchange={val => update({status:val})} project_id={id} />
|
<StateSelector selected={project.status} onchange={val => update({status:val})} project_id={id} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{#if project.company}
|
{#if project.company}
|
||||||
@@ -205,7 +205,7 @@
|
|||||||
</th>
|
</th>
|
||||||
<td class="tasks">
|
<td class="tasks">
|
||||||
{#if tasks}
|
{#if tasks}
|
||||||
<TaskList {tasks} {estimated_time} show_closed={project.show_closed} />
|
<TaskList {tasks} {estimated_time} states={project.allowed_states} show_closed={project.show_closed} />
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
estimated_time,
|
estimated_time,
|
||||||
show_closed,
|
show_closed,
|
||||||
siblings,
|
siblings,
|
||||||
|
states = {},
|
||||||
task
|
task
|
||||||
} = $props();
|
} = $props();
|
||||||
let children = $state(null);
|
let children = $state(null);
|
||||||
@@ -117,24 +118,24 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !deleted}
|
{#if !deleted}
|
||||||
<li draggable="true" {ondrop} ondragover={e => e.preventDefault()} {ondragstart} class="task {task.status.name.toLowerCase()}">
|
<li draggable="true" {ondrop} ondragover={e => e.preventDefault()} {ondragstart} class="task {states[task.status].toLowerCase()}">
|
||||||
<LineEditor bind:value={task.name} onclick={openTask} editable={true} onSet={setName} type="a" />
|
<LineEditor bind:value={task.name} onclick={openTask} editable={true} onSet={setName} type="a" />
|
||||||
{#if task.estimated_time}
|
{#if task.estimated_time}
|
||||||
<span class="estimated_time">({+task.estimated_time} h)</span>
|
<span class="estimated_time">({+task.estimated_time} h)</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if task.status.name != 'PENDING'}
|
{#if states[task.status] != 'PENDING'}
|
||||||
<button class="symbol" title={t('do_open')} onclick={() => patchTask({status:'PENDING'})}></button>
|
<button class="symbol" title={t('do_open')} onclick={() => patchTask({status:'PENDING'})}></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if task.status.name != 'OPEN'}
|
{#if states[task.status] != 'OPEN'}
|
||||||
<button class="symbol" title={t('do_open')} onclick={() => patchTask({status:'OPEN'})}></button>
|
<button class="symbol" title={t('do_open')} onclick={() => patchTask({status:'OPEN'})}></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if task.status.name != 'STARTED'}
|
{#if states[task.status] != 'STARTED'}
|
||||||
<button class="symbol" title={t('started')} onclick={() => patchTask({status:'STARTED'})}></button>
|
<button class="symbol" title={t('started')} onclick={() => patchTask({status:'STARTED'})}></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if task.status.name != 'COMPLETE'}
|
{#if states[task.status] != 'COMPLETE'}
|
||||||
<button class="symbol" title={t('complete')} onclick={() => patchTask({status:'COMPLETE'})}></button>
|
<button class="symbol" title={t('complete')} onclick={() => patchTask({status:'COMPLETE'})}></button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if task.status.name != 'CANCELLED'}
|
{#if states[task.status] != 'CANCELLED'}
|
||||||
<button class="symbol" title={t('abort')} onclick={() => patchTask({status:'CANCELLED'})} ></button>
|
<button class="symbol" title={t('abort')} onclick={() => patchTask({status:'CANCELLED'})} ></button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="symbol" title={t('delete_task')} onclick={deleteTask} ></button>
|
<button class="symbol" title={t('delete_task')} onclick={deleteTask} ></button>
|
||||||
@@ -143,7 +144,7 @@
|
|||||||
<span class="error">{error}</span>
|
<span class="error">{error}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if children}
|
{#if children}
|
||||||
<TaskList tasks={children} {estimated_time} {show_closed} />
|
<TaskList {states} tasks={children} {estimated_time} {show_closed} />
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { t } from '../../translations.svelte.js';
|
import { t } from '../../translations.svelte.js';
|
||||||
import ListTask from './ListTask.svelte';
|
import ListTask from './ListTask.svelte';
|
||||||
|
|
||||||
let { estimated_time, show_closed, tasks } = $props();
|
let { estimated_time, show_closed, states = {}, tasks } = $props();
|
||||||
|
|
||||||
let sortedTasks = $derived.by(() => Object.values(tasks).sort((a, b) => a.name.localeCompare(b.name)));
|
let sortedTasks = $derived.by(() => Object.values(tasks).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
@@ -10,6 +10,6 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each sortedTasks as task}
|
{#each sortedTasks as task}
|
||||||
<ListTask {task} siblings={tasks} {estimated_time} show_closed={show_closed || task.show_closed} />
|
<ListTask {states} {task} siblings={tasks} {estimated_time} show_closed={show_closed || task.show_closed} />
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{t('state')}</th>
|
<th>{t('state')}</th>
|
||||||
<td>
|
<td>
|
||||||
<StateSelector selected={task.status.code} onchange={val => update({status:val})} project_id={task.project_id} />
|
<StateSelector selected={task.status} onchange={val => update({status:val})} project_id={task.project_id} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{#if task.description}
|
{#if task.description}
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
showClosed = settingsJson.has(SHOW_CLOSED) && settingsJson.get(SHOW_CLOSED) == TRUE;
|
showClosed = settingsJson.has(SHOW_CLOSED) && settingsJson.get(SHOW_CLOSED) == TRUE;
|
||||||
}
|
}
|
||||||
var owner = Map.of(user.id(),new Member(user,OWNER));
|
var owner = Map.of(user.id(),new Member(user,OWNER));
|
||||||
var prj = new Project(0,name,description, OPEN.code(),companyId,showClosed, owner);
|
var prj = new Project(0,name,description, OPEN.code(),companyId,showClosed, owner, PREDEFINED);
|
||||||
prj = projects.save(prj);
|
prj = projects.save(prj);
|
||||||
|
|
||||||
if (json.has(TAGS) && json.get(TAGS) instanceof JSONArray arr){
|
if (json.has(TAGS) && json.get(TAGS) instanceof JSONArray arr){
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
|
|||||||
for (var member : prj.members().entrySet()) query.values(id, member.getKey(), member.getValue().permission().code());
|
for (var member : prj.members().entrySet()) query.values(id, member.getKey(), member.getValue().permission().code());
|
||||||
query.execute(db).close();
|
query.execute(db).close();
|
||||||
}
|
}
|
||||||
return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members());
|
return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members(),prj.allowedStates());
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new UmbrellaException("Failed to insert project into database");
|
throw new UmbrellaException("Failed to insert project into database");
|
||||||
|
|||||||
Reference in New Issue
Block a user