From 746294d6406ee9aee2aa8cf90c48996307120364 Mon Sep 17 00:00:00 2001
From: verboomp
Date: Mon, 23 Feb 2026 16:19:57 +0100
Subject: [PATCH] added download from excel and zip in frontend
---
.../core/utils/ExcelUtils.java | 22 +++--
.../core/utils/ImageHandler.java | 4 +-
.../core/utils/PdfUtils.java | 6 +-
.../core/utils/QuestionnaireJsonParser.java | 17 ++--
.../core/utils/ExcelUtilsTest.java | 2 +-
.../utils/QuestionnaireJsonParserTest.java | 4 +-
.../assets/fonts/MyFlutterApp.ttf | Bin 0 -> 1932 bytes
.../assets/images/icon-excel.png | Bin 0 -> 506 bytes
.../questionnaire_customer_controller.dart | 8 ++
.../foto/customer/foto_picture_widget.dart | 38 ++------
.../questionnaire_customer_list_widget.dart | 81 ++++++++++++++++-
.../questionnaire_customer_widget.dart | 86 ++++++++++--------
.../ui_utils/component/evaluation_circle.dart | 33 +++++++
.../lib/utils/global_router.dart | 4 +-
.../pubspec.yaml | 6 ++
15 files changed, 221 insertions(+), 90 deletions(-)
create mode 100644 hartmann-foto-documentation-frontend/assets/fonts/MyFlutterApp.ttf
create mode 100644 hartmann-foto-documentation-frontend/assets/images/icon-excel.png
create mode 100644 hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/evaluation_circle.dart
diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtils.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtils.java
index 0187100..8ce4a7b 100644
--- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtils.java
+++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtils.java
@@ -16,6 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import marketing.heyday.hartmann.fotodocumentation.core.model.Questionnaire;
import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer;
+import marketing.heyday.hartmann.fotodocumentation.core.utils.QuestionnaireJsonParser.MatrixAnswer;
import marketing.heyday.hartmann.fotodocumentation.core.utils.QuestionnaireJsonParser.QuestionJsonObj;
/**
@@ -33,7 +34,9 @@ public class ExcelUtils {
public Optional create(QuestionnaireCustomer customer, List questionnaires) {
LOG.debug("Create excel file for customer " + customer);
- // TODO: implement excel export
+ if(customer == null || questionnaires.isEmpty()) {
+ return Optional.empty();
+ }
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); XSSFWorkbook workbook = new XSSFWorkbook()) {
@@ -176,12 +179,7 @@ public class ExcelUtils {
if (!answer.selected()) {
continue;
}
- int index = 0;
- for (int i = 0; i < answerOpts.size(); i++) {
- if (answer.answer().equalsIgnoreCase(answerOpts.get(i).answer())) {
- index = i;
- }
- }
+ int index = getMatrixAnswerIndex(answerOpts, answer);
var cell = questionRow.createCell(1 + index);
cell.setCellType(CellType.BOOLEAN);
cell.setCellValue(answer.selected());
@@ -190,4 +188,14 @@ public class ExcelUtils {
}
return count;
}
+
+ private int getMatrixAnswerIndex(List answerOpts, MatrixAnswer answer) {
+ int index = 0;
+ for (int i = 0; i < answerOpts.size(); i++) {
+ if (answer.answer().equalsIgnoreCase(answerOpts.get(i).answer())) {
+ index = i;
+ }
+ }
+ return index;
+ }
}
diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageHandler.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageHandler.java
index e3af199..6e8f419 100644
--- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageHandler.java
+++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ImageHandler.java
@@ -29,17 +29,17 @@ import com.drew.metadata.exif.ExifIFD0Directory;
*/
public interface ImageHandler {
- static final Log LOG = LogFactory.getLog(ImageHandler.class);
+ Log LOG = LogFactory.getLog(ImageHandler.class);
/**
* Reads image bytes and returns a BufferedImage with correct EXIF orientation applied.
*/
default BufferedImage readImageWithCorrectOrientation(byte[] imageBytes) throws IOException {
- int orientation = getExifOrientation(imageBytes);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
if (image == null) {
throw new IOException("Failed to read image from byte array");
}
+ int orientation = getExifOrientation(imageBytes);
return applyOrientation(image, orientation);
}
diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/PdfUtils.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/PdfUtils.java
index 9cde273..1d40f6f 100644
--- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/PdfUtils.java
+++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/PdfUtils.java
@@ -228,7 +228,9 @@ public class PdfUtils implements ImageHandler {
// Return remaining text starting from current word
StringBuilder remaining = new StringBuilder(line);
for (int i = wordIndex; i < words.length; i++) {
- if (!remaining.isEmpty()) remaining.append(" ");
+ if (!remaining.isEmpty()) {
+ remaining.append(" ");
+ }
remaining.append(words[i]);
}
return remaining.toString();
@@ -289,7 +291,7 @@ public class PdfUtils implements ImageHandler {
cs.fill();
}
}
-
+
private void drawCircle(PDPageContentStream cs, float cx, float cy, float r) throws IOException {
float k = 0.5523f; // Bezier approximation for circle
cs.moveTo(cx - r, cy);
diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParser.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParser.java
index cd5a4a8..e055a0b 100644
--- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParser.java
+++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParser.java
@@ -12,6 +12,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import jakarta.json.*;
+import jakarta.json.JsonValue.ValueType;
import jakarta.json.stream.JsonParsingException;
/**
@@ -74,10 +75,10 @@ public class QuestionnaireJsonParser {
public record QuestionJsonObj(String id, String title, int order, String type, String data) {
public List getMatrixAnswer() {
- return getValue("questions", () -> null, (questions) -> {
+ return getValue("questions", () -> null, matrixQuestions -> {
var retVal = new ArrayList();
- for (var question : questions) {
+ for (var question : matrixQuestions) {
var questionObj = question.asJsonObject();
var id = questionObj.getString("id");
var title = questionObj.getString("title");
@@ -101,11 +102,15 @@ public class QuestionnaireJsonParser {
}
public Integer getNumberAnswer() {
- return getValue(() -> null, (answers) -> {
+ return getValue(() -> null, answers -> {
for (var answer : answers) {
var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) {
- return answerObj.getInt("answer");
+ var value = answerObj.get("answer");
+ if (value.getValueType() == ValueType.NUMBER) {
+ return answerObj.getInt("answer");
+ }
+ return Integer.parseInt(answerObj.getString("answer"));
}
}
return null;
@@ -113,7 +118,7 @@ public class QuestionnaireJsonParser {
}
public String getSingleAnswer() {
- return getValue(() -> "", (answers) -> {
+ return getValue(() -> "", answers -> {
for (var answer : answers) {
var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) {
@@ -125,7 +130,7 @@ public class QuestionnaireJsonParser {
}
public List getMultiAnswer() {
- return getValue(() -> List.of(), (answers) -> {
+ return getValue(() -> List.of(), answers -> {
List retVal = new ArrayList<>();
for (var answer : answers) {
var answerObj = answer.asJsonObject();
diff --git a/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtilsTest.java b/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtilsTest.java
index bb7ad0a..bb959d0 100644
--- a/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtilsTest.java
+++ b/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/ExcelUtilsTest.java
@@ -246,7 +246,7 @@ class ExcelUtilsTest implements TestAble {
private Questionnaire createQuestionnaire(Date date, String questions) {
return new Questionnaire.Builder()
.questionnaireDate(date)
- .questions(QuestionnaireJsonParserTest.testJson1)
+ .questions(QuestionnaireJsonParserTest.TEST_JSON_1)
.build();
}
}
diff --git a/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParserTest.java b/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParserTest.java
index 41f9143..ea46739 100644
--- a/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParserTest.java
+++ b/hartmann-foto-documentation-app/src/test/java/marketing/heyday/hartmann/fotodocumentation/core/utils/QuestionnaireJsonParserTest.java
@@ -21,14 +21,14 @@ public class QuestionnaireJsonParserTest {
@Test
public void testJson1() {
var parser = new QuestionnaireJsonParser();
- boolean retVal = parser.parse(testJson1);
+ boolean retVal = parser.parse(TEST_JSON_1);
assertTrue(retVal);
var questions = parser.getQuestions();
assertEquals(10, questions.size());
}
- public static final String testJson1 = """
+ public static final String TEST_JSON_1 = """
[
{
"id": "question1",
diff --git a/hartmann-foto-documentation-frontend/assets/fonts/MyFlutterApp.ttf b/hartmann-foto-documentation-frontend/assets/fonts/MyFlutterApp.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..cb03b5be78144c41e066989f526da6840a2e8590
GIT binary patch
literal 1932
zcmd^A&u<$=6n?Yo*d-|eV@%p4hfSQ=j+)wzUE2^stmDF?&_#IdS4a6#e%LINR5
z%=@w9H8CQZptne-#O9T&&nmYTuM@E+!Imyxi6_^U7dD{pL*K5nRqMCt=O07=9b(n>
zL9N;U>Q^GU3i~$=O|8amy}k*27m{g!@U6*Lfxmz(G}?CGrvnlD8Ph4`^Mgeg?W?sBP`e2L}-%PZT~0%k0`m8^a%oe6PTMfsnggJZ@ngLgzEDW^_mn
z12*~LyX>_`*8Zdk*Y}sN1K)E;cRg;&L%9g`6iH$`KS`qJ$>Zpp9mhj-?C40*VG^`X
z(sPBEN;Ijfwn5Xv-?h7YUCi`mUB&rAgCDmtFpRwau<~c@fW{E`y2LxS@2>1FtuSOD
z(U=VO0b{GSQKj6FhHgK0QOo;q6W!ny%X`i?oQ*e4vS`h^>|hqp3E=jmy}FNFCY_xil&
zSUQ!=r2V1!a3uT;o16Cr{UQH_btyH+Lg`f6kzgtt@`WOyDPJm^no32cQbF*+P!J;T
z1paZ{Pl7H7%vrE^lSCQg(IgJuu*nCGt^GU>W?5z#d>t;4yq{-VoRe{)WKgbdKH;c!C1-DZi1PhY)9PVj8iE*T2@i3_jI*0
zN{!-et`32TxPkPJ{69tu2NuO=C!k`6|t9(FVwOBZ?$QiFxFHr^li
zufS?h%vsy`T5u1}aJ!Djyl=aND20FRzKGl`+z$=8^+rEAC{=~Py8s_5XvHc;U|^(e@h+gdoM0{@uPR~zgq#qpd}
cKfc$_;Qtv98WNKjzP0=&o!9ym4gZUO1KeyU-~a#s
literal 0
HcmV?d00001
diff --git a/hartmann-foto-documentation-frontend/assets/images/icon-excel.png b/hartmann-foto-documentation-frontend/assets/images/icon-excel.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b281aecc08b0b34e9d2b436ad43feeae356c249
GIT binary patch
literal 506
zcmVomhS_Gi?(17@eQuCuH3KO4*pCJXp^0g3Q3~D9p*yyAetGxQQ%*Z81$c
z*n8)3L_X97EKRmOQBc!0ah7_nWkVU>Z)&uLf1*Dj5=!?*4?992cV(~C51-f1!tksY
zs@i5Uz)VEbK+8|{{u+bsBWbW9rS!tl5ao9VsqRTo;>A3qT;LK6m*;r#Aq4}WzbT`r
zvyIth_#R&P$h0eT=BN+>_bD3=EnUhF*nbNUgQ!N*Y=%qUWJ5Bdu)+*9afD)Me@zw<
zTcb={Vcb{^xQu%XAzx@ZfiRenEJN`pJl8vGo(FCLQ)N+V>g)vvmA9hh5v5t>C@sy4
wSZ0Pklz9|=p!yg!YEi)UfKQ7gJ^EEX0Tm^fDhGC0+5i9m07*qoM6N<$f^@{k7ytkO
literal 0
HcmV?d00001
diff --git a/hartmann-foto-documentation-frontend/lib/controller/questionnaire_customer_controller.dart b/hartmann-foto-documentation-frontend/lib/controller/questionnaire_customer_controller.dart
index 6aebfde..a961b35 100644
--- a/hartmann-foto-documentation-frontend/lib/controller/questionnaire_customer_controller.dart
+++ b/hartmann-foto-documentation-frontend/lib/controller/questionnaire_customer_controller.dart
@@ -7,6 +7,8 @@ abstract interface class QuestionnaireCustomerController {
Future get({required int id});
Future> export({required int customerId, int? questionnaireId});
+
+ Future> exportAll();
}
class QuestionnaireCustomerControllerImpl extends BaseController implements QuestionnaireCustomerController {
@@ -39,4 +41,10 @@ class QuestionnaireCustomerControllerImpl extends BaseController implements Ques
}
return runGetBytesWithAuth(uriStr);
}
+
+ @override
+ Future> exportAll() {
+ String uriStr = '${uriUtils.getBaseUrl()}$path/exportall';
+ return runGetBytesWithAuth(uriStr);
+ }
}
diff --git a/hartmann-foto-documentation-frontend/lib/pages/foto/customer/foto_picture_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/foto/customer/foto_picture_widget.dart
index 3f28b76..dd867fd 100644
--- a/hartmann-foto-documentation-frontend/lib/pages/foto/customer/foto_picture_widget.dart
+++ b/hartmann-foto-documentation-frontend/lib/pages/foto/customer/foto_picture_widget.dart
@@ -7,6 +7,7 @@ import 'package:fotodocumentation/dto/picture_dto.dart';
import 'package:fotodocumentation/l10n/app_localizations.dart';
import 'package:fotodocumentation/pages/ui_utils/component/customer_back_button.dart';
import 'package:fotodocumentation/pages/foto/customer/foto_picture_fullscreen_dialog.dart';
+import 'package:fotodocumentation/pages/ui_utils/component/evaluation_circle.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';
@@ -306,25 +307,28 @@ class _FotoPictureWidgetState extends State {
Row(
mainAxisSize: MainAxisSize.min,
children: [
- _evaluationCircle(
- key: const Key("evaluation_good"),
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_good"),
color: _generalStyle.evaluationGoodColor,
value: 1,
selected: dto.evaluation == 1,
+ doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
),
const SizedBox(width: 12),
- _evaluationCircle(
- key: const Key("evaluation_middle"),
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_middle"),
color: _generalStyle.evaluationMiddleColor,
value: 2,
selected: dto.evaluation == 2,
+ doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
),
const SizedBox(width: 12),
- _evaluationCircle(
- key: const Key("evaluation_bad"),
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_bad"),
color: _generalStyle.evaluationBadColor,
value: 3,
selected: dto.evaluation == 3,
+ doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
),
],
),
@@ -334,28 +338,6 @@ class _FotoPictureWidgetState extends State {
);
}
- Widget _evaluationCircle({
- required Key key,
- required Color color,
- required int value,
- required bool selected,
- }) {
- return InkWell(
- key: key,
- onTap: () async => _actionUpdateEvaluation(value),
- customBorder: const CircleBorder(),
- child: Container(
- width: 24,
- height: 24,
- decoration: BoxDecoration(
- color: color,
- shape: BoxShape.circle,
- border: selected ? Border.all(color: _generalStyle.secondaryWidgetBackgroundColor, width: 3) : null,
- ),
- ),
- );
- }
-
// Bottom navigation buttons
Widget _bottomNavigationWidget(List pictures, PictureDto selectedPicture) {
pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate));
diff --git a/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_list_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_list_widget.dart
index 824bd18..8363f46 100644
--- a/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_list_widget.dart
+++ b/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_list_widget.dart
@@ -9,6 +9,7 @@ import 'package:fotodocumentation/pages/ui_utils/component/search_bar_widget.dar
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/file_download.dart';
import 'package:fotodocumentation/utils/global_router.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
@@ -60,7 +61,6 @@ class _QuestionaireCustomerListWidgetState extends State actionSearch(text),
@@ -68,6 +68,16 @@ class _QuestionaireCustomerListWidgetState extends State _actionDownload(context),
+ iconAlignment: IconAlignment.end,
+ icon: Icon(
+ Icons.file_download_outlined,
+ color: _generalStyle.primaryButtonTextColor,
+ size: 24,
+ ),
+ label: Text(
+ "Alle herunterladen",
+ style: TextStyle(
+ fontFamily: _generalStyle.fontFamily,
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ color: _generalStyle.primaryButtonTextColor,
+ ),
+ ),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: _generalStyle.secondaryWidgetBackgroundColor,
+ elevation: 0,
+ padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
+ shape: const StadiumBorder(),
+ ),
+ );
+ }
+
Future actionSearch(String text) async {
_reloadData();
}
@@ -250,6 +288,47 @@ class _QuestionaireCustomerListWidgetState extends State _actionDownload(BuildContext context) async {
+ showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (context) => Dialog(
+ backgroundColor: Colors.white,
+ child: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const CircularProgressIndicator(),
+ const SizedBox(width: 16),
+ Text(
+ AppLocalizations.of(context)!.customerWidgetDownloadInProgress,
+ style: TextStyle(
+ fontFamily: _generalStyle.fontFamily,
+ fontSize: 16,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ try {
+ final bytes = await _questionnaireCustomerController.exportAll();
+ final fileName = 'download.zip';
+ if (context.mounted) {
+ Navigator.of(context).pop();
+ }
+ await downloadFile(bytes, fileName);
+ } catch (e) {
+ if (context.mounted) {
+ Navigator.of(context).pop();
+ }
+ rethrow;
+ }
+ }
/*
Widget _abcHeaderBar() {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
diff --git a/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_widget.dart b/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_widget.dart
index 9703d14..9478cab 100644
--- a/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_widget.dart
+++ b/hartmann-foto-documentation-frontend/lib/pages/questionnaire/customer/questionnaire_customer_widget.dart
@@ -7,6 +7,7 @@ import 'package:fotodocumentation/dto/questionnaire_dto.dart';
import 'package:fotodocumentation/l10n/app_localizations.dart';
import 'package:fotodocumentation/pages/questionnaire/customer/questionnaire_delete_dialog.dart';
import 'package:fotodocumentation/pages/ui_utils/component/customer_back_button.dart';
+import 'package:fotodocumentation/pages/ui_utils/component/evaluation_circle.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';
@@ -26,7 +27,7 @@ class QuestionaireCustomerWidget extends StatefulWidget {
class _QuestionaireCustomerWidgetState extends State {
QuestionnaireCustomerController get _customerController => DiContainer.get();
- QuestionnaireController get _pictureController => DiContainer.get();
+ QuestionnaireController get _questionnaireController => DiContainer.get();
GeneralStyle get _generalStyle => DiContainer.get();
late Future _dto;
@@ -155,16 +156,7 @@ class _QuestionaireCustomerWidgetState extends State
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.0,
children: [
- Expanded(
- flex: 1,
- child: Align(
- alignment: Alignment.centerLeft,
- child: Text(
- AppLocalizations.of(context)!.customerWidgetHeaderFoto,
- style: headerStyle,
- ),
- ),
- ),
+ const SizedBox(width: 45),
Expanded(
flex: 3,
child: Align(
@@ -209,7 +201,7 @@ class _QuestionaireCustomerWidgetState extends State
);
final dateStr = _dateFormat.format(questionnaireDto.questionnaireDate);
- final evaluationColor = _generalStyle.evaluationColor(value: questionnaireDto.evaluation);
+
return InkWell(
key: Key("table_row_${customerDto.id}"),
child: Padding(
@@ -219,23 +211,9 @@ class _QuestionaireCustomerWidgetState extends State
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 8.0,
children: [
- Expanded(
- flex: 1,
- child: Align(
- alignment: Alignment.centerLeft,
- child: ConstrainedBox(
- constraints: const BoxConstraints(maxWidth: 70, maxHeight: 70),
- child:
- Icon(Icons.abc),
- /*
- FIXME: remove me
- Image.network(
- headers: {cred.name: cred.value},
- pictureDto.thumbnailSizeUrl,
- fit: BoxFit.contain,
- ),*/
- ),
- ),
+ ConstrainedBox(
+ constraints: const BoxConstraints(minWidth: 45, maxWidth: 45),
+ child: Icon(IconData(0xE800, fontFamily: "MyFlutterApp")),
),
Expanded(
flex: 3,
@@ -248,14 +226,7 @@ class _QuestionaireCustomerWidgetState extends State
flex: 1,
child: Align(
alignment: Alignment.centerLeft,
- child: Container(
- width: 20,
- height: 20,
- decoration: BoxDecoration(
- color: evaluationColor,
- shape: BoxShape.circle,
- ),
- ),
+ child: _evalationWidget(questionnaireDto),
),
),
Expanded(
@@ -293,6 +264,37 @@ class _QuestionaireCustomerWidgetState extends State
);
}
+ Widget _evalationWidget(QuestionnaireDto dto) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_good"),
+ color: _generalStyle.evaluationGoodColor,
+ value: 1,
+ selected: dto.evaluation == 1,
+ doUpdate: (circle) async => _actionUpdateEvaluation(dto, circle.value),
+ ),
+ const SizedBox(width: 12),
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_middle"),
+ color: _generalStyle.evaluationMiddleColor,
+ value: 2,
+ selected: dto.evaluation == 2,
+ doUpdate: (circle) async => _actionUpdateEvaluation(dto, circle.value),
+ ),
+ const SizedBox(width: 12),
+ EvaluationCircleWidget(
+ buttonKey: const Key("evaluation_bad"),
+ color: _generalStyle.evaluationBadColor,
+ value: 3,
+ selected: dto.evaluation == 3,
+ doUpdate: (circle) async => _actionUpdateEvaluation(dto, circle.value),
+ ),
+ ],
+ );
+ }
+
Widget _downloadButton(BuildContext context, QuestionnaireCustomerDto customerDto) {
return ElevatedButton.icon(
key: Key("download_all_button"),
@@ -321,6 +323,12 @@ class _QuestionaireCustomerWidgetState extends State
);
}
+ Future _actionUpdateEvaluation(QuestionnaireDto dto, int value) async {
+ dto.evaluation = value;
+ _questionnaireController.updateEvaluation(dto);
+ setState(() {});
+ }
+
Future _actionDelete(BuildContext context, QuestionnaireCustomerDto customerDto, QuestionnaireDto questionnaireDto) async {
final confirmed = await showDialog(
context: context,
@@ -330,7 +338,7 @@ class _QuestionaireCustomerWidgetState extends State
);
if (confirmed == true) {
- _pictureController.delete(questionnaireDto);
+ _questionnaireController.delete(questionnaireDto);
setState(() {
_dto = _customerController.get(id: widget.customerId);
});
@@ -365,7 +373,7 @@ class _QuestionaireCustomerWidgetState extends State
try {
final bytes = await _customerController.export(customerId: customerDto.id, questionnaireId: questionnaireDto?.id);
- final fileName = questionnaireDto != null ? '${customerDto.customerNumber}_${questionnaireDto.id}.pdf' : '${customerDto.customerNumber}.pdf';
+ final fileName = questionnaireDto != null ? '${customerDto.customerNumber}_${questionnaireDto.id}.xlsx' : '${customerDto.customerNumber}.xlsx';
if (context.mounted) {
Navigator.of(context).pop();
}
diff --git a/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/evaluation_circle.dart b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/evaluation_circle.dart
new file mode 100644
index 0000000..338799c
--- /dev/null
+++ b/hartmann-foto-documentation-frontend/lib/pages/ui_utils/component/evaluation_circle.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
+import 'package:fotodocumentation/utils/di_container.dart';
+
+class EvaluationCircleWidget extends StatelessWidget {
+ GeneralStyle get _generalStyle => DiContainer.get();
+
+ final Key buttonKey;
+ final Color color;
+ final int value;
+ final bool selected;
+ final Future Function(EvaluationCircleWidget) doUpdate;
+
+ const EvaluationCircleWidget({super.key, required this.buttonKey, required this.color, required this.value, required this.selected, required this.doUpdate});
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ key: buttonKey,
+ onTap: () async => doUpdate(this), //_actionUpdateEvaluation(value),
+ customBorder: const CircleBorder(),
+ child: Container(
+ width: 24,
+ height: 24,
+ decoration: BoxDecoration(
+ color: color,
+ shape: BoxShape.circle,
+ border: selected ? Border.all(color: _generalStyle.secondaryWidgetBackgroundColor, width: 3) : null,
+ ),
+ ),
+ );
+ }
+}
diff --git a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart
index 8100a7f..657193a 100644
--- a/hartmann-foto-documentation-frontend/lib/utils/global_router.dart
+++ b/hartmann-foto-documentation-frontend/lib/utils/global_router.dart
@@ -17,8 +17,8 @@ class GlobalRouter {
static final String pathRoot = "/";
- static final String pathFoto = "/foto";
- static final String pathQuestionnaire = "/fragenbogen";
+ static final String pathFoto = "/fotodokumentation";
+ static final String pathQuestionnaire = "/fragebogen-auswertung";
static final String pathFotoHome = "$pathFoto/home";
static final String pathFotoCustomer = "$pathFoto/customer";
diff --git a/hartmann-foto-documentation-frontend/pubspec.yaml b/hartmann-foto-documentation-frontend/pubspec.yaml
index 1f022da..5c87f9a 100644
--- a/hartmann-foto-documentation-frontend/pubspec.yaml
+++ b/hartmann-foto-documentation-frontend/pubspec.yaml
@@ -122,3 +122,9 @@ flutter:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
+ fonts:
+ - family: MyFlutterApp
+ fonts:
+ - asset: assets/fonts/MyFlutterApp.ttf
+ style: normal
+
\ No newline at end of file