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

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<List<int>> export({required int customerId, int? questionnaireId});
Future<List<int>> exportAll();
}
class QuestionnaireCustomerControllerImpl extends BaseController implements QuestionnaireCustomerController {
@@ -39,4 +41,10 @@ class QuestionnaireCustomerControllerImpl extends BaseController implements Ques
}
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/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<FotoPictureWidget> {
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<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
Widget _bottomNavigationWidget(List<PictureDto> pictures, PictureDto selectedPicture) {
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/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<QuestionaireCustomerLis
children: [
PageHeaderWidget(text: "FRAGENBOGEN AUSWERTUNG"),
//_abcHeaderBar(),
const SizedBox(width: 48),
SearchBarWidget(
searchController: _searchController,
onSearch: (text) async => actionSearch(text),
@@ -68,6 +68,16 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
Expanded(
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 {
_reloadData();
}
@@ -250,6 +288,47 @@ class _QuestionaireCustomerListWidgetState extends State<QuestionaireCustomerLis
_dtos = _questionnaireCustomerController.getAll(_searchController.text, _selectedLetter ?? "");
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() {
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/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<QuestionaireCustomerWidget> {
QuestionnaireCustomerController get _customerController => DiContainer.get();
QuestionnaireController get _pictureController => DiContainer.get();
QuestionnaireController get _questionnaireController => DiContainer.get();
GeneralStyle get _generalStyle => DiContainer.get();
late Future<QuestionnaireCustomerDto?> _dto;
@@ -155,16 +156,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
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<QuestionaireCustomerWidget>
);
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<QuestionaireCustomerWidget>
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<QuestionaireCustomerWidget>
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<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) {
return ElevatedButton.icon(
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 {
final confirmed = await showDialog<bool>(
context: context,
@@ -330,7 +338,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
);
if (confirmed == true) {
_pictureController.delete(questionnaireDto);
_questionnaireController.delete(questionnaireDto);
setState(() {
_dto = _customerController.get(id: widget.customerId);
});
@@ -365,7 +373,7 @@ class _QuestionaireCustomerWidgetState extends State<QuestionaireCustomerWidget>
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();
}

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 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";

View File

@@ -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