working on project settings update:
adding/updating members not implemented
This commit is contained in:
@@ -12,6 +12,7 @@ public class Constants {
|
||||
public static final String ATTACHMENTS = "attachments";
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
public static final String BODY = "body";
|
||||
public static final String CODE = "code";
|
||||
public static final String COMPANY = "company";
|
||||
public static final String COMPANY_ID = "company_id";
|
||||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
@@ -10,6 +10,6 @@ import java.util.Map;
|
||||
public record Member(long userId, Permission permission) implements Mappable {
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
return Map.of(USER_ID,userId,PERMISSION,permission.name());
|
||||
return Map.of(USER_ID,userId,PERMISSION,permission.toMap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.core.model;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.CODE;
|
||||
import static de.srsoftware.umbrella.core.Constants.NAME;
|
||||
import static java.text.MessageFormat.format;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Map;
|
||||
|
||||
public enum Permission {
|
||||
public enum Permission implements Mappable {
|
||||
OWNER(1),
|
||||
EDIT(2),
|
||||
ASSIGNEE(3),
|
||||
@@ -27,4 +32,9 @@ public enum Permission {
|
||||
}
|
||||
throw new InvalidParameterException(format("{0} is not a valid permission code"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
return Map.of(NAME,name(),CODE,code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
/* © SRSoftware 2025 */
|
||||
package de.srsoftware.umbrella.core.model;
|
||||
|
||||
import static de.srsoftware.tools.Optionals.isSet;
|
||||
import static de.srsoftware.tools.Optionals.nullable;
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Util.markdown;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidFieldException;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||
|
||||
import de.srsoftware.tools.Mappable;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Project implements Mappable {
|
||||
private final Collection<Member> members;
|
||||
private final Map<Long,Member> members;
|
||||
private final boolean showClosed;
|
||||
private final Long companyId;
|
||||
private Status status;
|
||||
@@ -21,7 +26,7 @@ public class Project implements Mappable {
|
||||
private String description;
|
||||
private final Set<String> dirtyFields = new HashSet<>();
|
||||
|
||||
public Project(long id, String name, String description, Status status, Long companyId, boolean showClosed, Collection<Member> members) {
|
||||
public Project(long id, String name, String description, Status status, Long companyId, boolean showClosed, Map<Long,Member> members) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
@@ -44,17 +49,15 @@ public class Project implements Mappable {
|
||||
}
|
||||
|
||||
public boolean hasMember(UmbrellaUser user) {
|
||||
for (var member : members){
|
||||
if (member.userId() == user.id()) return true;
|
||||
}
|
||||
return false;
|
||||
var member = members.get(user.id());
|
||||
return isSet(member) && member.userId() == user.id();
|
||||
}
|
||||
|
||||
public long id(){
|
||||
return id;
|
||||
}
|
||||
|
||||
public Collection<Member> members(){
|
||||
public Map<Long,Member> members(){
|
||||
return members;
|
||||
}
|
||||
|
||||
@@ -64,13 +67,14 @@ public class Project implements Mappable {
|
||||
|
||||
public static Project of(ResultSet rs) throws SQLException {
|
||||
var companyId = rs.getLong(COMPANY_ID);
|
||||
return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),Status.of(rs.getInt(STATUS)),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new ArrayList<>());
|
||||
return new Project(rs.getLong(ID),rs.getString(NAME),rs.getString(DESCRIPTION),Status.of(rs.getInt(STATUS)),companyId == 0 ? null : companyId,rs.getBoolean(SHOW_CLOSED),new HashMap<>());
|
||||
}
|
||||
|
||||
public Project patch(JSONObject json) {
|
||||
for (var key : json.keySet()){
|
||||
switch (key){
|
||||
case DESCRIPTION: description = json.getString(key); break;
|
||||
case MEMBERS: patchMembers(json.getJSONObject(MEMBERS)); break;
|
||||
case NAME: name = json.getString(key); break;
|
||||
case STATUS: status = json.get(key) instanceof Number number ? Status.of(number.intValue()) : Status.valueOf(json.getString(key)); break;
|
||||
default: key = null;
|
||||
@@ -80,6 +84,20 @@ public class Project implements Mappable {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void patchMembers(JSONObject json) throws UmbrellaException {
|
||||
for (var key : json.keySet()){
|
||||
long userId;
|
||||
try {
|
||||
userId = Long.parseLong(key);
|
||||
} catch (NumberFormatException e) {
|
||||
throw invalidFieldException(USER_ID,"long");
|
||||
}
|
||||
if (!(json.get(key) instanceof Number number)) throw invalidFieldException(PERMISSION,"int");
|
||||
var permission = Permission.of(number.intValue());
|
||||
members.put(userId,new Member(userId,permission));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean showClosed(){
|
||||
return showClosed;
|
||||
}
|
||||
@@ -91,13 +109,17 @@ public class Project implements Mappable {
|
||||
@Override
|
||||
public Map<String, Object> toMap() {
|
||||
var map = new HashMap<String, Object>();
|
||||
var memberMap = new HashMap<Long,Map<String,Object>>();
|
||||
if (members != null) for (var entry : members.entrySet()){
|
||||
memberMap.put(entry.getKey(),entry.getValue().toMap());
|
||||
}
|
||||
map.put(ID,id);
|
||||
map.put(NAME,name);
|
||||
map.put(DESCRIPTION,Map.of(SOURCE,description,RENDERED,markdown(description)));
|
||||
map.put(STATUS,Map.of(STATUS_CODE,status.code(), NAME,status.name()));
|
||||
map.put(COMPANY_ID,companyId);
|
||||
map.put(SHOW_CLOSED,showClosed);
|
||||
map.put(MEMBERS,members == null ? List.of() : members.stream().map(Member::toMap).toList());
|
||||
map.put(MEMBERS,memberMap);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
function openTask(evt){
|
||||
evt.preventDefault();
|
||||
router.navigate(`/task/${task.id}/view`);
|
||||
//router.navigate(`/task/${task.id}/view`);
|
||||
location.href = `https://umbrella.srsoftware.de/task/${task.id}/view`;
|
||||
}
|
||||
|
||||
if (task.estimated_time){
|
||||
|
||||
42
frontend/src/Components/MemberEditor.svelte
Normal file
42
frontend/src/Components/MemberEditor.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '../translations.svelte.js';
|
||||
import PermissionSelector from './PermissionSelector.svelte';
|
||||
let { members, updatePermission = (uid,perm) => console.log({user:uid,perm:perm}) } = $props();
|
||||
let error = $state(null);
|
||||
let sortedMembers = $derived.by(() => Object.values(members).sort((a, b) => a.user.name.localeCompare(b.user.name)));
|
||||
|
||||
let permissions = $state(null);
|
||||
|
||||
async function loadPermissions(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/task/permissions`;
|
||||
var resp = await fetch(url,{credentials: 'include'});
|
||||
if (resp.ok){
|
||||
permissions = await resp.json();
|
||||
} else {
|
||||
message = await resp.text();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(loadPermissions);
|
||||
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
<table>
|
||||
<tbody>
|
||||
{#each sortedMembers as member,i}
|
||||
<tr>
|
||||
{#if !i}
|
||||
<th rowspan={sortedMembers.length}>{t('members')}</th>
|
||||
{/if}
|
||||
<td>{member.user.name}</td>
|
||||
<td>
|
||||
<PermissionSelector {permissions} selected={member.permission.code} onchange={(perm) => updatePermission(member.user.id,perm)} />
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
26
frontend/src/Components/PermissionSelector.svelte
Normal file
26
frontend/src/Components/PermissionSelector.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import {t} from '../translations.svelte.js';
|
||||
let {
|
||||
caption = t('select_permission'),
|
||||
selected = $bindable(0),
|
||||
onchange = (val) => console.log('changed to',val),
|
||||
permissions = null
|
||||
} = $props();
|
||||
|
||||
function onSelect(newVal){
|
||||
onchange({name:permissions[newVal],code:newVal});
|
||||
}
|
||||
|
||||
let message = $state(t('loading'));
|
||||
</script>
|
||||
|
||||
{#if permissions}
|
||||
<select bind:value={selected} onchange={() => onSelect(selected)}>
|
||||
{#each Object.entries(permissions) as [k,perm]}
|
||||
<option value={+k}>{t('permission_'+perm.toLowerCase())}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<span>{message}</span>
|
||||
{/if}
|
||||
@@ -5,12 +5,14 @@
|
||||
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
|
||||
import LineEditor from '../../Components/LineEditor.svelte';
|
||||
import StateSelector from '../../Components/StateSelector.svelte';
|
||||
import MemberEditor from '../../Components/MemberEditor.svelte';
|
||||
|
||||
let { id } = $props();
|
||||
let project = $state(null);
|
||||
let error = $state(null);
|
||||
let tasks = $state(null);
|
||||
let estimated_time = $state({sum:0});
|
||||
let showSettings = $state(false);
|
||||
|
||||
async function loadProject(){
|
||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${id}`;
|
||||
@@ -54,6 +56,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updatePermission(user_id,permission){
|
||||
let members = {};
|
||||
members[user_id] = permission.code;
|
||||
update({members:members});
|
||||
}
|
||||
|
||||
onMount(loadProject);
|
||||
</script>
|
||||
|
||||
@@ -68,6 +76,7 @@
|
||||
<th>{t('project')}</th>
|
||||
<td class="name">
|
||||
<LineEditor bind:value={project.name} editable={true} onSet={val => update({name:val})} />
|
||||
<button class="symbol" onclick={() => showSettings = true}></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -110,16 +119,12 @@
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t('members')}</th>
|
||||
<td>
|
||||
<ul>
|
||||
{#each Object.entries(project.members) as [uid,member]}
|
||||
<li>{member.user.name}: {t('permission.'+member.permission)}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/if}
|
||||
{#if showSettings}
|
||||
<fieldset class="project settings">
|
||||
<legend>{t('settings')}</legend>
|
||||
<MemberEditor members={project.members} {updatePermission} />
|
||||
</fieldset>
|
||||
{/if}
|
||||
@@ -3,7 +3,6 @@ package de.srsoftware.umbrella.items;
|
||||
|
||||
public class Constants {
|
||||
private Constants(){}
|
||||
public static final String CODE = "code";
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.items.database";
|
||||
public static final String TABLE_ITEMS = "items";
|
||||
public static final String TAX = "tax";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package de.srsoftware.umbrella.items;
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Constants.CODE;
|
||||
import static de.srsoftware.umbrella.core.Util.markdown;
|
||||
import static de.srsoftware.umbrella.items.Constants.*;
|
||||
|
||||
|
||||
@@ -118,9 +118,9 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
private boolean addMembers(Project project, HttpExchange ex) throws IOException {
|
||||
var map = project.toMap();
|
||||
var members = new HashMap<Long,Map<String,Object>>();
|
||||
for (var member : project.members()){
|
||||
var perm = member.permission().name();
|
||||
var userId = member.userId();
|
||||
for (var entry : project.members().entrySet()){
|
||||
var userId = entry.getKey();
|
||||
var perm = entry.getValue().permission().toMap();
|
||||
members.put(userId,Map.of(USER,users.loadUser(userId).toMap(),PERMISSION,perm));
|
||||
}
|
||||
if (!members.isEmpty()) map.put(MEMBERS,members);
|
||||
@@ -170,9 +170,9 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
||||
var map = project.toMap();
|
||||
var members = new HashMap<Long,Map<String,Object>>();
|
||||
var userMap = new HashMap<Long,UmbrellaUser>();
|
||||
for (var member : project.members()){
|
||||
var perm = member.permission().name();
|
||||
var userId = member.userId();
|
||||
for (var memberEntry : project.members().entrySet()){
|
||||
var perm = memberEntry.getValue().permission().name();
|
||||
var userId = memberEntry.getKey();
|
||||
var u = userMap.get(userId);
|
||||
if (u == null) userMap.put(userId,u = users.loadUser(userId));
|
||||
members.put(userId,Map.of(USER,u.toMap(),PERMISSION,perm));
|
||||
@@ -205,7 +205,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, OPEN,companyId,showClosed, List.of(new Member(user.id(), OWNER)));
|
||||
var prj = new Project(0,name,description, OPEN,companyId,showClosed, Map.of(user.id(),new Member(user.id(), OWNER)));
|
||||
prj = projects.save(prj);
|
||||
return sendContent(ex,prj);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SqliteDb implements ProjectDb {
|
||||
var userId = rs.getLong(USER_ID);
|
||||
var projectId = rs.getLong(PROJECT_ID);
|
||||
var permission = Permission.of(rs.getInt(PERMISSIONS));
|
||||
projects.get(projectId).members().add(new Member(userId,permission));
|
||||
projects.get(projectId).members().put(userId, new Member(userId,permission));
|
||||
}
|
||||
rs.close();
|
||||
return projects;
|
||||
@@ -198,7 +198,7 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
|
||||
if (id != null){
|
||||
if (!prj.members().isEmpty()) {
|
||||
var query = insertInto(TABLE_PROJECT_USERS, PROJECT_ID, USER_ID, PERMISSIONS);
|
||||
for (var member : prj.members()) query.values(id, member.userId(), member.permission().code());
|
||||
for (var entry : prj.members().entrySet()) query.values(id, entry.getKey(), entry.getValue().permission().code());
|
||||
query.execute(db).close();
|
||||
}
|
||||
return new Project(id, prj.name(), prj.description(),prj.status(),prj.companyId().orElse(null),prj.showClosed(),prj.members());
|
||||
@@ -208,6 +208,11 @@ CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255)
|
||||
}
|
||||
} else { // Update
|
||||
try {
|
||||
if (prj.dirtyFields().contains(MEMBERS)){
|
||||
// TODO:
|
||||
LOG.log(ERROR,"Updating/Adding project members not implemented!");
|
||||
prj.dirtyFields().remove(MEMBERS);
|
||||
}
|
||||
if (prj.isDirty()){
|
||||
update(TABLE_PROJECTS).set(NAME,DESCRIPTION,STATUS,COMPANY_ID,SHOW_CLOSED).where(ID,equal(prj.id())).prepare(db)
|
||||
.apply(prj.name(),prj.description(),prj.status().code(),prj.companyId(),prj.showClosed())
|
||||
|
||||
@@ -11,6 +11,7 @@ import static de.srsoftware.umbrella.core.ResponseCode.HTTP_NOT_IMPLEMENTED;
|
||||
import static de.srsoftware.umbrella.core.Util.mapValues;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.forbidden;
|
||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||
import static de.srsoftware.umbrella.project.Constants.PERMISSIONS;
|
||||
import static de.srsoftware.umbrella.task.Constants.*;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
@@ -56,6 +57,7 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
if (user.isEmpty()) return unauthorized(ex);
|
||||
var head = path.pop();
|
||||
return switch (head) {
|
||||
case PERMISSIONS -> getPermissionList(ex);
|
||||
case STATES -> getStateList(ex);
|
||||
default -> super.doGet(path,ex);
|
||||
};
|
||||
@@ -104,6 +106,12 @@ public class TaskModule extends BaseHandler implements TaskService {
|
||||
return sendContent(ex,result);
|
||||
}
|
||||
|
||||
private boolean getPermissionList(HttpExchange ex) throws IOException {
|
||||
var map = new HashMap<Integer,String>();
|
||||
for (var permission : Permission.values()) map.put(permission.code(),permission.name());
|
||||
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());
|
||||
|
||||
@@ -140,6 +140,10 @@
|
||||
"OWNER": "Besitzer"
|
||||
},
|
||||
"permissions": "Berechtigungen",
|
||||
"permission_assignee": "Verantwortlichter",
|
||||
"permission_edit": "bearbeiten",
|
||||
"permission_owner": "Besitzer",
|
||||
"permission_read_only": "lesen",
|
||||
"pos": "Pos",
|
||||
"position": "Position",
|
||||
"positions": "Positionen",
|
||||
@@ -165,7 +169,7 @@
|
||||
"sender_tax_id": "Steuernummer",
|
||||
"sent_email": "Email gesendet",
|
||||
"service": "Service",
|
||||
"settings" : "Eisntellungen",
|
||||
"settings" : "Einstellungen",
|
||||
"state": "Status",
|
||||
"state_cancelled": "abgebrochen",
|
||||
"state_complete": "abgeschlossen",
|
||||
|
||||
@@ -11,7 +11,6 @@ public class Constants {
|
||||
|
||||
public static final String CLIENT_ID = "client_id";
|
||||
public static final String CLIENT_SECRET = "client_secret";
|
||||
public static final String CODE = "code";
|
||||
public static final String CONFIG_DATABASE = "umbrella.modules.user.database";
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import static de.srsoftware.tools.Optionals.*;
|
||||
import static de.srsoftware.tools.Strings.uuid;
|
||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||
import static de.srsoftware.umbrella.core.Constants.*;
|
||||
import static de.srsoftware.umbrella.core.Constants.CODE;
|
||||
import static de.srsoftware.umbrella.core.Paths.LIST;
|
||||
import static de.srsoftware.umbrella.core.Paths.LOGOUT;
|
||||
import static de.srsoftware.umbrella.core.ResponseCode.*;
|
||||
|
||||
@@ -100,3 +100,14 @@ td, tr{
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.settings {
|
||||
position: fixed;
|
||||
background: black;
|
||||
top: 60px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid orange;
|
||||
border-radius: 5px;
|
||||
}
|
||||
Reference in New Issue
Block a user