added download from excel and zip in frontend

This commit is contained in:
verboomp
2026-02-23 16:19:57 +01:00
parent e0c6a7db5a
commit 746294d640
15 changed files with 221 additions and 90 deletions

View File

@@ -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.Questionnaire;
import marketing.heyday.hartmann.fotodocumentation.core.model.QuestionnaireCustomer; 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; import marketing.heyday.hartmann.fotodocumentation.core.utils.QuestionnaireJsonParser.QuestionJsonObj;
/** /**
@@ -33,7 +34,9 @@ public class ExcelUtils {
public Optional<byte[]> create(QuestionnaireCustomer customer, List<Questionnaire> questionnaires) { public Optional<byte[]> create(QuestionnaireCustomer customer, List<Questionnaire> questionnaires) {
LOG.debug("Create excel file for customer " + customer); 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()) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); XSSFWorkbook workbook = new XSSFWorkbook()) {
@@ -176,12 +179,7 @@ public class ExcelUtils {
if (!answer.selected()) { if (!answer.selected()) {
continue; continue;
} }
int index = 0; int index = getMatrixAnswerIndex(answerOpts, answer);
for (int i = 0; i < answerOpts.size(); i++) {
if (answer.answer().equalsIgnoreCase(answerOpts.get(i).answer())) {
index = i;
}
}
var cell = questionRow.createCell(1 + index); var cell = questionRow.createCell(1 + index);
cell.setCellType(CellType.BOOLEAN); cell.setCellType(CellType.BOOLEAN);
cell.setCellValue(answer.selected()); cell.setCellValue(answer.selected());
@@ -190,4 +188,14 @@ public class ExcelUtils {
} }
return count; return count;
} }
private int getMatrixAnswerIndex(List<MatrixAnswer> 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;
}
} }

View File

@@ -29,17 +29,17 @@ import com.drew.metadata.exif.ExifIFD0Directory;
*/ */
public interface ImageHandler { 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. * Reads image bytes and returns a BufferedImage with correct EXIF orientation applied.
*/ */
default BufferedImage readImageWithCorrectOrientation(byte[] imageBytes) throws IOException { default BufferedImage readImageWithCorrectOrientation(byte[] imageBytes) throws IOException {
int orientation = getExifOrientation(imageBytes);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes)); BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
if (image == null) { if (image == null) {
throw new IOException("Failed to read image from byte array"); throw new IOException("Failed to read image from byte array");
} }
int orientation = getExifOrientation(imageBytes);
return applyOrientation(image, orientation); return applyOrientation(image, orientation);
} }

View File

