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