diff --git a/buntspecht-backend/pom.xml b/buntspecht-backend/pom.xml index 2e0372f..8b3c354 100644 --- a/buntspecht-backend/pom.xml +++ b/buntspecht-backend/pom.xml @@ -14,7 +14,7 @@ ${java.version} ${java.version} 5.1.0 - 0.2.0 + 0.4.0 diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/businesslogic/TurnoverManager.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/businesslogic/TurnoverManager.java index e314783..81715cd 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/businesslogic/TurnoverManager.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/businesslogic/TurnoverManager.java @@ -55,6 +55,9 @@ public class TurnoverManager { case "created": path = qTurnover.created; break; + case "dueDate": + path = qTurnover.dueDate; + break; case "updated": path = qTurnover.updated; break; @@ -90,6 +93,14 @@ public class TurnoverManager { builder.and(qTurnover.created.before(filter.getCreated().getMax())); } } + if (filter.getDueDate() != null) { + if (filter.getDueDate().getMin() != null) { + builder.and(qTurnover.dueDate.after(filter.getDueDate().getMin())); + } + if (filter.getDueDate().getMax() != null) { + builder.and(qTurnover.dueDate.before(filter.getDueDate().getMax())); + } + } if (filter.getUpdated() != null) { if (filter.getUpdated().getMin() != null) { builder.and(qTurnover.updated.after(filter.getUpdated().getMin())); diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/TurnoverController.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/TurnoverController.java index 038e56c..8c7f69d 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/TurnoverController.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/TurnoverController.java @@ -53,16 +53,19 @@ public class TurnoverController extends BaseController { @RequestParam("descending") Optional descending, @RequestParam("from") Optional from, @RequestParam("to") Optional to, + @RequestParam("created_from") Optional fromCreated, + @RequestParam("created_to") Optional toCreated, @RequestParam("customer") Optional customer, @RequestParam("motif") Optional motif) { TurnoverFilterModel filter = new TurnoverFilterModel(); - filter.setCreated(new MinMax(from.orElse(null), to.orElse(null))); + filter.setDueDate(new MinMax(from.orElse(null), to.orElse(null))); + filter.setCreated(new MinMax(fromCreated.orElse(null), toCreated.orElse(null))); filter.setCustomer(customer.orElse(null)); filter.setMotif(motif.orElse(null)); return turnoverManager.fetch(getCurrentUsername(), limitParameter.orElse(15L), offsetParameter.orElse(0L), - sort.orElse("created"), descending.orElse(false), filter); + sort.orElse("dueDate"), descending.orElse(false), filter); } @PreAuthorize("isAuthenticated()") @@ -75,11 +78,14 @@ public class TurnoverController extends BaseController { @RequestParam("descending") Optional descending, @RequestParam("from") Optional from, @RequestParam("to") Optional to, + @RequestParam("created_from") Optional fromCreated, + @RequestParam("created_to") Optional toCreated, @RequestParam("customer") Optional customer, @RequestParam("motif") Optional motif) { TurnoverFilterModel filter = new TurnoverFilterModel(); - filter.setCreated(new MinMax(from.orElse(null), to.orElse(null))); + filter.setDueDate(new MinMax(from.orElse(null), to.orElse(null))); + filter.setCreated(new MinMax(fromCreated.orElse(null), toCreated.orElse(null))); filter.setCustomer(customer.orElse(null)); filter.setMotif(motif.orElse(null)); @@ -124,6 +130,9 @@ public class TurnoverController extends BaseController { } turnover.setCreated(Instant.now()); turnover.setUpdated(turnover.getCreated()); + if (turnover.getDueDate() == null) { + turnover.setDueDate(turnover.getCreated()); + } return turnoverManager.save(turnover); } @@ -154,18 +163,12 @@ public class TurnoverController extends BaseController { throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED); } + if (turnover.getDueDate() == null) { + turnover.setDueDate(turnover.getCreated()); + } + turnover.setUpdated(Instant.now()); return turnoverManager.save(turnover); } - - @PreAuthorize("hasRole('ROLE_ADMIN')") - @DeleteMapping("/{id}") - public void deleteById(@PathVariable("id") Long id) { - if (!turnoverManager.exists(id)) { - throw new EntityResponseStatusException(HttpStatus.NO_CONTENT); - } - - turnoverManager.deleteById(id); - } } diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/DebugController.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/DebugController.java index cb064a3..a641c42 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/DebugController.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/DebugController.java @@ -77,6 +77,8 @@ public class DebugController { turnover.setCreated(randomDate(startInclusive, endExclusive)); turnover.setUpdated(splittableRandom.nextBoolean() ? turnover.getCreated() : randomDate(turnover.getCreated(), endExclusive)); + turnover.setDueDate(splittableRandom.nextBoolean() ? turnover.getCreated() + : randomDate(startInclusive, turnover.getCreated())); turnover.setCustomer(RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(3, 10))); turnover.setMotif(RandomStringUtils.randomAlphabetic(splittableRandom.nextInt(5, 30))); diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/TurnoverManagementController.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/TurnoverManagementController.java index 371daca..144c138 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/TurnoverManagementController.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/admin/TurnoverManagementController.java @@ -49,16 +49,19 @@ public class TurnoverManagementController extends BaseController { @RequestParam("descending") Optional descending, @RequestParam("from") Optional from, @RequestParam("to") Optional to, + @RequestParam("created_from") Optional fromCreated, + @RequestParam("created_to") Optional toCreated, @RequestParam("customer") Optional customer, @RequestParam("motif") Optional motif) { TurnoverFilterModel filter = new TurnoverFilterModel(); - filter.setCreated(new MinMax(from.orElse(null), to.orElse(null))); + filter.setDueDate(new MinMax(from.orElse(null), to.orElse(null))); + filter.setCreated(new MinMax(fromCreated.orElse(null), toCreated.orElse(null))); filter.setCustomer(customer.orElse(null)); filter.setMotif(motif.orElse(null)); return turnoverManager.fetch(usernameParameter.orElse(null), limitParameter.orElse(15L), - offsetParameter.orElse(0L), sort.orElse("created"), descending.orElse(false), filter); + offsetParameter.orElse(0L), sort.orElse("dueDate"), descending.orElse(false), filter); } @PreAuthorize("hasRole('ROLE_ADMIN')") @@ -72,11 +75,14 @@ public class TurnoverManagementController extends BaseController { @RequestParam("descending") Optional descending, @RequestParam("from") Optional from, @RequestParam("to") Optional to, + @RequestParam("created_from") Optional fromCreated, + @RequestParam("created_to") Optional toCreated, @RequestParam("customer") Optional customer, @RequestParam("motif") Optional motif) { TurnoverFilterModel filter = new TurnoverFilterModel(); - filter.setCreated(new MinMax(from.orElse(null), to.orElse(null))); + filter.setDueDate(new MinMax(from.orElse(null), to.orElse(null))); + filter.setCreated(new MinMax(fromCreated.orElse(null), toCreated.orElse(null))); filter.setCustomer(customer.orElse(null)); filter.setMotif(motif.orElse(null)); return turnoverManager.overview(usernameParameter.orElse(null), limitParameter.orElse(15L), @@ -106,6 +112,15 @@ public class TurnoverManagementController extends BaseController { throw new EntityResponseStatusException(errors.getAllErrors(), HttpStatus.CONFLICT); } + Turnover existing = turnoverManager.get(turnover.getId()); + if (existing.equals(turnover)) { + throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED); + } + + if (turnover.getDueDate() == null) { + turnover.setDueDate(turnover.getCreated()); + } + turnover.setUpdated(Instant.now()); return turnoverManager.save(turnover); diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/model/TurnoverFilterModel.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/model/TurnoverFilterModel.java index c3fc14e..accabd2 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/model/TurnoverFilterModel.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/controller/model/TurnoverFilterModel.java @@ -5,6 +5,7 @@ import java.time.Instant; public class TurnoverFilterModel { private MinMax created; + private MinMax dueDate; private MinMax updated; private String customer; private String motif; @@ -19,6 +20,14 @@ public class TurnoverFilterModel { this.created = created; } + public MinMax getDueDate() { + return dueDate; + } + + public void setDueDate(MinMax dueDate) { + this.dueDate = dueDate; + } + public MinMax getUpdated() { return updated; } diff --git a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/model/Turnover.java b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/model/Turnover.java index 04aec16..4367871 100644 --- a/buntspecht-backend/src/main/java/de/champonthis/buntspecht/model/Turnover.java +++ b/buntspecht-backend/src/main/java/de/champonthis/buntspecht/model/Turnover.java @@ -25,6 +25,9 @@ public class Turnover { @Column(name = "created", nullable = false, updatable = false) private Instant created; + @Column(name = "due_date", nullable = false) + private Instant dueDate; + @Column(name = "updated", nullable = false) private Instant updated; @@ -72,6 +75,14 @@ public class Turnover { this.created = created; } + public Instant getDueDate() { + return dueDate; + } + + public void setDueDate(Instant dueDate) { + this.dueDate = dueDate; + } + public Instant getUpdated() { return updated; } @@ -136,6 +147,8 @@ public class Turnover { Turnover turnover = (Turnover) obj; boolean equals = true; + equals &= dueDate.equals(turnover.getDueDate()); + equals &= id == null && turnover.getId() == null || id.equals(turnover.getId()); equals &= username == null && turnover.getUsername() == null || username.equals(turnover.getUsername()); diff --git a/buntspecht-frontend/package-lock.json b/buntspecht-frontend/package-lock.json index 222bc3c..fded2ad 100644 --- a/buntspecht-frontend/package-lock.json +++ b/buntspecht-frontend/package-lock.json @@ -1,22 +1,22 @@ { "name": "buntspecht-web", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "buntspecht-web", - "version": "0.3.1", + "version": "0.4.0", "license": "AGPL3", "dependencies": { "@angular/animations": "^18.2.7", - "@angular/cdk": "^18.2.6", + "@angular/cdk": "^18.2.7", "@angular/common": "^18.2.7", "@angular/compiler": "^18.2.7", "@angular/core": "^18.2.7", "@angular/forms": "^18.2.7", - "@angular/material": "^18.2.6", - "@angular/material-moment-adapter": "^18.2.6", + "@angular/material": "^18.2.7", + "@angular/material-moment-adapter": "^18.2.7", "@angular/platform-browser": "^18.2.7", "@angular/platform-browser-dynamic": "^18.2.7", "@angular/router": "^18.2.7", @@ -359,9 +359,9 @@ } }, "node_modules/@angular/cdk": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.6.tgz", - "integrity": "sha512-Gfq/iv4zhlKYpdQkDaBRwxI71NHNUHM1Cs1XhnZ0/oFct5HXvSv1RHRGTKqBJLLACaAPzZKXJ/UglLoyO5CNiQ==", + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.7.tgz", + "integrity": "sha512-Dfl37WBLeEUURQrDeuMcOgX2bkQJ+BGMOlr1qsFXzUWHH+qgYW2YwO1rbna/rjxyeFzc2Sy569dYRzNPqMewzg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -534,16 +534,16 @@ } }, "node_modules/@angular/material": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.6.tgz", - "integrity": "sha512-ObxC/vomSb9QF3vIztuiInQzws+D6u09Dhfx6uNFjtyICqxEFpF7+Qx7QVDWrsuXOgxZTKgacK8f46iV8hWUfg==", + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.7.tgz", + "integrity": "sha512-mgPj2TCIrsngmu3iNnoaPc6su7uPv+NPCv9HaiKhTx4QGae8EW+RvUxEZJvh4Qaym1fJTi3hjnVeWvQDLQt4CA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^18.0.0 || ^19.0.0", - "@angular/cdk": "18.2.6", + "@angular/cdk": "18.2.7", "@angular/common": "^18.0.0 || ^19.0.0", "@angular/core": "^18.0.0 || ^19.0.0", "@angular/forms": "^18.0.0 || ^19.0.0", @@ -552,16 +552,16 @@ } }, "node_modules/@angular/material-moment-adapter": { - "version": "18.2.6", - "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-18.2.6.tgz", - "integrity": "sha512-9RwdilTesMBAxIEeY6JzMpWIOU+dS4Ned/1wdRQN33x3Qb/POhyqAv+3XfeLqoJyn7GXkl6dHFu8VEE/1WYaKg==", + "version": "18.2.7", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-18.2.7.tgz", + "integrity": "sha512-zY9ukZeZqJ/VwEvsNl1LjU4YduYA+GGA+IxBpaTcv/nS4JFPfi6n6StjOzcEOFstCx6G+4JP6qq5wcn3LvoaOg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/core": "^18.0.0 || ^19.0.0", - "@angular/material": "18.2.6", + "@angular/material": "18.2.7", "moment": "^2.18.1" } }, @@ -3473,9 +3473,9 @@ } }, "node_modules/@jsonjoy.com/util": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.3.0.tgz", - "integrity": "sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5634,9 +5634,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001666", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", - "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", "dev": true, "funding": [ { @@ -6696,9 +6696,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.31", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", - "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", + "version": "1.5.32", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz", + "integrity": "sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==", "dev": true, "license": "ISC" }, @@ -11612,9 +11612,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.0.tgz", - "integrity": "sha512-vTbzVAjQDzwQdKuvj7qEq6OlAprCjE656khuGQ4QaBLg7abQ9I9ISpmLuc6inWe7zP75AECjqUa4g4sdQvOXhg==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", + "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/buntspecht-frontend/package.json b/buntspecht-frontend/package.json index 4459c97..705ea10 100644 --- a/buntspecht-frontend/package.json +++ b/buntspecht-frontend/package.json @@ -1,6 +1,6 @@ { "name": "buntspecht-web", - "version": "0.3.1", + "version": "0.4.0", "license": "AGPL3", "scripts": { "ng": "ng", @@ -13,13 +13,13 @@ "private": true, "dependencies": { "@angular/animations": "^18.2.7", - "@angular/cdk": "^18.2.6", + "@angular/cdk": "^18.2.7", "@angular/common": "^18.2.7", "@angular/compiler": "^18.2.7", "@angular/core": "^18.2.7", "@angular/forms": "^18.2.7", - "@angular/material": "^18.2.6", - "@angular/material-moment-adapter": "^18.2.6", + "@angular/material": "^18.2.7", + "@angular/material-moment-adapter": "^18.2.7", "@angular/platform-browser": "^18.2.7", "@angular/platform-browser-dynamic": "^18.2.7", "@angular/router": "^18.2.7", diff --git a/buntspecht-frontend/src/app/pages/login/login.page.html b/buntspecht-frontend/src/app/pages/login/login.page.html index 6e04b91..b43c5e0 100644 --- a/buntspecht-frontend/src/app/pages/login/login.page.html +++ b/buntspecht-frontend/src/app/pages/login/login.page.html @@ -29,9 +29,7 @@ + [disabled]="loginForm.invalid">open_in_new{{'login' | i18n}} diff --git a/buntspecht-frontend/src/app/pages/management/management.page.html b/buntspecht-frontend/src/app/pages/management/management.page.html index d31d008..246f6a7 100644 --- a/buntspecht-frontend/src/app/pages/management/management.page.html +++ b/buntspecht-frontend/src/app/pages/management/management.page.html @@ -24,12 +24,12 @@ @if (filterOpen) {
- {{'management.filter.created' | i18n}} + {{'management.filter.dueDate' | i18n}} - - @@ -50,6 +50,10 @@
} + + + file_download + @if (entries && entries.total == 0) { @@ -123,7 +127,7 @@ @if (expanded && entries.total && entries.filter && entries.filter.username == entry[0]) { - } diff --git a/buntspecht-frontend/src/app/pages/management/management.page.ts b/buntspecht-frontend/src/app/pages/management/management.page.ts index 6faf940..73cd889 100644 --- a/buntspecht-frontend/src/app/pages/management/management.page.ts +++ b/buntspecht-frontend/src/app/pages/management/management.page.ts @@ -1,11 +1,13 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { FormControl } from '@angular/forms'; import { PageEvent } from '@angular/material/paginator'; import { Sort } from '@angular/material/sort'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { debounceTime, Observable, switchMap } from 'rxjs'; +import { I18nService } from 'src/app/services/i18n.service'; import { TurnoverManagementService } from 'src/app/services/turnover.management.service'; import { UserManagementService } from 'src/app/services/user.management.service'; +import { UiTurnovers } from 'src/app/ui/turnovers/turnovers.ui'; @Component({ selector: 'ui-management', @@ -26,11 +28,14 @@ export class PageManagement implements OnInit { users: Observable; usersFormControl = new FormControl(); + @ViewChild('uiTurnovers') uiTurnovers: UiTurnovers; + turnovers: any; constructor( private turnoverManagementService: TurnoverManagementService, private userManagementService: UserManagementService, + private i18n: I18nService, private router: Router, private route: ActivatedRoute ) { } @@ -173,7 +178,7 @@ export class PageManagement implements OnInit { if (this.entries.total) { this.expanded = true; const filter = JSON.parse(JSON.stringify(this.entries.filter || {})); - this.turnoverManagementService.fetch(this.turnovers.limit || 100, this.turnovers.offset || 0, 'created', true, filter).subscribe({ + this.turnoverManagementService.fetch(this.turnovers.limit || 100, this.turnovers.offset || 0, 'dueDate', true, filter).subscribe({ next: (data: any) => { this.turnovers = data; this.turnovers.filter = filter; @@ -190,4 +195,32 @@ export class PageManagement implements OnInit { this.applyUser(this.usersFormControl.value); } + + export() { + if (this.entries.total) { + let rows = [[this.i18n.get('user.username'), this.i18n.get('turnover.price'), this.i18n.get('turnover.price.suffix')]]; + + this.entries.results.forEach(result => { + rows[rows.length] = [result[0], result[1], [result[2]]] + }); + + if (this.uiTurnovers) { + rows.push(...this.uiTurnovers.getCsvRows()); + } + + if (rows.length) { + let csvContent = "data:text/csv;charset=utf-8," + + rows.map(e => e.join(";")).join("\n"); + + var encodedUri = encodeURI(csvContent); + var link = document.createElement("a"); + link.setAttribute("href", encodedUri); + link.setAttribute("download", "export.csv"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + } + } diff --git a/buntspecht-frontend/src/app/pages/turnover/turnover.page.html b/buntspecht-frontend/src/app/pages/turnover/turnover.page.html index 27bf70d..ab52e1b 100644 --- a/buntspecht-frontend/src/app/pages/turnover/turnover.page.html +++ b/buntspecht-frontend/src/app/pages/turnover/turnover.page.html @@ -13,6 +13,14 @@ i18n:(turnover.created | datef:'LLL' ):turnover.username}} } + + {{'turnover.dueDate' | i18n}} + + + + + {{'turnover.customer' | i18n}} @@ -64,23 +72,27 @@ - - @if (!working) { - - } + +
+ @if (!working) { + + } + @if (success) { + {{'turnover.success' | i18n}} + } + @if (admin && turnover.id) { + + + delete {{'turnover.delete' | i18n}} + + } +
@if (turnover.updated && turnover.updated != turnover.created) { - {{'turnover.updated.label' | i18n:(turnover.updated | datef:'LLL' )}} - } - @if (success) { - {{'turnover.success' | i18n}} - } - @if (admin && turnover.id) { - - - delete {{'turnover.delete' | i18n}} - +
+ {{'turnover.updated.label' | i18n:(turnover.updated | datef:'LLL' )}} +
}
diff --git a/buntspecht-frontend/src/app/pages/turnover/turnover.page.scss b/buntspecht-frontend/src/app/pages/turnover/turnover.page.scss index 0a1cb70..283f599 100644 --- a/buntspecht-frontend/src/app/pages/turnover/turnover.page.scss +++ b/buntspecht-frontend/src/app/pages/turnover/turnover.page.scss @@ -18,4 +18,8 @@ form { @media screen and (min-width: 992px) { max-width: 50%; } +} + +mat-card-actions .flex:first-child { + margin-bottom: 15px; } \ No newline at end of file diff --git a/buntspecht-frontend/src/app/pages/turnover/turnover.page.ts b/buntspecht-frontend/src/app/pages/turnover/turnover.page.ts index 18f78f1..5f20ce5 100644 --- a/buntspecht-frontend/src/app/pages/turnover/turnover.page.ts +++ b/buntspecht-frontend/src/app/pages/turnover/turnover.page.ts @@ -1,8 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MatDatepickerInputEvent } from '@angular/material/datepicker'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ActivatedRoute, Router } from '@angular/router'; +import moment, { Moment } from 'moment'; import { AuthService } from 'src/app/services/auth.service'; import { TurnoverManagementService } from 'src/app/services/turnover.management.service'; import { TurnoverService } from 'src/app/services/turnover.service'; @@ -23,6 +25,7 @@ export class PageTurnover implements OnInit { form: FormGroup; username: string = ""; admin: boolean = false; + today: Moment = moment(); constructor( private turnoverService: TurnoverService, @@ -36,6 +39,7 @@ export class PageTurnover implements OnInit { ngOnInit(): void { this.form = this.formBuilder.group({ + dueDate: ['', Validators.nullValidator], customer: ['', Validators.required], motif: ['', Validators.required], price: ['', Validators.required], @@ -64,6 +68,9 @@ export class PageTurnover implements OnInit { request.subscribe({ next: (data) => { this.turnover = data; + if (this.turnover.dueDate != this.turnover.created) { + this.form.get("dueDate").setValue(this.turnover.dueDate); + } this.form.get("customer").setValue(this.turnover.customer); this.form.get("motif").setValue(this.turnover.motif); this.form.get("price").setValue(this.turnover.price); @@ -102,6 +109,7 @@ export class PageTurnover implements OnInit { this.working = true; + this.turnover.dueDate = this.form.get("dueDate").value; this.turnover.customer = this.form.get("customer").value; this.turnover.motif = this.form.get("motif").value; this.turnover.price = this.form.get("price").value; @@ -137,6 +145,7 @@ export class PageTurnover implements OnInit { } this.working = true; + this.turnover.dueDate = this.form.get("dueDate").value; this.turnover.customer = this.form.get("customer").value; this.turnover.motif = this.form.get("motif").value; this.turnover.price = this.form.get("price").value; diff --git a/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.html b/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.html index 240dc16..ab814c1 100644 --- a/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.html +++ b/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.html @@ -1,5 +1,5 @@
- \ No newline at end of file diff --git a/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.ts b/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.ts index 091a1a3..c37ade6 100644 --- a/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.ts +++ b/buntspecht-frontend/src/app/pages/turnovers/manage/manage.page.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { FormControl } from '@angular/forms'; import { PageEvent } from '@angular/material/paginator'; import { Sort } from '@angular/material/sort'; @@ -6,6 +6,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { debounceTime, Observable, switchMap } from 'rxjs'; import { TurnoverManagementService } from 'src/app/services/turnover.management.service'; import { UserManagementService } from 'src/app/services/user.management.service'; +import { UiTurnovers } from 'src/app/ui/turnovers/turnovers.ui'; @Component({ selector: 'page-turnovers-manage', @@ -15,13 +16,15 @@ import { UserManagementService } from 'src/app/services/user.management.service' export class PageTurnoversManage implements OnInit { turnovers: any; - sort: string = "created"; + sort: string = "dueDate"; descending: boolean = true; filterOpen: boolean = false; users: Observable; usersFormControl = new FormControl(); + @ViewChild('uiTurnovers') uiTurnovers: UiTurnovers; + constructor( private turnoverManagementService: TurnoverManagementService, private userManagementService: UserManagementService, @@ -104,7 +107,7 @@ export class PageTurnoversManage implements OnInit { params['o'] = this.turnovers.offset; } - if (this.sort != 'created') { + if (this.sort != 'dueDate') { params['s'] = this.sort; } @@ -134,7 +137,7 @@ export class PageTurnoversManage implements OnInit { } applySort(event: Sort) { - this.sort = event.direction ? event.active : 'created'; + this.sort = event.direction ? event.active : 'dueDate'; this.descending = event.direction !== 'asc'; this.update(); } @@ -150,4 +153,21 @@ export class PageTurnoversManage implements OnInit { this.update(); } } + + export() { + let rows = this.uiTurnovers.getCsvRows(); + if (rows.length) { + let csvContent = "data:text/csv;charset=utf-8," + + rows.map(e => e.join(";")).join("\n"); + + var encodedUri = encodeURI(csvContent); + var link = document.createElement("a"); + link.setAttribute("href", encodedUri); + link.setAttribute("download", "export.csv"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + } diff --git a/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.html b/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.html index 4575cde..768e29a 100644 --- a/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.html +++ b/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.html @@ -8,12 +8,12 @@ @if (filterOpen) {
- {{'turnovers.filter.created' | i18n}} + {{'turnovers.filter.dueDate' | i18n}} - - @@ -34,8 +34,12 @@
} + + + file_download +
- \ No newline at end of file diff --git a/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.ts b/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.ts index afa318d..155f4ee 100644 --- a/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.ts +++ b/buntspecht-frontend/src/app/pages/turnovers/turnovers.page.ts @@ -1,11 +1,9 @@ -import { Component, OnInit } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { PageEvent } from '@angular/material/paginator'; import { Sort } from '@angular/material/sort'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { debounceTime, Observable, switchMap } from 'rxjs'; import { TurnoverService } from 'src/app/services/turnover.service'; -import { UserManagementService } from 'src/app/services/user.management.service'; +import { UiTurnovers } from 'src/app/ui/turnovers/turnovers.ui'; @Component({ selector: 'page-turnovers', @@ -16,11 +14,13 @@ export class PageTurnovers implements OnInit { turnovers: any; overview: any[]; - sort: string = "created"; + sort: string = "dueDate"; descending: boolean = true; filterOpen: boolean = false; init: boolean = true; + @ViewChild('uiTurnovers') uiTurnovers: UiTurnovers; + constructor( private turnoverService: TurnoverService, private router: Router, @@ -102,7 +102,7 @@ export class PageTurnovers implements OnInit { params['o'] = this.turnovers.offset; } - if (this.sort != 'created') { + if (this.sort != 'dueDate') { params['s'] = this.sort; } @@ -132,7 +132,7 @@ export class PageTurnovers implements OnInit { } applySort(event: Sort) { - this.sort = event.direction ? event.active : 'created'; + this.sort = event.direction ? event.active : 'dueDate'; this.descending = event.direction !== 'asc'; this.update(); } @@ -148,4 +148,20 @@ export class PageTurnovers implements OnInit { this.update(); } } + + export() { + let rows = this.uiTurnovers.getCsvRows(); + if (rows.length) { + let csvContent = "data:text/csv;charset=utf-8," + + rows.map(e => e.join(";")).join("\n"); + + var encodedUri = encodeURI(csvContent); + var link = document.createElement("a"); + link.setAttribute("href", encodedUri); + link.setAttribute("download", "export.csv"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } } diff --git a/buntspecht-frontend/src/app/pages/users/create/users.create.page.html b/buntspecht-frontend/src/app/pages/users/create/users.create.page.html index c9965e8..a64d9d7 100644 --- a/buntspecht-frontend/src/app/pages/users/create/users.create.page.html +++ b/buntspecht-frontend/src/app/pages/users/create/users.create.page.html @@ -40,8 +40,9 @@ @if (!working) { - + } diff --git a/buntspecht-frontend/src/app/pages/users/users.page.html b/buntspecht-frontend/src/app/pages/users/users.page.html index e08a976..7eaefd5 100644 --- a/buntspecht-frontend/src/app/pages/users/users.page.html +++ b/buntspecht-frontend/src/app/pages/users/users.page.html @@ -82,9 +82,11 @@ -
- {{'user.create' | i18n}}person_add +
+ + person_add + {{'user.create' | i18n}} + diff --git a/buntspecht-frontend/src/app/pages/users/users.page.scss b/buntspecht-frontend/src/app/pages/users/users.page.scss index 19b34d5..9979c08 100644 --- a/buntspecht-frontend/src/app/pages/users/users.page.scss +++ b/buntspecht-frontend/src/app/pages/users/users.page.scss @@ -1,3 +1,21 @@ +.filter-container { + padding-left: 15px; + justify-content: flex-start; + align-items: center; + + .filter { + + justify-content: flex-start; + align-items: center; + + &>* { + margin-top: 5px; + margin-bottom: 5px; + margin-left: 15px; + } + } +} + tr.user { &:hover { cursor: pointer; @@ -7,4 +25,8 @@ tr.user { &.disabled { pointer-events: none; } +} + +.mat-mdc-paginator a.margin { + margin: 15px; } \ No newline at end of file diff --git a/buntspecht-frontend/src/app/services/i18n.service.ts b/buntspecht-frontend/src/app/services/i18n.service.ts index 4f2aa23..4271a19 100644 --- a/buntspecht-frontend/src/app/services/i18n.service.ts +++ b/buntspecht-frontend/src/app/services/i18n.service.ts @@ -53,7 +53,7 @@ export class I18nService { } - get(key, args: string[]): string { + get(key, args: string[] = []): string { return this.getInternal(key, args, this.i18n, "", true); } diff --git a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.html b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.html index 03d5b5e..49e5615 100644 --- a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.html +++ b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.html @@ -34,13 +34,13 @@ {{turnover.username}} - - - {{'turnover.created' | + + {{'turnover.dueDate' | i18n}} - {{turnover.created | datef}} + + {{turnover.dueDate | datef:'L'}} + @@ -100,17 +100,25 @@ + + {{'turnover.created' | + i18n}} + + + {{turnover.created | datef}} + + + {{'turnover.updated' | i18n}} - +
@if(turnover.created != turnover.updated) { - {{turnover.updated | - datef}} + {{turnover.updated | datef}} }
@@ -127,9 +135,9 @@ -
+
@if (overview && overview.length > 2) { -
+
{{'turnover.price.total' | i18n:(overview[1] | number: '1.2-2')}} {{'turnover.timeInvestment.total' | i18n:(overview[2] | number: '1.1-1')}}
diff --git a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.scss b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.scss index 2293133..1d2adae 100644 --- a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.scss +++ b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.scss @@ -13,4 +13,8 @@ tr.turnover { @media screen and (min-width: 992px) { min-width: 160px; } +} + +.overview { + margin: 5px; } \ No newline at end of file diff --git a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.ts b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.ts index 1029b87..48321b4 100644 --- a/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.ts +++ b/buntspecht-frontend/src/app/ui/turnovers/turnovers.ui.ts @@ -2,6 +2,8 @@ import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@a import { PageEvent } from '@angular/material/paginator'; import { Sort } from '@angular/material/sort'; import { Router } from '@angular/router'; +import moment from 'moment'; +import { I18nService } from 'src/app/services/i18n.service'; @Component({ selector: 'ui-turnovers', @@ -23,7 +25,10 @@ export class UiTurnovers implements OnInit { columns: string[] = []; - constructor(private router: Router) { } + constructor( + private router: Router, + private i18n: I18nService + ) { } ngOnInit(): void { this.applyResize(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth); @@ -36,19 +41,56 @@ export class UiTurnovers implements OnInit { applyResize(width: number) { if (width < 992) { - this.columns = ['customer', 'price']; + this.columns = ['customer', 'price', 'motif']; } else { - this.columns = ['customer', 'motif', 'price', 'timeInvestment', 'remark', 'materialConsumption', 'updated']; + this.columns = ['customer', 'motif', 'price', 'timeInvestment', 'remark', 'materialConsumption', 'created', 'updated']; } if (this.username) { this.columns.unshift('username'); - } else { - this.columns.push('motif'); } - this.columns.unshift('created'); + this.columns.unshift('dueDate'); } select(row: any) { this.router.navigateByUrl('/t/' + row.id); } + + getCsvRows(): any[] { + if (this.turnovers.total) { + let rows = [[ + this.i18n.get('turnover.dueDate'), + this.i18n.get('turnover.customer'), + this.i18n.get('turnover.motif'), + this.i18n.get('turnover.price'), + this.i18n.get('turnover.timeInvestment'), + this.i18n.get('turnover.remark'), + this.i18n.get('turnover.materialConsumption'), + this.i18n.get('turnover.created'), + this.i18n.get('turnover.updated')]]; + + if (this.username) { + rows[0].unshift( + this.i18n.get('turnover.username')); + } + + this.turnovers.results.forEach(turnover => { + rows[rows.length] = [ + moment(turnover.dueDate).format('L'), + turnover.customer, + turnover.motif, + turnover.price.toFixed(2), + turnover.timeInvestment.toFixed(1), + turnover.remark, + turnover.materialConsumption, + moment(turnover.created).format(this.i18n.get('turnovers.export.dateformat')), + moment(turnover.updated).format(this.i18n.get('turnovers.export.dateformat')) + ] + if (this.username) { + rows[rows.length - 1].unshift(turnover.username); + } + }); + return rows; + } + return []; + } } diff --git a/buntspecht-frontend/src/assets/i18n/de-informal.json b/buntspecht-frontend/src/assets/i18n/de-informal.json index b111d8e..dc25941 100644 --- a/buntspecht-frontend/src/assets/i18n/de-informal.json +++ b/buntspecht-frontend/src/assets/i18n/de-informal.json @@ -34,6 +34,11 @@ ".": "Verwaltung", "filter": { "created": { + ".": "Erstellt", + "from": "von", + "to": "bis" + }, + "dueDate": { ".": "Zeitraum", "from": "von", "to": "bis" @@ -104,6 +109,12 @@ ".": "Kunde", "error": "Angabe von Kunde erforderlich" }, + "dueDate": { + ".": "Fälligkeitsdatum", + "label": { + ".": "Fällig am {0}" + } + }, "delete": "Löschen", "edit": "Buchung bearbeiten", "info": "Neue Buchung erstellen", @@ -134,14 +145,24 @@ }, "turnovers": { ".": "Buchungen", + "export": { + ".": "Als CSV exportieren", + "dateformat": "DD.MM.YYYY-HH:mm" + }, "filter": { + ".": "Filter", "created": { + ".": "Erstellt", + "from": "von", + "to": "bis" + }, + "dueDate": { ".": "Zeitraum", "from": "von", "to": "bis" }, "customer": "Kunde durchsuchen", - "motif": "Motif durchsuchen", + "motif": "Motiv durchsuchen", "username": "User auswählen" }, "mine": "Eigene Buchungen" diff --git a/buntspecht-frontend/src/assets/icons/favicon.png b/buntspecht-frontend/src/assets/icons/favicon.png index 969e18b..bc41067 100644 Binary files a/buntspecht-frontend/src/assets/icons/favicon.png and b/buntspecht-frontend/src/assets/icons/favicon.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-128x128.png b/buntspecht-frontend/src/assets/icons/icon-128x128.png index a4dd4ef..917864c 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-128x128.png and b/buntspecht-frontend/src/assets/icons/icon-128x128.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-144x144.png b/buntspecht-frontend/src/assets/icons/icon-144x144.png index 3cfd368..dfe4fed 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-144x144.png and b/buntspecht-frontend/src/assets/icons/icon-144x144.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-152x152.png b/buntspecht-frontend/src/assets/icons/icon-152x152.png index 6849709..06e16c2 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-152x152.png and b/buntspecht-frontend/src/assets/icons/icon-152x152.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-192x192.png b/buntspecht-frontend/src/assets/icons/icon-192x192.png index 283cc7b..972dc6f 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-192x192.png and b/buntspecht-frontend/src/assets/icons/icon-192x192.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-384x384.png b/buntspecht-frontend/src/assets/icons/icon-384x384.png index 6797e8e..438cb8f 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-384x384.png and b/buntspecht-frontend/src/assets/icons/icon-384x384.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-512x512.png b/buntspecht-frontend/src/assets/icons/icon-512x512.png index f8a8db2..6e84a71 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-512x512.png and b/buntspecht-frontend/src/assets/icons/icon-512x512.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-72x72.png b/buntspecht-frontend/src/assets/icons/icon-72x72.png index 86598a1..9b2c4cb 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-72x72.png and b/buntspecht-frontend/src/assets/icons/icon-72x72.png differ diff --git a/buntspecht-frontend/src/assets/icons/icon-96x96.png b/buntspecht-frontend/src/assets/icons/icon-96x96.png index 013ee58..51ee659 100644 Binary files a/buntspecht-frontend/src/assets/icons/icon-96x96.png and b/buntspecht-frontend/src/assets/icons/icon-96x96.png differ diff --git a/buntspecht-frontend/src/assets/icons/logo.svg b/buntspecht-frontend/src/assets/icons/logo.svg deleted file mode 100644 index eefc7d4..0000000 --- a/buntspecht-frontend/src/assets/icons/logo.svg +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - diff --git a/buntspecht-frontend/src/styles.scss b/buntspecht-frontend/src/styles.scss index abc76ab..fbe1c77 100644 --- a/buntspecht-frontend/src/styles.scss +++ b/buntspecht-frontend/src/styles.scss @@ -360,6 +360,10 @@ a[href*="//"]::after { app-root { background-color: #303030; } + + a { + color: $accent; + } table.default-table { diff --git a/buntspecht-frontend/src/variables.scss b/buntspecht-frontend/src/variables.scss index 82bcf3d..826d837 100644 --- a/buntspecht-frontend/src/variables.scss +++ b/buntspecht-frontend/src/variables.scss @@ -18,8 +18,8 @@ $light-theme: mat.m2-define-light-theme((color: (primary: $light-primary, // Define an alternate dark theme. $dark-theme: mat.m2-define-dark-theme((color: (primary: $dark-primary, - accent: $light-accent, - warn: $light-warn, + accent: $dark-accent, + warn: $dark-warn, ))); $primary: mat.get-theme-color($light-theme, primary, default);