@@ -228,7 +228,9 @@ public class PdfUtils implements ImageHandler {
// Return remaining text starting from current word // Return remaining text starting from current word
StringBuilder remaining = new StringBuilder(line); StringBuilder remaining = new StringBuilder(line);
for (int i = wordIndex; i < words.length; i++) { for (int i = wordIndex; i < words.length; i++) {
if (!remaining.isEmpty()) remaining.append(" "); if (!remaining.isEmpty()) {
remaining.append(" ");
}
remaining.append(words[i]); remaining.append(words[i]);
} }
return remaining.toString(); return remaining.toString();
@@ -289,7 +291,7 @@ public class PdfUtils implements ImageHandler {
cs.fill(); cs.fill();
} }
} }
private void drawCircle(PDPageContentStream cs, float cx, float cy, float r) throws IOException { private void drawCircle(PDPageContentStream cs, float cx, float cy, float r) throws IOException {
float k = 0.5523f; // Bezier approximation for circle float k = 0.5523f; // Bezier approximation for circle
cs.moveTo(cx - r, cy); cs.moveTo(cx - r, cy);

View File

@@ -12,6 +12,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import jakarta.json.*; import jakarta.json.*;
import jakarta.json.JsonValue.ValueType;
import jakarta.json.stream.JsonParsingException; 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 record QuestionJsonObj(String id, String title, int order, String type, String data) {
public List<MatrixQuestion> getMatrixAnswer() { public List<MatrixQuestion> getMatrixAnswer() {
return getValue("questions", () -> null, (questions) -> { return getValue("questions", () -> null, matrixQuestions -> {
var retVal = new ArrayList<MatrixQuestion>(); var retVal = new ArrayList<MatrixQuestion>();
for (var question : questions) { for (var question : matrixQuestions) {
var questionObj = question.asJsonObject(); var questionObj = question.asJsonObject();
var id = questionObj.getString("id"); var id = questionObj.getString("id");
var title = questionObj.getString("title"); var title = questionObj.getString("title");
@@ -101,11 +102,15 @@ public class QuestionnaireJsonParser {
} }
public Integer getNumberAnswer() { public Integer getNumberAnswer() {
return getValue(() -> null, (answers) -> { return getValue(() -> null, answers -> {
for (var answer : answers) { for (var answer : answers) {
var answerObj = answer.asJsonObject(); var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) { 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; return null;
@@ -113,7 +118,7 @@ public class QuestionnaireJsonParser {
} }
public String getSingleAnswer() { public String getSingleAnswer() {
return getValue(() -> "", (answers) -> { return getValue(() -> "", answers -> {
for (var answer : answers) { for (var answer : answers) {
var answerObj = answer.asJsonObject(); var answerObj = answer.asJsonObject();
if (answerObj.getBoolean("selected")) { if (answerObj.getBoolean("selected")) {
@@ -125,7 +130,7 @@ public class QuestionnaireJsonParser {
} }
public List<String> getMultiAnswer() { public List<String> getMultiAnswer() {
return getValue(() -> List.of(), (answers) -> { return getValue(() -> List.of(), answers -> {
List<String> retVal = new ArrayList<>(); List<String> retVal = new ArrayList<>();
for (var answer : answers) { for (var answer : answers) {
var answerObj = answer.asJsonObject(); var answerObj = answer.asJsonObject();

View File

@@ -246,7 +246,7 @@ class ExcelUtilsTest implements TestAble {
private Questionnaire createQuestionnaire(Date date, String questions) { private Questionnaire createQuestionnaire(Date date, String questions) {
return new Questionnaire.Builder() return new Questionnaire.Builder()
.questionnaireDate(date) .questionnaireDate(date)
.questions(QuestionnaireJsonParserTest.testJson1) .questions(QuestionnaireJsonParserTest.TEST_JSON_1)
.build(); .build();
} }
} }

View File

@@ -21,14 +21,14 @@ public class QuestionnaireJsonParserTest {
@Test @Test
public void testJson1() { public void testJson1() {
var parser = new QuestionnaireJsonParser(); var parser = new QuestionnaireJsonParser();
boolean retVal = parser.parse(testJson1); boolean retVal = parser.parse(TEST_JSON_1);
assertTrue(retVal); assertTrue(retVal);
var questions = parser.getQuestions(); var questions = parser.getQuestions();
assertEquals(10, questions.size()); assertEquals(10, questions.size());
} }
public static final String testJson1 = """ public static final String TEST_JSON_1 = """
[ [
{ {
"id": "question1", "id": "question1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

View File

@@ -7,6 +7,8 @@ abstract interface class QuestionnaireCustomerController {
Future<QuestionnaireCustomerDto?> get({required int id}); Future<QuestionnaireCustomerDto?> get({required int id});
Future<List<int>> export({required int customerId, int? questionnaireId}); Future<List<int>> export({required int customerId, int? questionnaireId});
Future<List<int>> exportAll();
} }
class QuestionnaireCustomerControllerImpl extends BaseController implements QuestionnaireCustomerController { class QuestionnaireCustomerControllerImpl extends BaseController implements QuestionnaireCustomerController {
@@ -39,4 +41,10 @@ class QuestionnaireCustomerControllerImpl extends BaseController implements Ques
} }
return runGetBytesWithAuth(uriStr); return runGetBytesWithAuth(uriStr);
} }
@override
Future<List<int>> exportAll() {
String uriStr = '${uriUtils.getBaseUrl()}$path/exportall';
return runGetBytesWithAuth(uriStr);
}
} }

View File

@@ -7,6 +7,7 @@ import 'package:fotodocumentation/dto/picture_dto.dart';
import 'package:fotodocumentation/l10n/app_localizations.dart'; import 'package:fotodocumentation/l10n/app_localizations.dart';
import 'package:fotodocumentation/pages/ui_utils/component/customer_back_button.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/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/general_error_widget.dart';
import 'package:fotodocumentation/pages/ui_utils/component/page_header_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/component/waiting_widget.dart';
@@ -306,25 +307,28 @@ class _FotoPictureWidgetState extends State<FotoPictureWidget> {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
_evaluationCircle( EvaluationCircleWidget(
key: const Key("evaluation_good"), buttonKey: const Key("evaluation_good"),
color: _generalStyle.evaluationGoodColor, color: _generalStyle.evaluationGoodColor,
value: 1, value: 1,
selected: dto.evaluation == 1, selected: dto.evaluation == 1,
doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
_evaluationCircle( EvaluationCircleWidget(
key: const Key("evaluation_middle"), buttonKey: const Key("evaluation_middle"),
color: _generalStyle.evaluationMiddleColor, color: _generalStyle.evaluationMiddleColor,
value: 2, value: 2,
selected: dto.evaluation == 2, selected: dto.evaluation == 2,
doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
_evaluationCircle( EvaluationCircleWidget(
key: const Key("evaluation_bad"), buttonKey: const Key("evaluation_bad"),
color: _generalStyle.evaluationBadColor, color: _generalStyle.evaluationBadColor,
value: 3, value: 3,
selected: dto.evaluation == 3, selected: dto.evaluation == 3,
doUpdate: (circle) => _actionUpdateEvaluation(circle.value),
), ),
], ],
), ),
@@ -334,28 +338,6 @@ class _FotoPictureWidgetState extends State<FotoPictureWidget> {
); );
} }
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 // Bottom navigation buttons
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) { Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate)); pictures.sort((a, b) => a.pictureDate.compareTo(b.pictureDate));

View File

@@ -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/component/waiting_widget.dart';
import 'package:fotodocumentation/pages/ui_utils/general_style.dart'; import 'package:fotodocumentation/pages/ui_utils/general_style.dart';
import 'package:fotodocumentation/utils/di_container.dart'; import 'package:fotodocumentation/utils/di_container.dart';
import 'package:fotodocumentation/utils/file_download.dart';
import 'package:fotodocumentation/utils/global_router.dart'; import 'package:fotodocumentation/utils/global_router.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -60,7 +61,6 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
children: [ children: [
PageHeaderWidget(text: "FRAGENBOGEN AUSWERTUNG"), PageHeaderWidget(text: "FRAGENBOGEN AUSWERTUNG"),
//_abcHeaderBar(), //_abcHeaderBar(),
const SizedBox(width: 48),
SearchBarWidget( SearchBarWidget(
searchController: _searchController, searchController: _searchController,
onSearch: (text) async => actionSearch(text), onSearch: (text) async => actionSearch(text),
@@ -68,6 +68,16 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
Expanded( Expanded(
child: _customerListWidget(), child: _customerListWidget(),
), ),
Padding(
padding: const EdgeInsets.only(top:24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
_downloadButton(context),
],
),
),
], ],
), ),
), ),
@@ -237,6 +247,34 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
); );
} }
Widget _downloadButton(BuildContext context) {
return ElevatedButton.icon(
key: Key("download_all_button"),
onPressed: () => _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<void> actionSearch(String text) async { Future<void> actionSearch(String text) async {
_reloadData(); _reloadData();
} }
@@ -250,6 +288,47 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
_dtos = _questionnaireCustomerController.getAll(_searchController.text, _selectedLetter ?? ""); _dtos = _questionnaireCustomerController.getAll(_searchController.text, _selectedLetter ?? "");
setState(() {}); setState(() {});
} }
Future<void> _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() { Widget _abcHeaderBar() {
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

View File

@@ -7,6 +7,7 @@ import 'package:fotodocumentation/dto/questionnaire_dto.dart';
import 'package:fotodocumentation/l10n/app_localizations.dart'; import 'package:fotodocumentation/l10n/app_localizations.dart';
import 'package:fotodocumentation/pages/questionnaire/customer/questionnaire_delete_dialog.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/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/general_error_widget.dart';
import 'package:fotodocumentation/pages/ui_utils/component/page_header_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/component/waiting_widget.dart';
@@ -26,7 +27,7 @@ class QuestionaireCustomerWidget extends StatefulWidget {
class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget> { class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget> {
QuestionnaireCustomerController get _customerController => DiContainer.get(); QuestionnaireCustomerController get _customerController => DiContainer.get();
QuestionnaireController get _pictureController => DiContainer.get(); QuestionnaireController get _questionnaireController => DiContainer.get();
GeneralStyle get _generalStyle => DiContainer.get(); GeneralStyle get _generalStyle => DiContainer.get();
late Future<QuestionnaireCustomerDto?> _dto; late Future<QuestionnaireCustomerDto?> _dto;
@@ -155,16 +156,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.0, spacing: 8.0,
children: [ children: [
Expanded( const SizedBox(width: 45),
flex: 1,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
AppLocalizations.of(context)!.customerWidgetHeaderFoto,
style: headerStyle,
),
),
),
Expanded( Expanded(
flex: 3, flex: 3,
child: Align( child: Align(
@@ -209,7 +201,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
); );
final dateStr = _dateFormat.format(questionnaireDto.questionnaireDate); final dateStr = _dateFormat.format(questionnaireDto.questionnaireDate);
final evaluationColor = _generalStyle.evaluationColor(value: questionnaireDto.evaluation);
return InkWell( return InkWell(
key: Key("table_row_${customerDto.id}"), key: Key("table_row_${customerDto.id}"),
child: Padding( child: Padding(
@@ -219,23 +211,9 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
spacing: 8.0, spacing: 8.0,
children: [ children: [
Expanded( ConstrainedBox(
flex: 1, constraints: const BoxConstraints(minWidth: 45, maxWidth: 45),
child: Align( child: Icon(IconData(0xE800, fontFamily: "MyFlutterApp")),
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,
),*/
),
),
), ),
Expanded( Expanded(
flex: 3, flex: 3,
@@ -248,14 +226,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
flex: 1, flex: 1,
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Container( child: _evalationWidget(questionnaireDto),
width: 20,
height: 20,
decoration: BoxDecoration(
color: evaluationColor,
shape: BoxShape.circle,
),
),
), ),
), ),
Expanded( Expanded(
@@ -293,6 +264,37 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
); );
} }
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) { Widget _downloadButton(BuildContext context, QuestionnaireCustomerDto customerDto) {
return ElevatedButton.icon( return ElevatedButton.icon(
key: Key("download_all_button"), key: Key("download_all_button"),
@@ -321,6 +323,12 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
); );
} }
Future<void> _actionUpdateEvaluation(QuestionnaireDto dto, int value) async {
dto.evaluation = value;
_questionnaireController.updateEvaluation(dto);
setState(() {});
}
Future<void> _actionDelete(BuildContext context, QuestionnaireCustomerDto customerDto, QuestionnaireDto questionnaireDto) async { Future<void> _actionDelete(BuildContext context, QuestionnaireCustomerDto customerDto, QuestionnaireDto questionnaireDto) async {
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
@@ -330,7 +338,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
); );
if (confirmed == true) { if (confirmed == true) {
_pictureController.delete(questionnaireDto); _questionnaireController.delete(questionnaireDto);
setState(() { setState(() {
_dto = _customerController.get(id: widget.customerId); _dto = _customerController.get(id: widget.customerId);
}); });
@@ -365,7 +373,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
try { try {
final bytes = await _customerController.export(customerId: customerDto.id, questionnaireId: questionnaireDto?.id); 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) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }

View File

@@ -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<void> 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,
),
),
);
}
}

View File

@@ -17,8 +17,8 @@ class GlobalRouter {
static final String pathRoot = "/"; static final String pathRoot = "/";
static final String pathFoto = "/foto"; static final String pathFoto = "/fotodokumentation";
static final String pathQuestionnaire = "/fragenbogen"; static final String pathQuestionnaire = "/fragebogen-auswertung";
static final String pathFotoHome = "$pathFoto/home"; static final String pathFotoHome = "$pathFoto/home";
static final String pathFotoCustomer = "$pathFoto/customer"; static final String pathFotoCustomer = "$pathFoto/customer";

View File

@@ -122,3 +122,9 @@ flutter:
# #
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages # see https://flutter.dev/custom-fonts/#from-packages
fonts:
- family: MyFlutterApp
fonts:
- asset: assets/fonts/MyFlutterApp.ttf
style: normal