From f48bfe2107d5730fe6bf059fa9271276229329c9 Mon Sep 17 00:00:00 2001 From: verboomp Date: Fri, 23 Jan 2026 15:09:34 +0100 Subject: [PATCH] First designs for ui --- CLAUDE.md | 99 +++++++ .../fotodocumentation/core/model/Picture.java | 20 +- .../core/service/CustomerPictureService.java | 51 +++- .../core/service/PictureService.java | 31 ++ .../rest/CustomerResource.java | 27 +- .../rest/PictureResource.java | 52 ++++ .../rest/jackson/ApplicationConfigApi.java | 6 +- .../rest/vo/CustomerListValue.java | 10 +- .../rest/vo/CustomerPictureValue.java | 6 +- .../rest/vo/CustomerValue.java | 27 ++ .../rest/vo/PictureValue.java | 27 ++ .../core/db/migration/V2__init2.sql | 4 + ...-foto-documentation-web-1.0.0-SNAPSHOT.war | Bin 26668182 -> 26671951 bytes .../assets/images/logo.png | Bin 0 -> 2197 bytes .../lib/controller/customer_controller.dart | 31 ++ .../lib/controller/picture_controller.dart | 23 ++ .../lib/dto/customer_dto.dart | 62 ++++ .../lib/l10n/app_de.arb | 73 ++++- .../lib/l10n/app_localizations.dart | 100 ++++++- .../lib/l10n/app_localizations_de.dart | 55 +++- .../lib/main.dart | 3 + .../pages/customer/customer_list_widget.dart | 275 ++++++++++++++++++ .../lib/pages/customer/customer_row_item.dart | 40 +++ .../lib/pages/customer/customer_widget.dart | 251 +++++++++++++++- .../lib/pages/customer/picture_widget.dart | 234 +++++++++++++++ .../lib/pages/login/login_widget.dart | 172 +++++++---- .../component/page_header_widget.dart | 87 +++--- .../component/search_bar_card_widget.dart | 81 ------ .../ui_utils/component/search_bar_widget.dart | 59 ++++ .../ui_utils/component/text_input_widget.dart | 60 ---- .../pages/ui_utils/dialog/dialog_result.dart | 11 - .../lib/pages/ui_utils/general_style.dart | 55 ++-- .../lib/utils/di_container.dart | 4 + .../lib/utils/extensions.dart | 20 +- .../lib/utils/global_router.dart | 22 +- .../lib/utils/global_stack.dart | 16 - .../lib/utils/password_utils.dart | 12 - .../pubspec.yaml | 1 + .../test/testing/test_utils.dart | 6 +- .../test/testing/test_utils.mocks.dart | 25 -- .../src/main/webapp/WEB-INF/web.xml | 1 + 41 files changed, 1727 insertions(+), 412 deletions(-) create mode 100644 CLAUDE.md create mode 100644 hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/PictureService.java create mode 100644 hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResource.java create mode 100644 hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerValue.java create mode 100644 hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/PictureValue.java create mode 100644 hartmann-foto-documentation-app/src/main/resources/marketing/heyday/hartmann/fotodocumentation/core/db/migration/V2__init2.sql create mode 100644 hartmann-foto-documentation-frontend/assets/images/logo.png create mode 100644 hartmann-foto-documentation-frontend/lib/controller/customer_controller.dart create mode 100644 hartmann-foto-documentation-frontend/lib/controller/picture_controller.dart create mode 100644 hartmann-foto-documentation-frontend/lib/dto/customer_dto.dart create mode 100644 hartmann-foto-documentation-frontend/lib/pages/customer/customer_list_widget.dart create mode 100644 hartmann-foto-documentation-frontend/lib/pages/customer/customer_row_item.dart create mode 100644 hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart delete mode 100644 hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_card_widget.dart create mode 100644 hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_widget.dart delete mode 100644 hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/text_input_widget.dart delete mode 100644 hartmann-foto-documentation-frontend/lib/pages/ui_utils/dialog/dialog_result.dart delete mode 100644 hartmann-foto-documentation-frontend/lib/utils/global_stack.dart delete mode 100644 hartmann-foto-documentation-frontend/lib/utils/password_utils.dart diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bd8107b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,99 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +### Backend (Maven) +```bash +# Full build and deploy +mvn deploy -U -f pom.xml + +# Run integration tests with Docker +mvn deploy -P docker -f hartmann-foto-documentation-docker/pom.xml +``` + +### Frontend (Flutter) +```bash +cd hartmann-foto-documentation-frontend + +# Install dependencies +flutter pub get + +# Build for web +flutter build web --no-tree-shake-icons + +# Run tests with coverage +flutter test --coverage --reporter=json + +# Run a single test file +flutter test test/path/to/test_file.dart + +# Static analysis +flutter analyze + +# Generate localization files +flutter gen-l10n +``` + +## Architecture + +### Multi-Module Maven Project +- **hartmann-foto-documentation-app** - Backend REST API (jar) +- **hartmann-foto-documentation-web** - WAR deployment package +- **hartmann-foto-documentation-docker** - Docker/integration tests +- **hartmann-foto-documentation-frontend** - Flutter mobile/web app + +### Technology Stack +- **Backend:** Java 21, WildFly 26.1.3, Jakarta EE 10, Hibernate 6.2, PostgreSQL 11, JWT auth +- **Frontend:** Flutter 3.3.0+, Provider for state management, go_router for navigation +- **API Docs:** Swagger/OpenAPI v3 + +### Backend Layered Architecture +``` +REST Resources → Services (@Stateless EJBs) → QueryService → JPA Entities +``` + +Key packages in `hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/`: +- `core/model/` - JPA entities (User, Right, Customer, Picture, JwtRefreshToken) +- `core/service/` - Business logic with AbstractService base class +- `core/query/` - QueryService for database operations +- `rest/` - JAX-RS resources and value objects + +### Frontend Structure +``` +lib/ +├── main.dart # Entry point (MaterialApp.router) +├── controller/ # Business logic controllers +├── pages/ # Feature-based UI (login/, customer/) +├── dto/ # Data Transfer Objects +├── utils/ # DI container, theme, routing +└── l10n/ # Localization (German supported) +``` + +### Authentication +- JWT token-based authentication with refresh tokens +- WildFly Elytron security realm +- Bearer token auth for REST endpoints +- User passwords stored with salt-based hashing + +### Database Entities +- User ↔ Right (many-to-many via user_to_right) +- Customer → Picture (one-to-many) +- JwtRefreshToken (device/IP tracking) + +Named queries pattern: `@NamedQuery` on entities (e.g., `User.BY_USERNAME`) + +## Local Development + +Docker Compose provides PostgreSQL and WildFly: +```bash +cd hartmann-foto-documentation-docker +docker-compose up +``` + +Port mappings: WildFly HTTP (8180), Management (9990), SMTP (8280) + +## CI/CD + +Jenkins pipeline with SonarQube integration. Build runs on macOS agent with JDK 21. diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/model/Picture.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/model/Picture.java index 6750a73..29e0b20 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/model/Picture.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/model/Picture.java @@ -39,6 +39,9 @@ public class Picture extends AbstractDateEntity { @Basic(fetch = FetchType.LAZY) private String comment; + + + private String category; @Column(name = "image") @Basic(fetch = FetchType.LAZY) @@ -79,6 +82,16 @@ public class Picture extends AbstractDateEntity { public void setComment(String comment) { this.comment = comment; } + + + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } public String getImage() { return image; @@ -121,12 +134,17 @@ public class Picture extends AbstractDateEntity { instance.setPictureDate(pictureDate); return this; } - + public Builder comment(String comment) { instance.setComment(comment); return this; } + public Builder category(String category) { + instance.setCategory(category); + return this; + } + public Builder image(String image) { instance.setImage(image); return this; diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java index e1140bc..40b247f 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/CustomerPictureService.java @@ -3,14 +3,21 @@ package marketing.heyday.hartmann.fotodocumentation.core.service; import java.util.List; import java.util.Optional; +import org.apache.commons.lang3.StringUtils; + import jakarta.annotation.security.PermitAll; import jakarta.ejb.LocalBean; import jakarta.ejb.Stateless; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import marketing.heyday.hartmann.fotodocumentation.core.model.Customer; import marketing.heyday.hartmann.fotodocumentation.core.model.Picture; import marketing.heyday.hartmann.fotodocumentation.core.query.Param; import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerListValue; import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue; +import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerValue; /** * @@ -30,8 +37,12 @@ public class CustomerPictureService extends AbstractService { Optional customerOpt = queryService.callNamedQuerySingleResult(Customer.FIND_BY_NUMBER, new Param(Customer.PARAM_NUMBER, customerPictureValue.customerNumber())); Customer customer = customerOpt.orElseGet(() -> new Customer.Builder().customerNumber(customerPictureValue.customerNumber()).name(customerPictureValue.pharmacyName()).build()); customer = entityManager.merge(customer); - - Picture picture = new Picture.Builder().customer(customer).username(customerPictureValue.username()).comment(customerPictureValue.comment()).image(customerPictureValue.base64String()).pictureDate(customerPictureValue.date()).build(); + + Picture picture = new Picture.Builder().customer(customer).username(customerPictureValue.username()) + .category(customerPictureValue.category()) + .comment(customerPictureValue.comment()) + .image(customerPictureValue.base64String()) + .pictureDate(customerPictureValue.date()).build(); customer.getPictures().add(picture); entityManager.persist(picture); @@ -39,10 +50,42 @@ public class CustomerPictureService extends AbstractService { return true; } - public List getAll(String query) { + // query = search for name, number and date + public List getAll(String queryStr, String startsWith) { // FIXME: do query - List customers = queryService.callNamedQueryList(Customer.FIND_ALL); + + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = builder.createQuery(Customer.class); + Root customerRoot = criteriaQuery.from(Customer.class); + + criteriaQuery = criteriaQuery.select(customerRoot); + + if (StringUtils.isNotBlank(startsWith)) { + String param = startsWith.toLowerCase() + "%"; + criteriaQuery = criteriaQuery.where(builder.like(builder.lower(customerRoot.get("name")), param)); + } + + if (StringUtils.isNotBlank(queryStr)) { + String param = "%" + StringUtils.trimToEmpty(queryStr).toLowerCase() + "%"; + var predicateName = builder.like(builder.lower(customerRoot.get("name")), param); + var predicateNr = builder.like(builder.lower(customerRoot.get("customerNumber")), param); + + var predicate = builder.or(predicateName, predicateNr); + criteriaQuery = criteriaQuery.where(predicate); + } + + TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); + List customers = typedQuery.getResultList(); customers.forEach(c -> c.getPictures().size()); return customers.parallelStream().map(CustomerListValue::builder).toList(); } + + public CustomerValue get(Long id) { + Customer customer = entityManager.find(Customer.class, id); + if (customer == null) { + return null; + } + + return CustomerValue.builder(customer); + } } diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/PictureService.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/PictureService.java new file mode 100644 index 0000000..62213cd --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/service/PictureService.java @@ -0,0 +1,31 @@ +package marketing.heyday.hartmann.fotodocumentation.core.service; + +import jakarta.annotation.security.PermitAll; +import jakarta.ejb.LocalBean; +import jakarta.ejb.Stateless; +import marketing.heyday.hartmann.fotodocumentation.core.model.Picture; +import marketing.heyday.hartmann.fotodocumentation.rest.vo.PictureValue; + +/** + * + *

