diff --git a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java index f5a84dc..6c7559b 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/model/Project.java @@ -72,7 +72,7 @@ public class Project implements Mappable { switch (key){ case DESCRIPTION: description = 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; } if (key != null) dirtyFields.add(key); diff --git a/frontend/public/fontawesome-webfont.woff b/frontend/public/fontawesome-webfont.woff new file mode 100644 index 0000000..839528c Binary files /dev/null and b/frontend/public/fontawesome-webfont.woff differ diff --git a/frontend/src/Components/CompanySelector.svelte b/frontend/src/Components/CompanySelector.svelte index a93f522..c34d166 100644 --- a/frontend/src/Components/CompanySelector.svelte +++ b/frontend/src/Components/CompanySelector.svelte @@ -6,6 +6,8 @@ let companies = $state(null); let value = 0; + let sortedCompanies = $derived.by(() => Object.values(companies).sort((a, b) => a.name.localeCompare(b.name))); + async function loadCompanies(){ const url = `${location.protocol}//${location.host.replace('5173','8080')}/api/company/list`; var resp = await fetch(url,{ credentials: 'include'}); @@ -27,8 +29,8 @@ {#if companies} {:else} diff --git a/frontend/src/routes/project/Create.svelte b/frontend/src/routes/project/Create.svelte index 7800492..15bb22e 100644 --- a/frontend/src/routes/project/Create.svelte +++ b/frontend/src/routes/project/Create.svelte @@ -26,7 +26,8 @@ body: JSON.stringify(project) }); if (resp.ok){ - router.navigate('/project'); + var newProject = await resp.json(); + router.navigate(`/project/${newProject.id}/view`); } else { error = await resp.text(); } diff --git a/frontend/src/routes/project/List.svelte b/frontend/src/routes/project/List.svelte index 377b0d7..5f13f8b 100644 --- a/frontend/src/routes/project/List.svelte +++ b/frontend/src/routes/project/List.svelte @@ -7,6 +7,7 @@ let error = $state(null); let projects = $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))); @@ -16,7 +17,11 @@ if (resp.ok){ companies = await resp.json(); 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){ projects = await resp.json(); } 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); @@ -36,10 +66,11 @@
{t('projects')} - + + {#if projects} - +
@@ -51,9 +82,11 @@ {#each sortedProjects as project} - router.navigate(`/project/${project.id}/view`)}> - - + + - + {/each} diff --git a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java index f972978..4b577a1 100644 --- a/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java +++ b/project/src/main/java/de/srsoftware/umbrella/project/ProjectModule.java @@ -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.project.Constants.CONFIG_DATABASE; import static java.lang.Boolean.TRUE; -import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; import static java.net.HttpURLConnection.HTTP_OK; import static java.util.Comparator.comparing; @@ -54,7 +53,6 @@ public class ProjectModule extends BaseHandler implements ProjectService { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head) { - case LIST -> listUserProjects(ex,user.get()); case null -> postProject(ex,user.get()); default -> { var projectId = Long.parseLong(head); @@ -108,7 +106,7 @@ public class ProjectModule extends BaseHandler implements ProjectService { if (user.isEmpty()) return unauthorized(ex); var head = path.pop(); return switch (head) { - case LIST -> listCompanyProjects(ex,user.get()); + case LIST -> postProjectList(ex,user.get()); case null -> postProject(ex,user.get()); default -> super.doGet(path,ex); }; @@ -132,15 +130,20 @@ public class ProjectModule extends BaseHandler implements ProjectService { 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 listCompanyProjects(long companyId, boolean includeClosed) throws UmbrellaException { return projects.ofCompany(companyId, includeClosed).values().stream().sorted(comparing(Project::name)).toList(); } - private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user) 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(); + private boolean listCompanyProjects(HttpExchange ex, UmbrellaUser user, long companyId) throws IOException, UmbrellaException { var company = companies.get(companyId); if (!companies.membership(companyId,user.id())) throw forbidden("You are mot a member of company {0}",company.name()); var projects = listCompanyProjects(companyId,false) @@ -155,9 +158,9 @@ public class ProjectModule extends BaseHandler implements ProjectService { 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>(); - for (var entry : listUserProjects(user.id(),false).entrySet()) { + for (var entry : listUserProjects(user.id(),showClosed).entrySet()) { var project = entry.getValue(); var map = project.toMap(); var members = new HashMap>(); diff --git a/translations/src/main/resources/de.json b/translations/src/main/resources/de.json index cf5c922..cc2aa74 100644 --- a/translations/src/main/resources/de.json +++ b/translations/src/main/resources/de.json @@ -22,6 +22,7 @@ "company": "Firma", "company_optional": "Firma (optional)", "confirmation": "Bestätigung", + "complete": "abschließen", "contact": "Kontakte", "contained_tax": "enthaltene Steuer", "content": "Inhalt", @@ -166,6 +167,8 @@ "service": "Service", "settings" : "Eisntellungen", "state": "Status", + "state_cancelled": "abgebrochen", + "state_complete": "abgeschlossen", "state_declined": "abgelehnt", "state_delayed": "verspätet", "state_error": "Fehler", diff --git a/web/src/main/resources/web/css/default.css b/web/src/main/resources/web/css/default.css index 0a16269..45a9a2c 100644 --- a/web/src/main/resources/web/css/default.css +++ b/web/src/main/resources/web/css/default.css @@ -1,3 +1,8 @@ +@font-face { + font-family: "awesome"; + src: url("../fontawesome-webfont.woff"); +} + a { color: orange; } @@ -88,4 +93,10 @@ td, tr{ } .task.complete > .name:before { content: "✓ "; +} + +.symbol { + font-family: awesome; + font-size: 20px; + font-weight: normal; } \ No newline at end of file diff --git a/web/src/main/resources/web/fontawesome-webfont.woff b/web/src/main/resources/web/fontawesome-webfont.woff new file mode 100644 index 0000000..839528c Binary files /dev/null and b/web/src/main/resources/web/fontawesome-webfont.woff differ
{t('name')}
{project.name} +
show(project.id)} > + {project.name} + show(project.id)} > {#if project.company_id} {companies[project.company_id].name} {/if} @@ -61,11 +94,20 @@ {t("state_"+project.status.name.toLowerCase())} + show(project.id)} > {#each Object.entries(project.members) as [uid,member]}
{member.user.name}
{/each}
+ + {#if project.status.code < 60} + + + {:else} + + {/if} +