implemented opening and closing of projects right from the project list
This commit is contained in:
@@ -72,7 +72,7 @@ public class Project implements Mappable {
|
|||||||
switch (key){
|
switch (key){
|
||||||
case DESCRIPTION: description = json.getString(key); break;
|
case DESCRIPTION: description = json.getString(key); break;
|
||||||
case NAME: name = json.getString(key); break;
|
case NAME: name = json.getString(key); break;
|
||||||
case STATUS: status = Status.of(json.getInt(key)); break;
|
case STATUS: status = json.get(key) instanceof Number number ? Status.of(number.intValue()) : Status.valueOf(json.getString(key)); break;
|
||||||
default: key = null;
|
default: key = null;
|
||||||
}
|
}
|
||||||
if (key != null) dirtyFields.add(key);
|
if (key != null) dirtyFields.add(key);
|
||||||
|
|||||||
BIN
frontend/public/fontawesome-webfont.woff
Normal file
BIN
frontend/public/fontawesome-webfont.woff
Normal file
Binary file not shown.
@@ -6,6 +6,8 @@
|
|||||||
let companies = $state(null);
|
let companies = $state(null);
|
||||||
let value = 0;
|
let value = 0;
|
||||||
|
|
||||||
|
let sortedCompanies = $derived.by(() => Object.values(companies).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
async function loadCompanies(){
|
async function loadCompanies(){
|
||||||
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`;
|
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`;
|
||||||
var resp = await fetch(url,{ credentials: 'include'});
|
var resp = await fetch(url,{ credentials: 'include'});
|
||||||
@@ -27,8 +29,8 @@
|
|||||||
{#if companies}
|
{#if companies}
|
||||||
<select onchange={select} bind:value>
|
<select onchange={select} bind:value>
|
||||||
<option value={0}>{caption}</option>
|
<option value={0}>{caption}</option>
|
||||||
{#each companies as company,idx}
|
{#each sortedCompanies as company}
|
||||||
<option value={idx}>{company.name}</option>
|
<option value={company.id}>{company.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
body: JSON.stringify(project)
|
body: JSON.stringify(project)
|
||||||
});
|
});
|
||||||
if (resp.ok){
|
if (resp.ok){
|
||||||
router.navigate('/project');
|
var newProject = await resp.json();
|
||||||
|
router.navigate(`/project/${newProject.id}/view`);
|
||||||
} else {
|
} else {
|
||||||
error = await resp.text();
|
error = await resp.text();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
let error = $state(null);
|
let error = $state(null);
|
||||||
let projects = $state(null);
|
let projects = $state(null);
|
||||||
let companies = $state(null);
|
let companies = $state(null);
|
||||||
|
let showClosed = $state(router.query.closed == "show");
|
||||||
|
|
||||||
let sortedProjects = $derived.by(() => Object.values(projects).sort((a, b) => a.name.localeCompare(b.name)));
|
let sortedProjects = $derived.by(() => Object.values(projects).sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
|
||||||
@@ -16,7 +17,11 @@
|
|||||||
if (resp.ok){
|
if (resp.ok){
|
||||||
companies = await resp.json();
|
companies = await resp.json();
|
||||||
url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/list`;
|
url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/list`;
|
||||||
resp = await fetch(url,{credentials:'include'});
|
resp = await fetch(url,{
|
||||||
|
credentials:'include',
|
||||||
|
method:'POST',
|
||||||
|
body:JSON.stringify({show_closed:showClosed})
|
||||||
|
});
|
||||||
if (resp.ok){
|
if (resp.ok){
|
||||||
projects = await resp.json();
|
projects = await resp.json();
|
||||||
} else {
|
} else {
|
||||||
@@ -27,6 +32,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function setState(pid,state_name){
|
||||||
|
const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/project/${pid}`;
|
||||||
|
const resp = await fetch(url,{
|
||||||
|
credentials:'include',
|
||||||
|
method:'PATCH',
|
||||||
|
body:JSON.stringify({status:state_name})
|
||||||
|
});
|
||||||
|
if (resp.ok){
|
||||||
|
var prj = await resp.json();
|
||||||
|
projects[prj.id].status = prj.status;
|
||||||
|
} else {
|
||||||
|
error = await resp.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(pid){
|
||||||
|
router.navigate(`/project/${pid}/view`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleClosed(){
|
||||||
|
router.navigate(showClosed?'/project':'/project?closed=show');
|
||||||
|
showClosed = !showClosed;
|
||||||
|
loadProjects();
|
||||||
|
}
|
||||||
|
|
||||||
onMount(loadProjects);
|
onMount(loadProjects);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -36,10 +66,11 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>
|
<legend>
|
||||||
{t('projects')}
|
{t('projects')}
|
||||||
<button onclick={() => router.navigate('/project/add')}>{t('create_new')}</button>
|
<button onclick={() => router.navigate('/project/add')}><span class="symbol"></span> {t('create_new_project')}</button>
|
||||||
|
<button onclick={toggleClosed}><span class="symbol"></span> {t(showClosed?'hide_closed':'show_closed')}</button>
|
||||||
</legend>
|
</legend>
|
||||||
{#if projects}
|
{#if projects}
|
||||||
<table>
|
<table class="project list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{t('name')}</th>
|
<th>{t('name')}</th>
|
||||||
@@ -51,9 +82,11 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each sortedProjects as project}
|
{#each sortedProjects as project}
|
||||||
<tr onclick={() => router.navigate(`/project/${project.id}/view`)}>
|
<tr>
|
||||||
<td>{project.name}</td>
|
<td class="name" onclick={() => show(project.id)} >
|
||||||
<td>
|
{project.name}
|
||||||
|
</td>
|
||||||
|
<td class="company" onclick={() => show(project.id)} >
|
||||||
{#if project.company_id}
|
{#if project.company_id}
|
||||||
{companies[project.company_id].name}
|
{companies[project.company_id].name}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -61,11 +94,20 @@
|
|||||||
<td>
|
<td>
|
||||||
{t("state_"+project.status.name.toLowerCase())}
|
{t("state_"+project.status.name.toLowerCase())}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="members" onclick={() => show(project.id)} >
|
||||||
{#each Object.entries(project.members) as [uid,member]}
|
{#each Object.entries(project.members) as [uid,member]}
|
||||||
<div>{member.user.name}</div>
|
<div>{member.user.name}</div>
|
||||||
{/each}
|
{/each}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<button class="edit symbol" title={t('edit')}></button>
|
||||||
|
{#if project.status.code < 60}
|
||||||
|
<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>
|
||||||
|
{:else}
|
||||||
|
<button class="open symbol" title={t('open')} onclick={() => setState(project.id,'OPEN')} ></button>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import static de.srsoftware.umbrella.core.model.Permission.OWNER;
|
|||||||
import static de.srsoftware.umbrella.core.model.Status.OPEN;
|
import static de.srsoftware.umbrella.core.model.Status.OPEN;
|
||||||
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
|
import static de.srsoftware.umbrella.project.Constants.CONFIG_DATABASE;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
|
|
||||||
import static java.net.HttpURLConnection.HTTP_OK;
|
import static java.net.HttpURLConnection.HTTP_OK;
|
||||||
import static java.util.Comparator.comparing;
|
import static java.util.Comparator.comparing;
|
||||||
|
|
||||||
@@ -54,7 +53,6 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case LIST -> listUserProjects(ex,user.get());
|
|
||||||
case null -> postProject(ex,user.get());
|
case null -> postProject(ex,user.get());
|
||||||
default -> {
|
default -> {
|
||||||
var projectId = Long.parseLong(head);
|
var projectId = Long.parseLong(head);
|
||||||
@@ -108,7 +106,7 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case LIST -> listCompanyProjects(ex,user.get());
|
case LIST -> postProjectList(ex,user.get());
|
||||||
case null -> postProject(ex,user.get());
|
case null -> postProject(ex,user.get());
|
||||||
default -> super.doGet(path,ex);
|
default -> super.doGet(path,ex);
|
||||||
};
|
};
|
||||||
@@ -132,15 +130,20 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
return sendContent(ex,map);
|
return sendContent(ex,map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean postProjectList(HttpExchange ex, UmbrellaUser user) throws IOException {
|
||||||
|
var json = json(ex);
|
||||||
|
var showClosed = json.has(SHOW_CLOSED) && json.get(SHOW_CLOSED) instanceof Boolean bool ? bool : false;
|
||||||
|
if (json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number companyId) return listCompanyProjects(ex, user, companyId.longValue());
|
||||||
|
return listUserProjects(ex,user,showClosed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Collection<Project> listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
public Collection<Project> listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException {
|
||||||
return projects.ofCompany(companyId, includeClosed).values().stream().sorted(comparing(Project::name)).toList();
|
return projects.ofCompany(companyId, includeClosed).values().stream().sorted(comparing(Project::name)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user, long companyId) throws IOException, UmbrellaException {
|
||||||
var json = json(ex);
|
|
||||||
if (!(json.has(COMPANY_ID) && json.get(COMPANY_ID) instanceof Number cid)) throw missingFieldException(COMPANY_ID);
|
|
||||||
var companyId = cid.longValue();
|
|
||||||
var company = companies.get(companyId);
|
var company = companies.get(companyId);
|
||||||
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
|
if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name());
|
||||||
var projects = listCompanyProjects(companyId,false)
|
var projects = listCompanyProjects(companyId,false)
|
||||||
@@ -155,9 +158,9 @@ public class ProjectModule extends BaseHandler implements ProjectService {
|
|||||||
return projects.ofUser(userId, includeClosed);
|
return projects.ofUser(userId, includeClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean listUserProjects(HttpExchange ex, UmbrellaUser user) throws IOException, UmbrellaException {
|
private boolean listUserProjects(HttpExchange ex, UmbrellaUser user, boolean showClosed) throws IOException, UmbrellaException {
|
||||||
var projects = new HashMap<Long,Map<String,Object>>();
|
var projects = new HashMap<Long,Map<String,Object>>();
|
||||||
for (var entry : listUserProjects(user.id(),false).entrySet()) {
|
for (var entry : listUserProjects(user.id(),showClosed).entrySet()) {
|
||||||
var project = entry.getValue();
|
var project = entry.getValue();
|
||||||
var map = project.toMap();
|
var map = project.toMap();
|
||||||
var members = new HashMap<Long,Map<String,Object>>();
|
var members = new HashMap<Long,Map<String,Object>>();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"company": "Firma",
|
"company": "Firma",
|
||||||
"company_optional": "Firma (optional)",
|
"company_optional": "Firma (optional)",
|
||||||
"confirmation": "Bestätigung",
|
"confirmation": "Bestätigung",
|
||||||
|
"complete": "abschließen",
|
||||||
"contact": "Kontakte",
|
"contact": "Kontakte",
|
||||||
"contained_tax": "enthaltene Steuer",
|
"contained_tax": "enthaltene Steuer",
|
||||||
"content": "Inhalt",
|
"content": "Inhalt",
|
||||||
@@ -166,6 +167,8 @@
|
|||||||
"service": "Service",
|
"service": "Service",
|
||||||
"settings" : "Eisntellungen",
|
"settings" : "Eisntellungen",
|
||||||
"state": "Status",
|
"state": "Status",
|
||||||
|
"state_cancelled": "abgebrochen",
|
||||||
|
"state_complete": "abgeschlossen",
|
||||||
"state_declined": "abgelehnt",
|
"state_declined": "abgelehnt",
|
||||||
"state_delayed": "verspätet",
|
"state_delayed": "verspätet",
|
||||||
"state_error": "Fehler",
|
"state_error": "Fehler",
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "awesome";
|
||||||
|
src: url("../fontawesome-webfont.woff");
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
@@ -89,3 +94,9 @@ td, tr{
|
|||||||
.task.complete > .name:before {
|
.task.complete > .name:before {
|
||||||
content: "✓ ";
|
content: "✓ ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
|
font-family: awesome;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
BIN
web/src/main/resources/web/fontawesome-webfont.woff
Normal file
BIN
web/src/main/resources/web/fontawesome-webfont.woff
Normal file
Binary file not shown.
Reference in New Issue
Block a user