Copyright: Copyright (c) 2024

+ *

Company: heyday Marketing GmbH

+ * @author Patrick Verboom + * @version 1.0 + * + * created: 19 Jan 2026 + */ +@Stateless +@LocalBean +@PermitAll +public class PictureService extends AbstractService { + + public PictureValue get(Long id) { + Picture picture = entityManager.find(Picture.class, id); + if (picture == null) { + return null; + } + + return PictureValue.builder(picture); + } +} diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerResource.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerResource.java index e41fc9f..d88e8f5 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerResource.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerResource.java @@ -13,14 +13,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.ejb.EJB; import jakarta.enterprise.context.RequestScoped; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService; import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerListValue; +import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerValue; /** * @@ -42,12 +39,24 @@ public class CustomerResource { @GZIP @GET @Path("") - @Consumes(MediaType.APPLICATION_JSON) + @Produces(JSON_OUT) @Operation(summary = "Get customer list") @ApiResponse(responseCode = "200", description = "Successfully retrieved customer list", content = @Content(mediaType = JSON_OUT, array = @ArraySchema(schema = @Schema(implementation = CustomerListValue.class)))) - public Response doAddCustomerPicture(@QueryParam("query") String query) { - LOG.debug("Query customers for query " + query); - var retVal = customerPictureService.getAll(query); + public Response doGetCustomerList(@QueryParam("query") String query, @QueryParam("startsWith") String startsWith) { + LOG.debug("Query customers for query " + query + " startsWith: " + startsWith); + var retVal = customerPictureService.getAll(query, startsWith); + return Response.ok().entity(retVal).build(); + } + + @GZIP + @GET + @Path("{id}") + @Produces(JSON_OUT) + @Operation(summary = "Get customer value") + @ApiResponse(responseCode = "200", description = "Successfully retrieved customer value", content = @Content(mediaType = JSON_OUT, array = @ArraySchema(schema = @Schema(implementation = CustomerValue.class)))) + public Response doGetDetailCustomer(@PathParam("id") Long id) { + LOG.debug("Get Customer details for id " + id); + var retVal = customerPictureService.get(id); return Response.ok().entity(retVal).build(); } } diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResource.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResource.java new file mode 100644 index 0000000..f9d2cea --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/PictureResource.java @@ -0,0 +1,52 @@ +package marketing.heyday.hartmann.fotodocumentation.rest; + +import static marketing.heyday.hartmann.fotodocumentation.rest.jackson.ApplicationConfigApi.JSON_OUT; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jboss.resteasy.annotations.GZIP; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.ejb.EJB; +import jakarta.enterprise.context.RequestScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; +import marketing.heyday.hartmann.fotodocumentation.core.service.PictureService; +import marketing.heyday.hartmann.fotodocumentation.rest.vo.PictureValue; + +/** + * + *

Copyright: Copyright (c) 2024

+ *

Company: heyday Marketing GmbH

+ * @author Patrick Verboom + * @version 1.0 + * + * created: 21 Jan 2026 + */ +@RequestScoped +@Path("picture") +public class PictureResource { + private static final Log LOG = LogFactory.getLog(PictureResource.class); + + @EJB + private PictureService pictureService; + + @GZIP + @GET + @Path("{id}") + @Produces(JSON_OUT) + @Operation(summary = "Get picture value") + @ApiResponse(responseCode = "200", description = "Successfully retrieved picture value", content = @Content(mediaType = JSON_OUT, array = @ArraySchema(schema = @Schema(implementation = PictureValue.class)))) + public Response doGetDetailCustomer(@PathParam("id") Long id) { + LOG.debug("Get Picture details for id " + id); + var retVal = pictureService.get(id); + return Response.ok().entity(retVal).build(); + } +} diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/jackson/ApplicationConfigApi.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/jackson/ApplicationConfigApi.java index 174aaaf..50d38e2 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/jackson/ApplicationConfigApi.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/jackson/ApplicationConfigApi.java @@ -14,10 +14,7 @@ import io.swagger.v3.oas.annotations.servers.Server; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MediaType; -import marketing.heyday.hartmann.fotodocumentation.rest.CustomerPictureResource; -import marketing.heyday.hartmann.fotodocumentation.rest.CustomerResource; -import marketing.heyday.hartmann.fotodocumentation.rest.LoginResource; -import marketing.heyday.hartmann.fotodocumentation.rest.MonitoringResource; +import marketing.heyday.hartmann.fotodocumentation.rest.*; /** * @@ -48,6 +45,7 @@ public class ApplicationConfigApi extends Application { retVal.add(MonitoringResource.class); retVal.add(CustomerPictureResource.class); retVal.add(CustomerResource.class); + retVal.add(PictureResource.class); LOG.info("returning rest api classes " + retVal); return retVal; } diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerListValue.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerListValue.java index 6ebabf4..5a40024 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerListValue.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerListValue.java @@ -1,6 +1,10 @@ package marketing.heyday.hartmann.fotodocumentation.rest.vo; +import java.util.Date; + +import io.swagger.v3.oas.annotations.media.Schema; import marketing.heyday.hartmann.fotodocumentation.core.model.Customer; +import marketing.heyday.hartmann.fotodocumentation.core.model.Picture; /** * @@ -12,12 +16,14 @@ import marketing.heyday.hartmann.fotodocumentation.core.model.Customer; * created: 19 Jan 2026 */ -public record CustomerListValue(String name, String customerNumber, int amountOfPicture) { +@Schema(name = "CustomerList") +public record CustomerListValue(Long id, String name, String customerNumber, Date lastUpdateDate) { public static CustomerListValue builder(Customer customer) { if (customer == null) { return null; } - return new CustomerListValue(customer.getName(), customer.getCustomerNumber(), customer.getPictures().size()); + Date date = customer.getPictures().stream().map(Picture::getPictureDate).sorted((p1, p2) -> p2.compareTo(p1)).findFirst().orElse(null); + return new CustomerListValue(customer.getCustomerId(), customer.getName(), customer.getCustomerNumber(), date); } } diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerPictureValue.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerPictureValue.java index 0aa212e..86da208 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerPictureValue.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerPictureValue.java @@ -2,6 +2,8 @@ package marketing.heyday.hartmann.fotodocumentation.rest.vo; import java.util.Date; +import io.swagger.v3.oas.annotations.media.Schema; + /** * *

Copyright: Copyright (c) 2024

@@ -11,7 +13,7 @@ import java.util.Date; * * created: 19 Jan 2026 */ - -public record CustomerPictureValue(String username, String pharmacyName, String customerNumber, Date date, String comment, String base64String) { +@Schema(name = "CustomerPictureUpload") +public record CustomerPictureValue(String username, String pharmacyName, String customerNumber, Date date, String comment, String category, String base64String) { } diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerValue.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerValue.java new file mode 100644 index 0000000..e70ba7b --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/CustomerValue.java @@ -0,0 +1,27 @@ +package marketing.heyday.hartmann.fotodocumentation.rest.vo; + +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; +import marketing.heyday.hartmann.fotodocumentation.core.model.Customer; + +/** + * + *

Copyright: Copyright (c) 2024

+ *

Company: heyday Marketing GmbH

+ * @author Patrick Verboom + * @version 1.0 + * + * created: 22 Jan 2026 + */ +@Schema(name = "Customer") +public record CustomerValue(Long id, String name, String customerNumber, List pictures) { + + public static CustomerValue builder(Customer customer) { + if (customer == null) { + return null; + } + List pictures = customer.getPictures().parallelStream().map(PictureValue::builder).filter(p -> p != null).toList(); + return new CustomerValue(customer.getCustomerId(), customer.getName(), customer.getCustomerNumber(), pictures); + } +} \ No newline at end of file diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/PictureValue.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/PictureValue.java new file mode 100644 index 0000000..31cdeea --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/vo/PictureValue.java @@ -0,0 +1,27 @@ +package marketing.heyday.hartmann.fotodocumentation.rest.vo; + +import java.util.Date; + +import io.swagger.v3.oas.annotations.media.Schema; +import marketing.heyday.hartmann.fotodocumentation.core.model.Picture; + +/** + * + *

Copyright: Copyright (c) 2024

+ *

Company: heyday Marketing GmbH

+ * @author Patrick Verboom + * @version 1.0 + * + * created: 22 Jan 2026 + */ + +@Schema(name = "Picture") +public record PictureValue(Long id, String comment, String category, String image, Date pictureDate, String username) { + + public static PictureValue builder(Picture picture) { + if (picture == null) { + return null; + } + return new PictureValue(picture.getPictureId(), picture.getComment(), picture.getCategory(), picture.getImage(), picture.getPictureDate(), picture.getUsername()); + } +} \ No newline at end of file diff --git a/hartmann-foto-documentation-app/src/main/resources/marketing/heyday/hartmann/fotodocumentation/core/db/migration/V2__init2.sql b/hartmann-foto-documentation-app/src/main/resources/marketing/heyday/hartmann/fotodocumentation/core/db/migration/V2__init2.sql new file mode 100644 index 0000000..e81bded --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/resources/marketing/heyday/hartmann/fotodocumentation/core/db/migration/V2__init2.sql @@ -0,0 +1,4 @@ + +-- picture + +alter table picture add column category varchar(250); diff --git a/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war b/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war index 9f17fc9d705d7566f312507301b19e2847ccae30..133c95d81cb2601332161b886ef7dafa3e489c89 100644 GIT binary patch delta 57584 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 a5KZ7S(_JCJ4zvdNF1(6^lM7-vIR6Kd$p6m( delta 53676 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 GVEzMz7V=L3 diff --git a/hartmann-foto-documentation-frontend/assets/images/logo.png b/hartmann-foto-documentation-frontend/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8026da0b49f8b7487270d713d33506fbd2dc449c GIT binary patch literal 2197 zcmV;G2x|97R4K^?2MI!2+1vNc?%3<~uK%3`;!hgydUp2B+xOnQ znRzp#&`vD&7N{WUnC(WHItBLqqH&* zqb)-9l;94!=-qyw5V%HOuF9R;f_EAW5p^hT&ksGJgn&_8C&PImA=-}YO0 z6`&{){+XeAEkghFMX5q4PYF`+Nv)G0`1i`v%SMdF7oj~AE>|I?DWT=AS6IBJ_;;D$ zoe*PLgs3hNDWxwdP6##IUuha1W%W$d6i_j+M^P84 zdDzyYWVQqhIXCuC)qH<7p$2KKH)PJK%Xr?^UH5CC+&{lf@%ub z8fY{sMw|w8chofE=g8x@(>w0NjpS`TO0nfQMSgvm%J*G4L9PQI(e&(diobrB%6Bz3 zkgxqD#n<0abZM1hFJEWTGQ(O<(zQa?@4a}0+(!1IxcQuHK`pb=eK%P8x=e#VhE^XJh1jdO_N){DOi&jE74f9(tn_O`MBmML=eK`MW0 z;;K&Ved59IB7so|Z~o^S;+YUOC;0)mqTI~fUs-gl z^nI64WYnLFH_xd7smBj%Ss_v#!9L zNYdh<2nY%Pksce?1uH|ju^*mf(Ez%?KA+0#H0rmrK*^g_FzR#S4H&)iMPA{e<$`B3 z4@JO*z5yMxr_eW08T_40cDQ7?iRrmNX=YZpTq&50+WF%o9q9tlI&w7MEKIU%fWQLm zDs=uDg$2W9*wK-^;k_BdU)%8p!G>Akg$Osqu1@lR`iq8^^so}NqLcuCCsX$GM$v%* z=s;m{fL*hP6-qk(Yq}u9_n%NPfYQSEo(fvt*2ApK&qaR7)CV~6COb_hmlQOFJNRvD zPT|I1Z&GMvLI0lb+o@=1%2~CT@~|#+bTU~XJ4*-$f|Va_*X&WoOrz)aBMREk%!e^U zae5l>UWZ7C|vlruPhh`7uLV$yPtyBuoiZFT# z-+Mw6KP^>^H$b-5d;Th|CsmGiKelW|sjlKKGnCl$y|?oOMPiXTjzfDC$MRfAfr7mIXbh(UiH2 ztg}$w?8;eZ$WGA$DJuZ2GdFnQXRWVG4_h=31-W||q#CvmycDV?&S8xq9m;MVSuAF_ zT0@yos7J22rH80$;wnFgQ4Q6HqGc?3W-jVrO<7atKgf++I|q}r*6%lGWKPzw2CDE{ zXst!)z>Yh(4K;a;!_Eg;c#62HLPk9$$SGu10FXUnh9Wlio+D^%_aZ6;z1qjZR-^14 z0<^JjoDPl5>&w4thsa;;KgQ*{@+(8ZPVg>PK+yJS&Uage{9NJSE^|+lZ0+6jX1|Z6 zGn!|!Lwe_3vTn+DU?W;nqAS<_qujBhm4OMpRU|{S8vu)jyLh`VpszHh6@Y~zZM9FV z9hl%2<&yQCVql~1z)RDTttrn2u>1kMcu$T=LBltw;D=h2R>@oTe zQVFQ1a-cW2h`9fd6k-6u#jQYfj!Q|Gl~lc4#o6~H9HLt?hM~s(E4Uxo7ob%jIES_K zZe#5LN=gUs^k5v7fbv>`6ZFx*gprIBJq;f2M{qjx5(%HX(-foVt(g)b=F^KD2-qAU z1zvIVH#>4Gf^j8}AgiMUEA(Tk5hJCyi8qaSc#O@%z&bZ2aZDD+W?4ZRxy13=O1d>g zjOoEgj9~RRPOI{QR)QyHA6t&nwhVP~E6&&qhn*j>BkT0+k2Nb{oce5|GERGL?Gf5i zp}RN0T~PT%U7VGx3$;|?h_e}LkncL}3zT*$$CeV+kQ=%(;5v2psSqq5TNopiI*9&1 X`UVnSe;jJg00000NkvXXu0mjf{;C{I literal 0 HcmV?d00001 diff --git a/hartmann-foto-documentation-frontend/lib/controller/customer_controller.dart b/hartmann-foto-documentation-frontend/lib/controller/customer_controller.dart new file mode 100644 index 0000000..541de6e --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/controller/customer_controller.dart @@ -0,0 +1,31 @@ +import 'package:fotodocumentation/controller/base_controller.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; + +abstract interface class CustomerController { + Future> getAll(String query, String startsWith); + + Future get({required int id}); +} + +class CustomerControllerImpl extends BaseController implements CustomerController { + final String path = "customer"; + + @override + Future> getAll(String query, String startsWith) async { + String uriStr = '${uriUtils.getBaseUrl()}$path?query=$query&startsWith=$startsWith'; + return runGetListWithAuth(uriStr, (p0) { + List retVal = []; + for (var elem in p0) { + var entity = CustomerListDto.fromJson(elem); + retVal.add(entity); + } + return retVal; + }); + } + + @override + Future get({required int id}) { + String uriStr = '${uriUtils.getBaseUrl()}$path/$id'; + return runGetWithAuth(uriStr, (json) => CustomerDto.fromJson(json)); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/controller/picture_controller.dart b/hartmann-foto-documentation-frontend/lib/controller/picture_controller.dart new file mode 100644 index 0000000..af168ce --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/controller/picture_controller.dart @@ -0,0 +1,23 @@ +import 'package:fotodocumentation/controller/base_controller.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; + +abstract interface class PictureController { + Future get({required int id}); + Future delete(PictureDto dto); +} + +class PictureControllerImpl extends BaseController implements PictureController { + final String path = "picture"; + + @override + Future get({required int id}) { + String uriStr = '${uriUtils.getBaseUrl()}$path/$id'; + return runGetWithAuth(uriStr, (json) => PictureDto.fromJson(json)); + } + + @override + Future delete(PictureDto dto) { + // TODO: implement delete + throw UnimplementedError(); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/dto/customer_dto.dart b/hartmann-foto-documentation-frontend/lib/dto/customer_dto.dart new file mode 100644 index 0000000..8f78a18 --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/dto/customer_dto.dart @@ -0,0 +1,62 @@ +import 'package:fotodocumentation/utils/date_time_utils.dart'; + +class CustomerListDto { + final int id; + final String name; + final String customerNumber; + final DateTime? lastUpdateDate; + + CustomerListDto({required this.id, required this.name, required this.customerNumber, required this.lastUpdateDate}); + + /// Create from JSON response + factory CustomerListDto.fromJson(Map json) { + return CustomerListDto( + id: json['id'] as int, + name: json['name'] as String, + customerNumber: json['customerNumber'] as String, + lastUpdateDate: DateTimeUtils.toDateTime(json['lastUpdateDate']), + ); + } +} + +class CustomerDto { + final int id; + final String name; + final String customerNumber; + final List pictures; + + CustomerDto({required this.id, required this.name, required this.customerNumber, required this.pictures}); + + /// Create from JSON response + factory CustomerDto.fromJson(Map json) { + return CustomerDto( + id: json['id'] as int, + name: json['name'] as String, + customerNumber: json['customerNumber'] as String, + pictures: List.from(json["pictures"].map((x) => PictureDto.fromJson(x))), + ); + } +} + +class PictureDto { + final int id; + final String? comment; + final String? category; + final String image; + final DateTime pictureDate; + final String? username; + +PictureDto({required this.id, required this.comment, required this.category, required this.image, required this.pictureDate, required this.username}); + + /// Create from JSON response + factory PictureDto.fromJson(Map json) { + return PictureDto( + id: json['id'] as int, + comment: json['comment'] as String?, + category: json['category'] as String?, + image: json['image'] as String, + pictureDate: DateTimeUtils.toDateTime(json['pictureDate']) ?? DateTime.now(), + username: json['username'] as String?, + ); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/l10n/app_de.arb b/hartmann-foto-documentation-frontend/lib/l10n/app_de.arb index 18f6c9f..b27c4c0 100644 --- a/hartmann-foto-documentation-frontend/lib/l10n/app_de.arb +++ b/hartmann-foto-documentation-frontend/lib/l10n/app_de.arb @@ -1,13 +1,9 @@ { "@@locale": "de", - "searchTFHint": "Suchtext", + "searchTFHint": "Suche nach Apothekennamen, Kundennummer, Datum", "@searchTFHint": { "description": "Search hint TextField" }, - "searchButtonLabel": "Suchen", - "@searchButtonLabel": { - "description": "Search button label" - }, "loginUsernameTFLabel": "Benutzername", "@loginUsernameTFLabel": { "description": "Usernamt TextField Label" @@ -16,10 +12,18 @@ "@loginPasswordTFLabel": { "description": "Password TextField Label" }, - "loginLoginButtonLabel": "Anmelden", + "loginLoginButtonLabel": "Einloggen", "@loginLoginButtonLabel": { "description": "Login Button Label" }, + "loginTitle": "BILDERUPLOAD", + "@loginTitle": { + "description": "Login page title" + }, + "loginErrorMessage": "Falscher Benutzername oder Passwort", + "@loginErrorMessage": { + "description": "Login error message for invalid credentials" + }, "errorWidgetStatusCode": "Statuscode {statusCode}", "@errorWidgetStatusCode": { "description": "Error message showing server status code", @@ -48,10 +52,6 @@ "@submitWidget": { "description": "Save Button text" }, - "textInputWidgetValidatorText": "Bitte geben Sie einen Text ein", - "@textInputWidgetValidatorText": { - "description": "Awaiting result info text" - }, "waitingWidget": "Warten auf Ergebnis …", "@waitingWidget": { "description": "Awaiting result info text" @@ -71,5 +71,58 @@ "deleteDialogButtonApprove": "Ja", "@deleteDialogButtonApprove": { "description": "Approve Button text" + }, + "customerListHeaderCustomerNumber": "Kunden-Nr.", + "@customerListHeaderCustomerNumber": { + "description": "Customer list table header for customer number" + }, + "customerListHeaderName": "Apothekenname", + "@customerListHeaderName": { + "description": "Customer list table header for name" + }, + "customerListHeaderLastDate": "Datum Bilder", + "@customerListHeaderLastDate": { + "description": "Customer list table header for last date" + }, + "customerListHeaderLastDateSuffix": " (zuletzt aktualisiert)", + "@customerListHeaderLastDateSuffix": { + "description": "Customer list table header for ladt date" + }, + "customerListHeadline": "BILDERUPLOAD", + "@customerListHeadline": { + "description": "Customer list page headline" + }, + "customerListEmpty": "Keine Ergebnisse gefunden", + "@customerListEmpty": { + "description": "Empty customer list message" + }, + "customerWidgetNotFound": "Die Apotheke konnte nicht gefunden werden.", + "@customerWidgetNotFound": { + "description": "Customer not found error message" + }, + "customerWidgetCustomerNumberPrefix": "KundenNr: {customerNumber}", + "@customerWidgetCustomerNumberPrefix": { + "description": "Customer number prefix with placeholder", + "placeholders": { + "customerNumber": { + "type": "String" + } + } + }, + "customerWidgetHeaderFoto": "Foto", + "@customerWidgetHeaderFoto": { + "description": "Customer widget table header for photo" + }, + "customerWidgetHeaderComment": "Kommentar", + "@customerWidgetHeaderComment": { + "description": "Customer widget table header for comment" + }, + "customerWidgetHeaderUploadDate": "Upload-Datum", + "@customerWidgetHeaderUploadDate": { + "description": "Customer widget table header for upload date" + }, + "backButtonLabel": "zurück", + "@backButtonLabel": { + "description": "Back button label" } } \ No newline at end of file diff --git a/hartmann-foto-documentation-frontend/lib/l10n/app_localizations.dart b/hartmann-foto-documentation-frontend/lib/l10n/app_localizations.dart index af6b044..8a31bd0 100644 --- a/hartmann-foto-documentation-frontend/lib/l10n/app_localizations.dart +++ b/hartmann-foto-documentation-frontend/lib/l10n/app_localizations.dart @@ -97,15 +97,9 @@ abstract class AppLocalizations { /// Search hint TextField /// /// In de, this message translates to: - /// **'Suchtext'** + /// **'Suche nach Apothekennamen, Kundennummer, Datum'** String get searchTFHint; - /// Search button label - /// - /// In de, this message translates to: - /// **'Suchen'** - String get searchButtonLabel; - /// Usernamt TextField Label /// /// In de, this message translates to: @@ -121,9 +115,21 @@ abstract class AppLocalizations { /// Login Button Label /// /// In de, this message translates to: - /// **'Anmelden'** + /// **'Einloggen'** String get loginLoginButtonLabel; + /// Login page title + /// + /// In de, this message translates to: + /// **'BILDERUPLOAD'** + String get loginTitle; + + /// Login error message for invalid credentials + /// + /// In de, this message translates to: + /// **'Falscher Benutzername oder Passwort'** + String get loginErrorMessage; + /// Error message showing server status code /// /// In de, this message translates to: @@ -148,12 +154,6 @@ abstract class AppLocalizations { /// **'Speichern'** String get submitWidget; - /// Awaiting result info text - /// - /// In de, this message translates to: - /// **'Bitte geben Sie einen Text ein'** - String get textInputWidgetValidatorText; - /// Awaiting result info text /// /// In de, this message translates to: @@ -183,6 +183,78 @@ abstract class AppLocalizations { /// In de, this message translates to: /// **'Ja'** String get deleteDialogButtonApprove; + + /// Customer list table header for customer number + /// + /// In de, this message translates to: + /// **'Kunden-Nr.'** + String get customerListHeaderCustomerNumber; + + /// Customer list table header for name + /// + /// In de, this message translates to: + /// **'Apothekenname'** + String get customerListHeaderName; + + /// Customer list table header for last date + /// + /// In de, this message translates to: + /// **'Datum Bilder'** + String get customerListHeaderLastDate; + + /// Customer list table header for ladt date + /// + /// In de, this message translates to: + /// **' (zuletzt aktualisiert)'** + String get customerListHeaderLastDateSuffix; + + /// Customer list page headline + /// + /// In de, this message translates to: + /// **'BILDERUPLOAD'** + String get customerListHeadline; + + /// Empty customer list message + /// + /// In de, this message translates to: + /// **'Keine Ergebnisse gefunden'** + String get customerListEmpty; + + /// Customer not found error message + /// + /// In de, this message translates to: + /// **'Die Apotheke konnte nicht gefunden werden.'** + String get customerWidgetNotFound; + + /// Customer number prefix with placeholder + /// + /// In de, this message translates to: + /// **'KundenNr: {customerNumber}'** + String customerWidgetCustomerNumberPrefix(String customerNumber); + + /// Customer widget table header for photo + /// + /// In de, this message translates to: + /// **'Foto'** + String get customerWidgetHeaderFoto; + + /// Customer widget table header for comment + /// + /// In de, this message translates to: + /// **'Kommentar'** + String get customerWidgetHeaderComment; + + /// Customer widget table header for upload date + /// + /// In de, this message translates to: + /// **'Upload-Datum'** + String get customerWidgetHeaderUploadDate; + + /// Back button label + /// + /// In de, this message translates to: + /// **'zurück'** + String get backButtonLabel; } class _AppLocalizationsDelegate diff --git a/hartmann-foto-documentation-frontend/lib/l10n/app_localizations_de.dart b/hartmann-foto-documentation-frontend/lib/l10n/app_localizations_de.dart index 3851fc8..ddcdc7e 100644 --- a/hartmann-foto-documentation-frontend/lib/l10n/app_localizations_de.dart +++ b/hartmann-foto-documentation-frontend/lib/l10n/app_localizations_de.dart @@ -9,10 +9,7 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get searchTFHint => 'Suchtext'; - - @override - String get searchButtonLabel => 'Suchen'; + String get searchTFHint => 'Suche nach Apothekennamen, Kundennummer, Datum'; @override String get loginUsernameTFLabel => 'Benutzername'; @@ -21,7 +18,13 @@ class AppLocalizationsDe extends AppLocalizations { String get loginPasswordTFLabel => 'Passwort'; @override - String get loginLoginButtonLabel => 'Anmelden'; + String get loginLoginButtonLabel => 'Einloggen'; + + @override + String get loginTitle => 'BILDERUPLOAD'; + + @override + String get loginErrorMessage => 'Falscher Benutzername oder Passwort'; @override String errorWidgetStatusCode(int statusCode) { @@ -39,9 +42,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get submitWidget => 'Speichern'; - @override - String get textInputWidgetValidatorText => 'Bitte geben Sie einen Text ein'; - @override String get waitingWidget => 'Warten auf Ergebnis …'; @@ -57,4 +57,43 @@ class AppLocalizationsDe extends AppLocalizations { @override String get deleteDialogButtonApprove => 'Ja'; + + @override + String get customerListHeaderCustomerNumber => 'Kunden-Nr.'; + + @override + String get customerListHeaderName => 'Apothekenname'; + + @override + String get customerListHeaderLastDate => 'Datum Bilder'; + + @override + String get customerListHeaderLastDateSuffix => ' (zuletzt aktualisiert)'; + + @override + String get customerListHeadline => 'BILDERUPLOAD'; + + @override + String get customerListEmpty => 'Keine Ergebnisse gefunden'; + + @override + String get customerWidgetNotFound => + 'Die Apotheke konnte nicht gefunden werden.'; + + @override + String customerWidgetCustomerNumberPrefix(String customerNumber) { + return 'KundenNr: $customerNumber'; + } + + @override + String get customerWidgetHeaderFoto => 'Foto'; + + @override + String get customerWidgetHeaderComment => 'Kommentar'; + + @override + String get customerWidgetHeaderUploadDate => 'Upload-Datum'; + + @override + String get backButtonLabel => 'zurück'; } diff --git a/hartmann-foto-documentation-frontend/lib/main.dart b/hartmann-foto-documentation-frontend/lib/main.dart index 42f8d02..cfea410 100644 --- a/hartmann-foto-documentation-frontend/lib/main.dart +++ b/hartmann-foto-documentation-frontend/lib/main.dart @@ -8,6 +8,8 @@ import 'package:fotodocumentation/utils/di_container.dart'; import 'package:fotodocumentation/utils/main_utils.dart'; import 'package:fotodocumentation/utils/global_router.dart'; +import 'package:intl/date_symbol_data_local.dart'; + var logger = Logger( printer: PrettyPrinter(methodCount: 2, errorMethodCount: 8, colors: true, printEmojis: true, dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart), ); @@ -18,6 +20,7 @@ void main() async { final theme = await ThemeLoader.loadTheme(); + await initializeDateFormatting('de_DE', null); LoginController loginController = DiContainer.get(); //await loginController.isLoggedIn(); runApp(FotoDocumentationApp(theme: theme)); diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_list_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_list_widget.dart new file mode 100644 index 0000000..593941c --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_list_widget.dart @@ -0,0 +1,275 @@ +import 'package:flutter/material.dart'; +import 'package:fotodocumentation/controller/base_controller.dart'; +import 'package:fotodocumentation/controller/customer_controller.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; +import 'package:fotodocumentation/l10n/app_localizations.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/search_bar_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; +import 'package:fotodocumentation/utils/di_container.dart'; +import 'package:fotodocumentation/utils/global_router.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; + +class CustomerListWidget extends StatefulWidget { + const CustomerListWidget({super.key}); + + @override + State createState() => _CustomerListWidgetState(); +} + +class _CustomerListWidgetState extends State { + CustomerController get _customerController => DiContainer.get(); + GeneralStyle get _generalStyle => DiContainer.get(); + + final _searchController = TextEditingController(); + late Future> _dtos; + String? _selectedLetter; + late DateFormat _dateFormat; + + @override + void initState() { + super.initState(); + _dateFormat = DateFormat('dd MMMM yyyy'); + _dtos = _customerController.getAll(_searchController.text, ""); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _body(context), + ); + } + + Widget _body(BuildContext context) { + return Container( + color: _generalStyle.pageBackgroundColor, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, left: 50.0, right: 50.0, bottom: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PageHeaderWidget(text: AppLocalizations.of(context)!.customerListHeadline), + _abcHeaderBar(), + FractionallySizedBox( + widthFactor: 0.5, + alignment: Alignment.centerLeft, + child: SearchBarWidget( + searchController: _searchController, + onSearch: (text) async => actionSearch(text), + ), + ), + Expanded( + child: _customerListWidget(), + ), + ], + ), + ), + ); + } + + Widget _customerListWidget() { + return FutureBuilder>( + future: _dtos, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const WaitingWidget(); + } + if (snapshot.hasData) { + List dtos = snapshot.data ?? []; + + return _listWidget(dtos); + } else if (snapshot.hasError) { + var error = snapshot.error; + return (error is ServerError) ? GeneralErrorWidget.fromServerError(error) : GeneralErrorWidget(error: snapshot.error.toString()); + } + return const WaitingWidget(); + }, + ); + } + + Widget _listWidget(List dtos) { + if (dtos.isEmpty) { + return Text(AppLocalizations.of(context)!.customerListEmpty, + style: TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + )); + } + return Card( + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + _tableHeaderRow(context), + Divider(thickness: 2, height: 1, color: _generalStyle.secondaryWidgetBackgroundColor), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(0), + itemCount: dtos.length, + itemBuilder: (BuildContext context, int index) { + return _tableDataRow(context, dtos[index]); + }, + separatorBuilder: (BuildContext context, int index) => Divider( + color: _generalStyle.secondaryWidgetBackgroundColor, + ), + ), + ), + ], + ), + ); + } + + Widget _tableHeaderRow(BuildContext context) { + final headerStyle = TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + ); + + final headerStyleSuffix = TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.normal, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + ); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + const SizedBox(width: 48), + Expanded( + flex: 1, + child: Text( + AppLocalizations.of(context)!.customerListHeaderCustomerNumber, + style: headerStyle, + ), + ), + Expanded( + flex: 3, + child: Text( + AppLocalizations.of(context)!.customerListHeaderName, + style: headerStyle, + ), + ), + Expanded( + flex: 2, + child: Wrap( + children: [ + Text( + AppLocalizations.of(context)!.customerListHeaderLastDate, + style: headerStyle, + ), + Text( + AppLocalizations.of(context)!.customerListHeaderLastDateSuffix, + style: headerStyleSuffix, + ), + ], + ), + ), + ], + ), + ); + } + + Widget _tableDataRow(BuildContext context, CustomerListDto dto) { + final dataStyle = TextStyle( + fontFamily: _generalStyle.fontFamily, + fontSize: 16.0, + color: _generalStyle.secondaryTextLabelColor, + ); + + final dateStr = dto.lastUpdateDate == null ? "" : _dateFormat.format(dto.lastUpdateDate!); + return InkWell( + onTap: () => _actionSelect(context, dto), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + children: [ + SizedBox( + width: 48, + child: Icon(Icons.folder_open_outlined, color: _generalStyle.secondaryWidgetBackgroundColor), + ), + Expanded( + flex: 1, + child: Text(dto.customerNumber, style: dataStyle), + ), + Expanded( + flex: 3, + child: Text(dto.name, style: dataStyle), + ), + Expanded( + flex: 2, + child: Text(dateStr, style: dataStyle), + ), + ], + ), + ), + ); + } + + Future actionSearch(String text) async { + _reloadData(); + } + + Future _actionSelect(BuildContext context, CustomerListDto dto) async { + context.go("${GlobalRouter.pathCustomer}/${dto.id}"); + } + + void _reloadData() { + _dtos = _customerController.getAll(_searchController.text, _selectedLetter ?? ""); + setState(() {}); + } + + Widget _abcHeaderBar() { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Wrap( + spacing: 8, + runSpacing: 8, + children: letters.split('').map((letter) { + final isSelected = _selectedLetter == letter; + return InkWell( + onTap: () { + setState(() { + _selectedLetter = isSelected ? null : letter; + _reloadData(); + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? _generalStyle.secondaryWidgetBackgroundColor : Colors.white, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + letter, + style: TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20.0, + color: isSelected ? Colors.white : _generalStyle.secondaryWidgetBackgroundColor, + ), + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_row_item.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_row_item.dart new file mode 100644 index 0000000..8848a42 --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_row_item.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; +import 'package:fotodocumentation/pages/ui_utils/dialog/delete_dialog.dart'; + +class CustomerRowItem extends StatelessWidget { + final CustomerListDto dto; + final Future Function(CustomerListDto) doDelete; + final Future Function(CustomerListDto)? doSelect; + + const CustomerRowItem({super.key, required this.dto, required this.doDelete, this.doSelect}); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: CircleAvatar( + backgroundColor: Colors.grey[300], + child: Text( + dto.name.isNotEmpty ? dto.name[0].toUpperCase() : '?', + style: TextStyle( + color: Colors.grey[700], + fontWeight: FontWeight.bold, + ), + ), + ), + title: Text(dto.name), + subtitle: Text('CustomerNumber: ${dto.customerNumber}'), + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + onTap: () async => await _doSelect(context), + ); + } + + Future _doSelect(BuildContext context) async { + if (doSelect != null) { + await doSelect!(dto); + } + } +} diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart index 72f592d..de766e3 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/customer_widget.dart @@ -1,15 +1,260 @@ +import 'dart:convert' show base64Decode; + import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:intl/intl.dart'; + +import 'package:fotodocumentation/controller/base_controller.dart'; +import 'package:fotodocumentation/l10n/app_localizations.dart'; +import 'package:fotodocumentation/controller/customer_controller.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/page_header_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; +import 'package:fotodocumentation/utils/di_container.dart'; +import 'package:fotodocumentation/utils/global_router.dart'; + class CustomerWidget extends StatefulWidget { - const CustomerWidget({super.key}); + final int customerId; + const CustomerWidget({super.key, required this.customerId}); @override State createState() => _CustomerWidgetState(); } class _CustomerWidgetState extends State { + CustomerController get _customerController => DiContainer.get(); + GeneralStyle get _generalStyle => DiContainer.get(); + + late Future _dto; + late DateFormat _dateFormat; + + @override + void initState() { + super.initState(); + _dateFormat = DateFormat('dd MMMM yyyy'); + _dto = _customerController.get(id: widget.customerId); + } + @override Widget build(BuildContext context) { - return const Placeholder(); + return Scaffold( + body: Container( + color: _generalStyle.pageBackgroundColor, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, left: 50.0, right: 50.0, bottom: 8.0), + child: _body(context), + ), + ), + ); } -} \ No newline at end of file + + Widget _body(BuildContext context) { + return FutureBuilder( + future: _dto, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const WaitingWidget(); + } + if (snapshot.hasData) { + CustomerDto? dto = snapshot.data; + + return _mainWidget(dto); + } else if (snapshot.hasError) { + var error = snapshot.error; + return (error is ServerError) ? GeneralErrorWidget.fromServerError(error) : GeneralErrorWidget(error: snapshot.error.toString()); + } + return const WaitingWidget(); + }, + ); + } + + Widget _mainWidget(CustomerDto? dto) { + if (dto == null) { + return Text( + AppLocalizations.of(context)!.customerWidgetNotFound, + style: TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + ), + ); + } + + var subText = AppLocalizations.of(context)!.customerWidgetCustomerNumberPrefix(dto.customerNumber); + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PageHeaderWidget(text: dto.name, subText: subText), + _backButton(context), + const SizedBox(height: 24), + Expanded( + child: _customerWidget(dto), + ), + ], + ), + ); + } + + Widget _customerWidget(CustomerDto dto) { + var dtos = dto.pictures; + + return Card( + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + _tableHeaderRow(context), + Divider(thickness: 2, height: 1, color: _generalStyle.secondaryWidgetBackgroundColor), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.only(top: 8.0), + itemCount: dtos.length, + itemBuilder: (BuildContext context, int index) { + return _tableDataRow(context, dtos[index]); + }, + separatorBuilder: (BuildContext context, int index) => Divider(color: _generalStyle.secondaryWidgetBackgroundColor), + ), + ), + ], + ), + ); + } + + Widget _tableHeaderRow(BuildContext context) { + final headerStyle = TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + ); + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 1, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.customerWidgetHeaderFoto, + style: headerStyle, + ), + ), + ), + Expanded( + flex: 3, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.customerWidgetHeaderComment, + style: headerStyle, + ), + ), + ), + Expanded( + flex: 2, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.customerWidgetHeaderUploadDate, + style: headerStyle, + ), + ), + ), + ], + ), + ); + } + + Widget _tableDataRow(BuildContext context, PictureDto dto) { + final dataStyle = TextStyle( + fontFamily: _generalStyle.fontFamily, + fontSize: 16.0, + color: _generalStyle.secondaryTextLabelColor, + ); + + final dateStr = _dateFormat.format(dto.pictureDate); + return InkWell( + onTap: () => _actionSelect(context, dto), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 1, + child: Align( + alignment: Alignment.centerLeft, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 70, maxHeight: 70), + child: Image.memory( + base64Decode(dto.image), + fit: BoxFit.contain, + ), + ), + ), + ), + Expanded( + flex: 3, + child: Align( + alignment: Alignment.centerLeft, + child: Text(dto.comment ?? "", style: dataStyle), + ), + ), + Expanded( + flex: 2, + child: Align( + alignment: Alignment.centerLeft, + child: Text(dateStr, style: dataStyle), + ), + ), + ], + ), + ), + ); + } + + Future _actionSelect(BuildContext context, PictureDto dto) async { + context.go("${GlobalRouter.pathPicture}/${dto.id}"); + } + + Widget _backButton(BuildContext context) { + return ElevatedButton.icon( + onPressed: () => context.go(GlobalRouter.pathHome), + icon: Icon( + Icons.chevron_left, + color: _generalStyle.secondaryTextLabelColor, + size: 24, + ), + label: Text( + AppLocalizations.of(context)!.backButtonLabel, + style: TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.normal, + fontSize: 16, + color: _generalStyle.secondaryTextLabelColor, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart new file mode 100644 index 0000000..30877b7 --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/pages/customer/picture_widget.dart @@ -0,0 +1,234 @@ +import 'dart:convert' show base64Decode; + +import 'package:flutter/material.dart'; +import 'package:fotodocumentation/controller/picture_controller.dart'; + +import 'package:intl/intl.dart'; + +import 'package:fotodocumentation/controller/base_controller.dart'; +import 'package:fotodocumentation/dto/customer_dto.dart'; +import 'package:fotodocumentation/l10n/app_localizations.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/general_error_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/component/waiting_widget.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; +import 'package:fotodocumentation/utils/di_container.dart'; + +class PictureWidget extends StatefulWidget { + final int id; + const PictureWidget({super.key, required this.id}); + + @override + State createState() => _PictureWidgetState(); +} + +class _PictureWidgetState extends State { + PictureController get _pictureController => DiContainer.get(); + GeneralStyle get _generalStyle => DiContainer.get(); + + late Future _dto; + late DateFormat _dateFormat; + final ScrollController _commentScrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _dateFormat = DateFormat('dd MMMM yyyy'); + _dto = _pictureController.get(id: widget.id); + } + + @override + void dispose() { + _commentScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + color: _generalStyle.pageBackgroundColor, + child: Padding( + padding: const EdgeInsets.only(top: 8.0, left: 50.0, right: 50.0, bottom: 8.0), + child: _body(context), + ), + ), + ); + } + + Widget _body(BuildContext context) { + return FutureBuilder( + future: _dto, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const WaitingWidget(); + } + if (snapshot.hasData) { + PictureDto? dto = snapshot.data; + + return _mainWidget(dto); + } else if (snapshot.hasError) { + var error = snapshot.error; + return (error is ServerError) ? GeneralErrorWidget.fromServerError(error) : GeneralErrorWidget(error: snapshot.error.toString()); + } + return const WaitingWidget(); + }, + ); + } + + Widget _mainWidget(PictureDto? dto) { + if (dto == null) { + return Text( + AppLocalizations.of(context)!.customerWidgetNotFound, + style: TextStyle( + fontFamily: _generalStyle.fontFamily, + fontWeight: FontWeight.bold, + fontSize: 20, + color: _generalStyle.secondaryWidgetBackgroundColor, + ), + ); + } + + return Card( + margin: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(24.0), + child: LayoutBuilder( + builder: (context, constraints) { + final isNarrow = constraints.maxWidth < 800; + return SingleChildScrollView( + child: isNarrow + ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _imageWidget(dto), + const SizedBox(height: 32), + _contentWidget(dto), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _imageWidget(dto), + const SizedBox(width: 32), + _contentWidget(dto), + ], + ), + ); + }, + ), + ), + ); + } + + Widget _imageWidget(PictureDto dto) { + return Image.memory( + base64Decode(dto.image), + fit: BoxFit.contain, + ); + } + + Widget _contentWidget(PictureDto dto) { + final labelStyle = TextStyle( + fontWeight: FontWeight.normal, + fontSize: 16, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.primaryTextLabelColor, + ); + + final contentStyle = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.secondaryTextLabelColor, + ); + + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + "INFORMATIONEN", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 44, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.primaryTextLabelColor, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Text( + "APOTHEKE", + style: labelStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + "Name of apotheke", + style: contentStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Text( + "KUNDENNUMMER", + style: labelStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + "123445587474873", + style: contentStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Text( + "DATUM", + style: labelStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + _dateFormat.format(dto.pictureDate), + style: contentStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 20.0), + child: Text( + "KOMMENTAR", + style: labelStyle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Container( + width: 300, + height: 150, + decoration: BoxDecoration( + border: Border.all(color: _generalStyle.secondaryTextLabelColor.withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + ), + child: Scrollbar( + controller: _commentScrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _commentScrollController, + padding: const EdgeInsets.all(8.0), + child: Text( + dto.comment ?? "", + style: contentStyle, + ), + ), + ), + ), + ), + ]); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/pages/login/login_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/login/login_widget.dart index 3bd35a3..b3d9d33 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/login/login_widget.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/login/login_widget.dart @@ -6,9 +6,7 @@ import 'package:go_router/go_router.dart'; import 'package:fotodocumentation/controller/login_controller.dart'; import 'package:fotodocumentation/dto/jwt_token_pair_dto.dart'; import 'package:fotodocumentation/l10n/app_localizations.dart'; -import 'package:fotodocumentation/pages/ui_utils/component/general_submit_widget.dart'; -import 'package:fotodocumentation/pages/ui_utils/header_utils.dart'; -import 'package:fotodocumentation/pages/ui_utils/modern_app_bar.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; import 'package:fotodocumentation/utils/di_container.dart'; import 'package:fotodocumentation/utils/login_credentials.dart'; @@ -20,9 +18,9 @@ class LoginWidget extends StatefulWidget { } class _LoginWidgetState extends State { - HeaderUtils get _headerUtils => DiContainer.get(); LoginController get _loginController => DiContainer.get(); LoginCredentials get _loginCredentials => DiContainer.get(); + GeneralStyle get _generalStyle => DiContainer.get(); final GlobalKey _formKey = GlobalKey(); @@ -43,26 +41,13 @@ class _LoginWidgetState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: ModernAppBar( - title: _headerUtils.titleWidget("Login title"), - actions: [], - ), body: _body(context), ); } Widget _body(BuildContext context) { return Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.grey[50]!, - Colors.white, - ], - ), - ), + color: _generalStyle.pageBackgroundColor, child: _content(context), ); } @@ -81,49 +66,116 @@ class _LoginWidgetState extends State { }, child: ListView( children: [ - Card( - elevation: 4, - margin: EdgeInsets.zero, - clipBehavior: Clip.antiAlias, + Center( child: Padding( - padding: const EdgeInsets.all(30.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - key: Key("username"), - controller: _usernameController, - decoration: InputDecoration( - border: UnderlineInputBorder(), - labelText: AppLocalizations.of(context)!.loginUsernameTFLabel, - ), + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Image.asset( + 'assets/images/logo.png', + height: 120, + ), + ), + ), + Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 500), + child: Card( + elevation: 0, + margin: EdgeInsets.zero, + clipBehavior: Clip.antiAlias, + child: Padding( + padding: const EdgeInsets.all(70.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Text( + AppLocalizations.of(context)!.loginTitle, + style: TextStyle( + fontWeight: FontWeight.bold, + color: _generalStyle.primaryTextLabelColor, + fontFamily: _generalStyle.fontFamily, + fontSize: 50, + ), + ), + ), + const SizedBox(height: 40), + if (_error != null) ...[ + Center( + child: Text( + _error!, + style: TextStyle( + color: _generalStyle.errorColor, + fontWeight: FontWeight.bold, + fontSize: 16, + fontFamily: _generalStyle.fontFamily, + ), + ), + ), + const SizedBox(height: 40), + ], + TextFormField( + key: Key("username"), + controller: _usernameController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: _error != null ? BorderSide(color: _generalStyle.errorColor) : BorderSide(), + ), + enabledBorder: _error != null + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: _generalStyle.errorColor), + ) + : null, + labelText: AppLocalizations.of(context)!.loginUsernameTFLabel.toUpperCase(), + labelStyle: TextStyle(color: _generalStyle.primaryTextLabelColor, fontFamily: _generalStyle.fontFamily), + floatingLabelBehavior: FloatingLabelBehavior.always, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + ), + const SizedBox(height: 25), + TextFormField( + key: Key("password"), + controller: _passwordController, + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: _error != null ? BorderSide(color: _generalStyle.errorColor) : BorderSide(), + ), + enabledBorder: _error != null + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: _generalStyle.errorColor), + ) + : null, + labelText: AppLocalizations.of(context)!.loginPasswordTFLabel.toUpperCase(), + labelStyle: TextStyle(color: _generalStyle.primaryTextLabelColor, fontFamily: _generalStyle.fontFamily), + floatingLabelBehavior: FloatingLabelBehavior.always, + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + ), + ), + const SizedBox(height: 25), + Center( + child: ElevatedButton( + key: Key("SubmitWidgetButton"), + style: ElevatedButton.styleFrom( + backgroundColor: _generalStyle.primaryButtonBackgroundColor, + foregroundColor: _generalStyle.primaryButtonTextColor, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20), + ), + onPressed: () async => await _actionSubmit(context), + child: Text( + AppLocalizations.of(context)!.loginLoginButtonLabel, + style: TextStyle(fontWeight: FontWeight.bold, fontFamily: _generalStyle.fontFamily), + ), + ), + ), + const SizedBox(height: 30), + ], ), - const SizedBox(height: 10), - TextFormField( - key: Key("password"), - controller: _passwordController, - obscureText: true, - decoration: InputDecoration( - border: UnderlineInputBorder(), - labelText: AppLocalizations.of(context)!.loginPasswordTFLabel, - ), - ), - const SizedBox(height: 10), - if (_error != null) ...[ - Text( - _error!, - style: const TextStyle(color: Colors.red), - ), - const SizedBox(height: 10), - ], - GeneralSubmitWidget( - key: const Key("submit"), - onSelect: () async => await _actionSubmit(context), - title: AppLocalizations.of(context)!.loginLoginButtonLabel, - ), - const SizedBox(height: 30), - ], + ), ), ), ), @@ -142,7 +194,7 @@ class _LoginWidgetState extends State { JwtTokenPairDto? jwtTokenPairDto = authenticateReply.jwtTokenPairDto; if (jwtTokenPairDto == null) { - setState(() => _error = "Error message"); + setState(() => _error = AppLocalizations.of(context)!.loginErrorMessage); return; } diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/page_header_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/page_header_widget.dart index 2203e39..6204f16 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/page_header_widget.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/page_header_widget.dart @@ -1,65 +1,54 @@ import 'package:flutter/material.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; +import 'package:fotodocumentation/utils/di_container.dart'; class PageHeaderWidget extends StatelessWidget { - final IconData iconData; final String text; final String subText; - final Color? iconColor; - const PageHeaderWidget({super.key, this.iconData = Icons.business, required this.text, this.subText = "", this.iconColor}); + const PageHeaderWidget({super.key, required this.text, this.subText = ""}); + + GeneralStyle get _generalStyle => DiContainer.get(); @override Widget build(BuildContext context) { - final color = iconColor ?? Theme.of(context).colorScheme.primary; - return Card( - elevation: 2, - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: Colors.grey[300]!, - width: 1, - ), - ), - child: Padding( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withAlpha(51), - shape: BoxShape.circle, - ), - child: Icon( - iconData, - size: 32, - color: color, - ), - ), - const SizedBox(width: 16), - Text( - text, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ], - ), - if (subText.isNotEmpty) ...[ - const SizedBox(height: 16), + return Padding( + padding: const EdgeInsets.only(top:24.0, bottom: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ Text( - subText, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Colors.grey[600], - ), + key: Key("PageHeaderTextHeadline"), + text, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 50, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.primaryTextLabelColor, + ), + ), + const Spacer(), + Image.asset( + 'assets/images/logo.png', + height: 48, ), ], + ), + if (subText.isNotEmpty) ...[ + const SizedBox(height: 16), + Text( + key: Key("PageHeaderTextSubHeadline"), + subText, + style: TextStyle( + fontSize: 16, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.secondaryTextLabelColor, + ), + ), ], - ), + ], ), ); } diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_card_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_card_widget.dart deleted file mode 100644 index 2f4c052..0000000 --- a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_card_widget.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fotodocumentation/l10n/app_localizations.dart'; - -class SearchBarCardWidget extends StatefulWidget { - final TextEditingController searchController; - final Function(String) onSearch; - const SearchBarCardWidget({super.key, required this.searchController, required this.onSearch}); - - @override - State createState() => _SearchBarCardWidgetState(); -} - -class _SearchBarCardWidgetState extends State { - @override - Widget build(BuildContext context) { - return Card( - elevation: 2, - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: Colors.grey[300]!, - width: 1, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: TextField( - key: Key("Search_text_field"), - controller: widget.searchController, - textAlignVertical: TextAlignVertical.center, - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.searchTFHint, - border: InputBorder.none, - prefixIcon: const Icon(Icons.search, size: 28), - contentPadding: EdgeInsets.zero, - isDense: true, - suffixIcon: InkWell( - key: Key("Search_text_clear_button"), - onTap: () => _actionClear(), - child: const Icon( - Icons.close, - color: Colors.black, - ), - ) - ), - onSubmitted: (_) => _actionSubmit(), - ), - ), - const SizedBox(width: 8), - ElevatedButton.icon( - key: Key("Search_text_button"), - onPressed: _actionSubmit, - icon: const Icon(Icons.search, size: 18), - label: Text(AppLocalizations.of(context)!.searchButtonLabel), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - ), - ], - ), - ), - ); - } - - void _actionSubmit() { - widget.onSearch(widget.searchController.text); - } - - void _actionClear() { - widget.searchController.text = ""; - widget.onSearch(widget.searchController.text); - } -} diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_widget.dart new file mode 100644 index 0000000..0ee5f71 --- /dev/null +++ b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/search_bar_widget.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fotodocumentation/l10n/app_localizations.dart'; +import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; +import 'package:fotodocumentation/utils/di_container.dart'; + +class SearchBarWidget extends StatefulWidget { + final TextEditingController searchController; + final Function(String) onSearch; + const SearchBarWidget({super.key, required this.searchController, required this.onSearch}); + + @override + State createState() => _SearchBarWidgetState(); +} + +class _SearchBarWidgetState extends State { + GeneralStyle get _generalStyle => DiContainer.get(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0, vertical: 16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: TextField( + key: Key("Search_text_field"), + controller: widget.searchController, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + hint: Text( + AppLocalizations.of(context)!.searchTFHint, + style: TextStyle( + fontSize: 16, + fontFamily: _generalStyle.fontFamily, + color: _generalStyle.secondaryTextLabelColor.withValues(alpha: 0.5), + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), + borderSide: BorderSide(color: _generalStyle.secondaryTextLabelColor), + ), + prefixIcon: Icon(Icons.search, size: 30, color: _generalStyle.secondaryWidgetBackgroundColor,), + contentPadding: EdgeInsets.zero, + isDense: true, + ), + onSubmitted: (_) => _actionSubmit(), + ), + ), + const SizedBox(width: 8), + ], + ), + ); + } + + void _actionSubmit() { + widget.onSearch(widget.searchController.text); + } +} diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/text_input_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/text_input_widget.dart deleted file mode 100644 index e09551f..0000000 --- a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/text_input_widget.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fotodocumentation/l10n/app_localizations.dart'; - -import 'package:provider/provider.dart'; - -class TextInputWidget extends StatelessWidget { - final String labelText; - final bool required; - final bool obscureText; - final bool readOnly; - final Function? onTap; - const TextInputWidget({super.key, required this.labelText, this.required = false, this.obscureText = false, this.readOnly = false, this.onTap}); - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, controller, child) { - return TextFormField( - readOnly: readOnly, - obscureText: obscureText, - controller: controller, - decoration: InputDecoration( - border: const UnderlineInputBorder(), - labelText: labelText, - ), - validator: (String? value) => required && (value == null || value.isEmpty) ? AppLocalizations.of(context)!.textInputWidgetValidatorText : null, - onTap: () => onTap?.call(), - ); - }); - } -} - -class TextMultiInputWidget extends StatelessWidget { - final String labelText; - final bool required; - final bool obscureText; - final bool readOnly; - final Function? onTap; - final int maxLines; - const TextMultiInputWidget({super.key, required this.labelText, this.required = false, this.obscureText = false, this.readOnly = false, this.maxLines = 6, this.onTap}); - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, controller, child) { - return TextFormField( - readOnly: readOnly, - minLines: 3, // Set this - maxLines: maxLines, // and this - keyboardType: TextInputType.multiline, - obscureText: obscureText, - controller: controller, - decoration: InputDecoration( - border: const UnderlineInputBorder(), - labelText: labelText, - ), - validator: (String? value) => required && (value == null || value.isEmpty) ? AppLocalizations.of(context)!.textInputWidgetValidatorText : null, - onTap: () => onTap?.call(), - ); - }); - } -} diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/dialog/dialog_result.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/dialog/dialog_result.dart deleted file mode 100644 index 682a170..0000000 --- a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/dialog/dialog_result.dart +++ /dev/null @@ -1,11 +0,0 @@ -class DialogResult { - final DialogResultType type; - final T? dto; - - const DialogResult({required this.type, this.dto}); -} - -enum DialogResultType { - create, - add; -} \ No newline at end of file diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/general_style.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/general_style.dart index 07bb923..99da76d 100644 --- a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/general_style.dart +++ b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/general_style.dart @@ -1,36 +1,55 @@ import 'package:flutter/material.dart'; -import 'package:pinput/pinput.dart'; - abstract interface class GeneralStyle { - PinTheme get pinTheme; - ButtonStyle get elevatedButtonStyle; ButtonStyle get roundedButtonStyle; + + Color get primaryTextLabelColor; + Color get secondaryTextLabelColor; + + Color get primaryButtonBackgroundColor; + Color get primaryButtonTextColor; + + Color get secondaryWidgetBackgroundColor; + + Color get pageBackgroundColor; + + Color get errorColor; + + String get fontFamily; } class GeneralStyleImpl implements GeneralStyle { static final ButtonStyle _elevatedButtonStyle = ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)); static final ButtonStyle _roundedButtonStyle = ElevatedButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(8)); - @override - PinTheme get pinTheme => _getPinTheme(); - @override ButtonStyle get elevatedButtonStyle => _elevatedButtonStyle; @override ButtonStyle get roundedButtonStyle => _roundedButtonStyle; - PinTheme _getPinTheme() { - return PinTheme( - width: 56, - height: 56, - textStyle: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(8), - ), - ); - } + @override + Color get primaryTextLabelColor => const Color(0xFF0045FF); + + @override + Color get secondaryTextLabelColor => const Color(0xFF2F2F2F); + + @override + Color get primaryButtonBackgroundColor => const Color(0xFF0045FF); + + @override + Color get secondaryWidgetBackgroundColor => const Color(0xFF001689); + + @override + Color get primaryButtonTextColor => Colors.white; + + @override + Color get pageBackgroundColor => const Color(0xFFF5F5F5); + + @override + Color get errorColor => const Color(0xFFFF0000); + + @override + String get fontFamily => 'Panton'; } diff --git a/hartmann-foto-documentation-frontend/lib/utils/di_container.dart b/hartmann-foto-documentation-frontend/lib/utils/di_container.dart index 8a43e06..da1e34d 100644 --- a/hartmann-foto-documentation-frontend/lib/utils/di_container.dart +++ b/hartmann-foto-documentation-frontend/lib/utils/di_container.dart @@ -1,4 +1,6 @@ +import 'package:fotodocumentation/controller/customer_controller.dart'; import 'package:fotodocumentation/controller/login_controller.dart'; +import 'package:fotodocumentation/controller/picture_controller.dart'; import 'package:fotodocumentation/pages/ui_utils/dialog/snackbar_utils.dart'; import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; import 'package:fotodocumentation/pages/ui_utils/header_utils.dart'; @@ -26,6 +28,8 @@ class DiContainer { DiContainer.instance.put(UrlUtils, UrlUtilsImpl()); DiContainer.instance.put(SnackbarUtils, SnackbarUtilsImpl()); DiContainer.instance.put(LoginController, LoginControllerImpl()); + DiContainer.instance.put(CustomerController, CustomerControllerImpl()); + DiContainer.instance.put(PictureController, PictureControllerImpl()); } void put(Type key, T object) { diff --git a/hartmann-foto-documentation-frontend/lib/utils/extensions.dart b/hartmann-foto-documentation-frontend/lib/utils/extensions.dart index 9a99099..978a955 100644 --- a/hartmann-foto-documentation-frontend/lib/utils/extensions.dart +++ b/hartmann-foto-documentation-frontend/lib/utils/extensions.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' show Colors, Color; +import 'package:flutter/material.dart' show Color; extension HexColor on Color { /// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#". @@ -9,21 +9,3 @@ extension HexColor on Color { return Color(int.parse(buffer.toString(), radix: 16)); } } - -extension RiskColor on Color { - static const Color noRisk = Colors.transparent; - static final Color lowRisk = HexColor.fromHex("#FFFF00"); - static final Color mediumRisk = HexColor.fromHex("#FF9000"); - static final Color highRisk = HexColor.fromHex("#FF4000"); - - static Color colorForRisk(int value) { - if (value == 1) { - return lowRisk; - } else if (value == 2) { - return mediumRisk; - } else if (value == 3) { - return highRisk; - } - return noRisk; - } -} diff --git a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart index f92dc22..37d99d3 100644 --- a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart +++ b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart @@ -1,7 +1,9 @@ // needed for web horizontal scroll behavior import 'package:flutter/material.dart'; import 'package:fotodocumentation/main.dart'; +import 'package:fotodocumentation/pages/customer/customer_list_widget.dart'; import 'package:fotodocumentation/pages/customer/customer_widget.dart'; +import 'package:fotodocumentation/pages/customer/picture_widget.dart'; import 'package:fotodocumentation/pages/login/login_widget.dart'; import 'package:fotodocumentation/utils/di_container.dart'; import 'package:fotodocumentation/utils/login_credentials.dart'; @@ -14,6 +16,8 @@ class GlobalRouter { static final GlobalKey skillEditorNavigatorKey = GlobalKey(debugLabel: 'skillEditor'); static final String pathHome = "/home"; + static final String pathCustomer = "/customer"; + static final String pathPicture = "/picture"; static final String pathLogin = "/login"; static final GoRouter router = createRouter(pathHome); @@ -33,7 +37,23 @@ class GlobalRouter { ), GoRoute( path: pathHome, - builder: (context, state) => CustomerWidget(), + builder: (context, state) => CustomerListWidget(), + ), + GoRoute( + path: "$pathCustomer/:id", + builder: (context, state) { + var idStr = state.pathParameters['id']; + var id = idStr == null ? null : int.tryParse(idStr); + return CustomerWidget(customerId: id ?? -1); + }, + ), + GoRoute( + path: "$pathPicture/:id", + builder: (context, state) { + var idStr = state.pathParameters['id']; + var id = idStr == null ? null : int.tryParse(idStr); + return PictureWidget(id: id ?? -1); + }, ), ], redirect: (context, state) { diff --git a/hartmann-foto-documentation-frontend/lib/utils/global_stack.dart b/hartmann-foto-documentation-frontend/lib/utils/global_stack.dart deleted file mode 100644 index a206d9e..0000000 --- a/hartmann-foto-documentation-frontend/lib/utils/global_stack.dart +++ /dev/null @@ -1,16 +0,0 @@ -class GlobalStack { - final _list = []; - - void push(T value) => _list.add(value); - - T pop() => _list.removeLast(); - - T peek() => _list.last; - - bool get isEmpty => _list.isEmpty; - - bool get isNotEmpty => _list.isNotEmpty; - - @override - String toString() => _list.toString(); -} diff --git a/hartmann-foto-documentation-frontend/lib/utils/password_utils.dart b/hartmann-foto-documentation-frontend/lib/utils/password_utils.dart deleted file mode 100644 index 6ae9d61..0000000 --- a/hartmann-foto-documentation-frontend/lib/utils/password_utils.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:basic_utils/basic_utils.dart' show StringUtils; - -abstract interface class PasswordUtils { - String create(); -} - -class PasswordUtilsImpl implements PasswordUtils { - @override - String create() { - return StringUtils.generateRandomString(8, special: false); - } -} diff --git a/hartmann-foto-documentation-frontend/pubspec.yaml b/hartmann-foto-documentation-frontend/pubspec.yaml index 7e8743c..dcb5fde 100644 --- a/hartmann-foto-documentation-frontend/pubspec.yaml +++ b/hartmann-foto-documentation-frontend/pubspec.yaml @@ -94,6 +94,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/theme/appainter_theme.json + - assets/images/logo.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/hartmann-foto-documentation-frontend/test/testing/test_utils.dart b/hartmann-foto-documentation-frontend/test/testing/test_utils.dart index d9ed5fa..45852bc 100644 --- a/hartmann-foto-documentation-frontend/test/testing/test_utils.dart +++ b/hartmann-foto-documentation-frontend/test/testing/test_utils.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:fotodocumentation/controller/customer_controller.dart'; import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:fotodocumentation/l10n/app_localizations.dart'; import 'package:fotodocumentation/pages/ui_utils/dialog/snackbar_utils.dart'; import 'package:fotodocumentation/pages/ui_utils/header_utils.dart'; import 'package:fotodocumentation/utils/login_credentials.dart'; -import 'package:fotodocumentation/utils/password_utils.dart'; import 'package:fotodocumentation/utils/jwt_token_storage.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:fotodocumentation/utils/global_router.dart'; @@ -58,10 +58,10 @@ Future pumpAppConfig(WidgetTester tester, String initialLocation) async { // dart run build_runner build @GenerateMocks([ LoginCredentials, + CustomerController, HeaderUtils, - PasswordUtils, SnackbarUtils, JwtTokenStorage, - http.Client + http.Client, ]) void main() {} diff --git a/hartmann-foto-documentation-frontend/test/testing/test_utils.mocks.dart b/hartmann-foto-documentation-frontend/test/testing/test_utils.mocks.dart index c5115ed..b8cff77 100644 --- a/hartmann-foto-documentation-frontend/test/testing/test_utils.mocks.dart +++ b/hartmann-foto-documentation-frontend/test/testing/test_utils.mocks.dart @@ -14,7 +14,6 @@ import 'package:fotodocumentation/pages/ui_utils/dialog/snackbar_utils.dart' import 'package:fotodocumentation/pages/ui_utils/header_utils.dart' as _i7; import 'package:fotodocumentation/utils/jwt_token_storage.dart' as _i10; import 'package:fotodocumentation/utils/login_credentials.dart' as _i4; -import 'package:fotodocumentation/utils/password_utils.dart' as _i8; import 'package:http/http.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i5; @@ -162,30 +161,6 @@ class MockHeaderUtils extends _i1.Mock implements _i7.HeaderUtils { ) as _i2.Widget); } -/// A class which mocks [PasswordUtils]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPasswordUtils extends _i1.Mock implements _i8.PasswordUtils { - MockPasswordUtils() { - _i1.throwOnMissingStub(this); - } - - @override - String create() => (super.noSuchMethod( - Invocation.method( - #create, - [], - ), - returnValue: _i5.dummyValue( - this, - Invocation.method( - #create, - [], - ), - ), - ) as String); -} - /// A class which mocks [SnackbarUtils]. /// /// See the documentation for Mockito's code generation for more information. diff --git a/hartmann-foto-documentation-web/src/main/webapp/WEB-INF/web.xml b/hartmann-foto-documentation-web/src/main/webapp/WEB-INF/web.xml index 5c7886d..54a191d 100644 --- a/hartmann-foto-documentation-web/src/main/webapp/WEB-INF/web.xml +++ b/hartmann-foto-documentation-web/src/main/webapp/WEB-INF/web.xml @@ -5,6 +5,7 @@ Secure /api/customer + /api/picture GET POST