diff --git a/school_data_hub_client/lib/src/protocol/client.dart b/school_data_hub_client/lib/src/protocol/client.dart index 459dce1c..edb273c4 100644 --- a/school_data_hub_client/lib/src/protocol/client.dart +++ b/school_data_hub_client/lib/src/protocol/client.dart @@ -2431,12 +2431,12 @@ class EndpointPupilWorkbooks extends _i1.EndpointRef { @override String get name => 'pupilWorkbooks'; - _i2.Future<_i53.PupilWorkbook> postPupilWorkbook( + _i2.Future<_i53.PupilWorkbook?> postPupilWorkbook( int isbn, int pupilId, String createdBy, ) => - caller.callServerEndpoint<_i53.PupilWorkbook>( + caller.callServerEndpoint<_i53.PupilWorkbook?>( 'pupilWorkbooks', 'postPupilWorkbook', { diff --git a/school_data_hub_flutter/lib/common/widgets/custom_expansion_tile/custom_expansion_tile.dart b/school_data_hub_flutter/lib/common/widgets/custom_expansion_tile/custom_expansion_tile.dart index e0cebcc6..5d3cd6ba 100644 --- a/school_data_hub_flutter/lib/common/widgets/custom_expansion_tile/custom_expansion_tile.dart +++ b/school_data_hub_flutter/lib/common/widgets/custom_expansion_tile/custom_expansion_tile.dart @@ -132,8 +132,8 @@ class CustomExpansionTileController { /// populated by instances of your new inner widgets, and then in /// these inner widgets you would use [CustomExpansionTileController.of]. static CustomExpansionTileController of(BuildContext context) { - final _ExpansionTileState? result = - context.findAncestorStateOfType<_ExpansionTileState>(); + final _ExpansionTileState? result = context + .findAncestorStateOfType<_ExpansionTileState>(); if (result != null) { return result._tileController; } @@ -251,10 +251,10 @@ class CustomExpansionTile extends StatefulWidget { this.controlAffinity, this.controller, }) : assert( - expandedCrossAxisAlignment != CrossAxisAlignment.baseline, - 'CrossAxisAlignment.baseline is not supported since the expanded children ' - 'are aligned in a column, not a row. Try to use another constant.', - ); + expandedCrossAxisAlignment != CrossAxisAlignment.baseline, + 'CrossAxisAlignment.baseline is not supported since the expanded children ' + 'are aligned in a column, not a row. Try to use another constant.', + ); /// A widget to display before the title. /// @@ -498,10 +498,12 @@ class CustomExpansionTile extends StatefulWidget { class _ExpansionTileState extends State with SingleTickerProviderStateMixin { - static final Animatable _easeOutTween = - CurveTween(curve: Curves.easeOut); - static final Animatable _easeInTween = - CurveTween(curve: Curves.easeIn); + static final Animatable _easeOutTween = CurveTween( + curve: Curves.easeOut, + ); + static final Animatable _easeInTween = CurveTween( + curve: Curves.easeIn, + ); // static final Animatable _halfTween = // Tween(begin: 0.0, end: 0.5); @@ -528,14 +530,18 @@ class _ExpansionTileState extends State _heightFactor = _animationController.drive(_easeInTween); //_iconTurns = _animationController.drive(_halfTween.chain(_easeInTween)); _border = _animationController.drive(_borderTween.chain(_easeOutTween)); - _headerColor = - _animationController.drive(_headerColorTween.chain(_easeInTween)); - _iconColor = - _animationController.drive(_iconColorTween.chain(_easeInTween)); - _backgroundColor = - _animationController.drive(_backgroundColorTween.chain(_easeOutTween)); - - _isExpanded = PageStorage.maybeOf(context)?.readState(context) as bool? ?? + _headerColor = _animationController.drive( + _headerColorTween.chain(_easeInTween), + ); + _iconColor = _animationController.drive( + _iconColorTween.chain(_easeInTween), + ); + _backgroundColor = _animationController.drive( + _backgroundColorTween.chain(_easeOutTween), + ); + + _isExpanded = + PageStorage.maybeOf(context)?.readState(context) as bool? ?? widget.initiallyExpanded; if (_isExpanded) { _animationController.value = 1.0; @@ -631,9 +637,11 @@ class _ExpansionTileState extends State // } Widget _buildChildren(BuildContext context, Widget? child) { - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); - final ShapeBorder expansionTileBorder = _border.value ?? + final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of( + context, + ); + final ShapeBorder expansionTileBorder = + _border.value ?? const Border( top: BorderSide(color: Colors.transparent), bottom: BorderSide(color: Colors.transparent), @@ -644,7 +652,8 @@ class _ExpansionTileState extends State return Container( clipBehavior: clipBehavior, decoration: ShapeDecoration( - color: _backgroundColor.value ?? + color: + _backgroundColor.value ?? expansionTileTheme.backgroundColor ?? Colors.transparent, shape: expansionTileBorder, @@ -653,22 +662,23 @@ class _ExpansionTileState extends State mainAxisSize: MainAxisSize.min, children: [ ListTileTheme.merge( - iconColor: _iconColor.value ?? expansionTileTheme.iconColor, - textColor: _headerColor.value, - child: const SizedBox.shrink() - // ListTile( - // onTap: _handleTap, - // contentPadding: - // widget.tilePadding ?? expansionTileTheme.tilePadding, - // leading: widget.leading ?? _buildLeadingIcon(context), - // title: widget.title, - // subtitle: widget.subtitle, - // trailing: widget.trailing ?? _buildTrailingIcon(context), - // ), - ), + iconColor: _iconColor.value ?? expansionTileTheme.iconColor, + textColor: _headerColor.value, + child: const SizedBox.shrink(), + // ListTile( + // onTap: _handleTap, + // contentPadding: + // widget.tilePadding ?? expansionTileTheme.tilePadding, + // leading: widget.leading ?? _buildLeadingIcon(context), + // title: widget.title, + // subtitle: widget.subtitle, + // trailing: widget.trailing ?? _buildTrailingIcon(context), + // ), + ), ClipRect( child: Align( - alignment: widget.expandedAlignment ?? + alignment: + widget.expandedAlignment ?? expansionTileTheme.expandedAlignment ?? Alignment.center, heightFactor: _heightFactor.value, @@ -683,40 +693,48 @@ class _ExpansionTileState extends State @override void didChangeDependencies() { final ThemeData theme = Theme.of(context); - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); + final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of( + context, + ); final ExpansionTileThemeData defaults = theme.useMaterial3 ? _ExpansionTileDefaultsM3(context) : _ExpansionTileDefaultsM2(context); _borderTween - ..begin = widget.collapsedShape ?? + ..begin = + widget.collapsedShape ?? expansionTileTheme.collapsedShape ?? const Border( top: BorderSide(color: Colors.transparent), bottom: BorderSide(color: Colors.transparent), ) - ..end = widget.shape ?? + ..end = + widget.shape ?? expansionTileTheme.collapsedShape ?? Border( top: BorderSide(color: theme.dividerColor), bottom: BorderSide(color: theme.dividerColor), ); _headerColorTween - ..begin = widget.collapsedTextColor ?? + ..begin = + widget.collapsedTextColor ?? expansionTileTheme.collapsedTextColor ?? defaults.collapsedTextColor - ..end = widget.textColor ?? + ..end = + widget.textColor ?? expansionTileTheme.textColor ?? defaults.textColor; _iconColorTween - ..begin = widget.collapsedIconColor ?? + ..begin = + widget.collapsedIconColor ?? expansionTileTheme.collapsedIconColor ?? defaults.collapsedIconColor - ..end = widget.iconColor ?? + ..end = + widget.iconColor ?? expansionTileTheme.iconColor ?? defaults.iconColor; _backgroundColorTween - ..begin = widget.collapsedBackgroundColor ?? + ..begin = + widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor ..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor; super.didChangeDependencies(); @@ -724,8 +742,9 @@ class _ExpansionTileState extends State @override Widget build(BuildContext context) { - final ExpansionTileThemeData expansionTileTheme = - ExpansionTileTheme.of(context); + final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of( + context, + ); final bool closed = !_isExpanded && _animationController.isDismissed; final bool shouldRemoveChildren = closed && !widget.maintainState; @@ -734,7 +753,8 @@ class _ExpansionTileState extends State child: TickerMode( enabled: !closed, child: Padding( - padding: widget.childrenPadding ?? + padding: + widget.childrenPadding ?? expansionTileTheme.childrenPadding ?? EdgeInsets.zero, child: Column( diff --git a/school_data_hub_flutter/lib/core/init/init_on_user_auth.dart b/school_data_hub_flutter/lib/core/init/init_on_user_auth.dart index 72fffc4b..4a025ecb 100644 --- a/school_data_hub_flutter/lib/core/init/init_on_user_auth.dart +++ b/school_data_hub_flutter/lib/core/init/init_on_user_auth.dart @@ -29,6 +29,7 @@ import 'package:school_data_hub_flutter/features/school_lists/domain/school_list import 'package:school_data_hub_flutter/features/timetable/data/timetable_api_service.dart'; import 'package:school_data_hub_flutter/features/timetable/timetable.dart'; import 'package:school_data_hub_flutter/features/user/domain/user_manager.dart'; +import 'package:school_data_hub_flutter/features/workbooks/domain/pupil_workbook_manager.dart'; import 'package:school_data_hub_flutter/features/workbooks/domain/workbook_manager.dart'; import 'package:watch_it/watch_it.dart'; @@ -180,6 +181,21 @@ class InitOnUserAuth { }, ); + di.registerSingletonAsync( + () async { + final pupilWorkbookManager = PupilWorkbookManager(); + await pupilWorkbookManager.init(); + _log.info('[PupilWorkbookManager] initialized ✅️'); + return pupilWorkbookManager; + }, + dispose: (instance) { + _log.info('[PupilWorkbookManager] disposed 🚮'); + instance.dispose(); + return; + }, + dependsOn: [HubSessionManager, PupilProxyManager], + ); + di.registerSingletonAsync( () async { final competenceManager = CompetenceManager(); diff --git a/school_data_hub_flutter/lib/features/learning/data/competence_goal_api_service.dart b/school_data_hub_flutter/lib/features/learning/data/competence_goal_api_service.dart new file mode 100644 index 00000000..adee1a43 --- /dev/null +++ b/school_data_hub_flutter/lib/features/learning/data/competence_goal_api_service.dart @@ -0,0 +1,37 @@ +import 'package:school_data_hub_client/school_data_hub_client.dart'; +import 'package:school_data_hub_flutter/core/client/client_helper.dart'; +import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; +import 'package:watch_it/watch_it.dart'; + +class CompetenceGoalApiService { + // Private constructor + CompetenceGoalApiService._internal(); + // Singleton instance + static final CompetenceGoalApiService _instance = + CompetenceGoalApiService._internal(); + // Factory constructor to return the singleton instance + factory CompetenceGoalApiService() { + return _instance; + } + Client get _client => di(); + + HubSessionManager get _hubSessionManager => di(); + // - post a competence goal + Future postCompetenceGoal({ + required int pupilId, + required int competenceId, + required String description, + required List strategies, + }) async { + final response = ClientHelper.apiCall( + call: () => _client.competenceGoal.postCompetenceGoal( + competenceId: competenceId, + pupilId: pupilId, + description: description, + strategies: strategies, + createdBy: _hubSessionManager.userName!, + ), + ); + return response; + } +} diff --git a/school_data_hub_flutter/lib/features/learning/domain/competence_manager.dart b/school_data_hub_flutter/lib/features/learning/domain/competence_manager.dart index 6be0dea5..4d17756c 100644 --- a/school_data_hub_flutter/lib/features/learning/domain/competence_manager.dart +++ b/school_data_hub_flutter/lib/features/learning/domain/competence_manager.dart @@ -10,6 +10,7 @@ import 'package:school_data_hub_flutter/common/services/notification_service.dar import 'package:school_data_hub_flutter/core/env/env_manager.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/learning/data/competence_check_api_service.dart'; +import 'package:school_data_hub_flutter/features/learning/data/competence_goal_api_service.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_helper.dart'; import 'package:school_data_hub_flutter/features/learning/domain/filters/competence_filter_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_proxy_manager.dart'; @@ -23,7 +24,7 @@ class CompetenceManager { final _notificationService = di(); final _competenceCheckApiService = CompetenceCheckApiService(); - + final _competenceGoalApiService = CompetenceGoalApiService(); final _competences = ValueNotifier>([]); ValueListenable> get competences => _competences; @@ -289,12 +290,20 @@ class CompetenceManager { required List strategies, }) async { // TODO: Implement backend call when available - // Currently there is no endpoint for posting a competence goal - // We would need something like _client.competence.postCompetenceGoal(...) + final pupilData = await _competenceGoalApiService.postCompetenceGoal( + pupilId: pupilId, + competenceId: competenceId, + description: description, + strategies: strategies, + ); + if (pupilData == null) { + return; + } + di().updatePupilProxyWithPupilData(pupilData); _notificationService.showSnackBar( - NotificationType.warning, - 'Speichern nicht möglich: Backend-Endpunkt fehlt (CompetenceGoal)', + NotificationType.success, + 'Lernziel erstellt', ); return; diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_status_tree.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_status_tree.dart index cadf1133..5971ff02 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_status_tree.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_status_tree.dart @@ -3,8 +3,8 @@ import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/common/theme/app_colors.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_helper.dart'; import 'package:school_data_hub_flutter/features/learning/domain/competence_manager.dart'; -import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_card.dart'; import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/competence_check_card.dart'; +import 'package:school_data_hub_flutter/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_checks/pupil_competence_card.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:watch_it/watch_it.dart'; @@ -36,22 +36,25 @@ List buildPupilCompetenceStatusTree({ } double? averageCompetenceStatus; if (pupilCompetenceChecksMap.containsKey(competence.publicId)) { - final filteredChecks = - pupilCompetenceChecksMap[competence.publicId]! - .where((check) => check.score != 0) - .toList(); + final filteredChecks = pupilCompetenceChecksMap[competence.publicId]! + .where((check) => check.score != 0) + .toList(); if (filteredChecks.isNotEmpty) { - final totalStatus = filteredChecks - .map((check) => check.score) + final totalWeightedScore = filteredChecks + .map((check) => check.score * check.valueFactor) .reduce((a, b) => a + b); - averageCompetenceStatus = totalStatus / filteredChecks.length; + final totalValueFactor = filteredChecks + .map((check) => check.valueFactor) + .reduce((a, b) => a + b); + averageCompetenceStatus = totalWeightedScore / totalValueFactor; } } if (competence.parentCompetence == parentId) { - final isReport = - !di().isCompetenceWithChildren(competence); + final isReport = !di().isCompetenceWithChildren( + competence, + ); final children = buildPupilCompetenceStatusTree( pupil: pupil, parentId: competence.publicId, @@ -65,42 +68,41 @@ List buildPupilCompetenceStatusTree({ padding: EdgeInsets.only(left: indentation), child: children.isNotEmpty || - pupilCompetenceChecksMap.containsKey(competence.publicId) - ? Wrap( - children: [ - PupilCompetenceCard( - backgroundColor: competenceBackgroundColor, - competence: competence, - pupil: pupil, - isReport: isReport, - competenceChecks: - pupilCompetenceChecksMap.containsKey( - competence.publicId, - ) - ? [ - ...pupilCompetenceChecksMap[competence - .publicId]! - .map((check) { - return CompetenceCheckCard( - competenceCheck: check, - ); - }), - ] - : [], - checksAverageValue: averageCompetenceStatus, - children: children, - ), - ], - ) - : PupilCompetenceCard( - backgroundColor: competenceBackgroundColor, - isReport: isReport, - competence: competence, - pupil: pupil, - competenceChecks: const [], - checksAverageValue: averageCompetenceStatus, - children: children, - ), + pupilCompetenceChecksMap.containsKey(competence.publicId) + ? Wrap( + children: [ + PupilCompetenceCard( + backgroundColor: competenceBackgroundColor, + competence: competence, + pupil: pupil, + isReport: isReport, + competenceChecks: + pupilCompetenceChecksMap.containsKey( + competence.publicId, + ) + ? [ + ...pupilCompetenceChecksMap[competence.publicId]! + .map((check) { + return CompetenceCheckCard( + competenceCheck: check, + ); + }), + ] + : [], + checksAverageValue: averageCompetenceStatus, + children: children, + ), + ], + ) + : PupilCompetenceCard( + backgroundColor: competenceBackgroundColor, + isReport: isReport, + competence: competence, + pupil: pupil, + competenceChecks: const [], + checksAverageValue: averageCompetenceStatus, + children: children, + ), ), ); } diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart index 0461c1f0..dc99ed9e 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_competence_goals/competence_goal_card.dart @@ -88,17 +88,49 @@ class CompetenceGoalCard extends StatelessWidget { ), ], ), - const Gap(5), - Row( - children: [ - Flexible( - child: Text( - // TODO: add builder for the list of strings - pupilGoal.strategies.toString(), + if (pupilGoal.strategies != null && + pupilGoal.strategies!.isNotEmpty) ...[ + const Gap(5), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Strategien:', + style: TextStyle(fontWeight: FontWeight.w500), ), - ), - ], - ), + const Gap(10), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (int i = 0; i < pupilGoal.strategies!.length; i++) + Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '• ', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Expanded( + child: Text( + pupilGoal.strategies![i], + style: const TextStyle(fontSize: 15), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ], const Gap(10), Row( children: [ diff --git a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_workbooks.dart b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_workbooks.dart index 10920bf3..e99cfd13 100644 --- a/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_workbooks.dart +++ b/school_data_hub_flutter/lib/features/learning/presentation/pupil_competence_list_page/widgets/pupil_learning_content/pupil_learning_content_workbooks.dart @@ -1,24 +1,29 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; -import 'package:school_data_hub_client/school_data_hub_client.dart'; import 'package:school_data_hub_flutter/app_utils/scanner.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/theme/styles.dart'; +import 'package:school_data_hub_flutter/common/widgets/dialogs/short_textfield_dialog.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_proxy.dart'; import 'package:school_data_hub_flutter/features/workbooks/domain/pupil_workbook_manager.dart'; -import 'package:school_data_hub_flutter/features/workbooks/domain/workbook_manager.dart'; -import 'package:school_data_hub_flutter/features/workbooks/presentation/new_workbook_page/new_workbook_page.dart'; import 'package:school_data_hub_flutter/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart'; import 'package:watch_it/watch_it.dart'; -class PupilLearningContentWorkbooks extends StatelessWidget { +class PupilLearningContentWorkbooks extends WatchingWidget { final PupilProxy pupil; const PupilLearningContentWorkbooks({required this.pupil, super.key}); @override Widget build(BuildContext context) { - final _hubSessionManager = di(); + final hubSessionManager = di(); + final pupilWorkbookManager = watch(di()); + final pupilWorkbooks = pupilWorkbookManager.getPupilWorkbooks( + pupil.pupilId, + ); + return Column( children: [ const Row( @@ -34,39 +39,49 @@ class PupilLearningContentWorkbooks extends StatelessWidget { style: AppStyles.actionButtonStyle, //- TODO: strip this logic and use a controller instead ? onPressed: () async { - final scanResult = await qrScanner( - context: context, overlayText: 'ISBN code scannen'); - if (scanResult != null) { - final scannedIsbn = int.parse(scanResult); - if (!di() - .workbooks - .value - .any((element) => element.isbn == scannedIsbn)) { - if (context.mounted) { - Navigator.of(context).push(MaterialPageRoute( - builder: (ctx) => NewWorkbookPage( - isEdit: false, - isbn: scannedIsbn, - ))); - } - di().showInformationDialog( - 'Das Arbeitsheft wurde noch nicht erfasst. Bitte hinzufügen!'); - return; + String? isbnString; + if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) { + isbnString = await shortTextfieldDialog( + context: context, + title: 'ISBN code eingeben', + labelText: 'ISBN code', + hintText: 'ISBN code', + ); + if (isbnString != null) { + isbnString = isbnString.trim(); } - if (pupil.pupilWorkbooks!.isNotEmpty) { - if (pupil.pupilWorkbooks! - .any((element) => element.isbn == int.parse(scanResult))) { - di().showSnackBar(NotificationType.error, - 'Dieses Arbeitsheft ist schon erfasst!'); + } else { + isbnString = await qrScanner( + context: context, + overlayText: 'ISBN code scannen', + ); + } + + if (isbnString != null) { + final isbn = int.parse(isbnString); + + if (pupil.pupilWorkbooks?.isNotEmpty ?? false) { + if (pupil.pupilWorkbooks!.any( + (element) => element.isbn == isbn, + )) { + di().showSnackBar( + NotificationType.error, + 'Dieses Arbeitsheft ist schon erfasst!', + ); return; } } - di().postPupilWorkbook(pupil.internalId, - int.parse(scanResult), _hubSessionManager.userName!); + di().postPupilWorkbook( + pupil.pupilId, + isbn, + hubSessionManager.userName!, + ); return; } - di() - .showSnackBar(NotificationType.error, 'Fehler beim Scannen'); + di().showSnackBar( + NotificationType.error, + 'Fehler beim Scannen', + ); }, child: const Text( "NEUES ARBEITSHEFT", @@ -74,23 +89,21 @@ class PupilLearningContentWorkbooks extends StatelessWidget { ), ), const Gap(15), - if (pupil.pupilWorkbooks != null && - pupil.pupilWorkbooks!.isNotEmpty) ...[ + if (pupilWorkbooks.isNotEmpty) ...[ ListView.builder( padding: const EdgeInsets.all(0), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - itemCount: pupil.pupilWorkbooks!.length, + itemCount: pupilWorkbooks.length, itemBuilder: (context, int index) { - List pupilWorkbooks = pupil.pupilWorkbooks!; - return ClipRRect( borderRadius: BorderRadius.circular(25.0), child: Column( children: [ PupilWorkbookCard( - pupilWorkbook: pupilWorkbooks[index], - pupilId: pupil.internalId), + pupilWorkbook: pupilWorkbooks[index], + pupilId: pupil.pupilId, + ), ], ), ); diff --git a/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart b/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart index aec1d843..7b45b697 100644 --- a/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart +++ b/school_data_hub_flutter/lib/features/pupil/domain/models/pupil_proxy.dart @@ -10,6 +10,7 @@ import 'package:school_data_hub_flutter/features/pupil/domain/filters/pupils_fil import 'package:school_data_hub_flutter/features/pupil/domain/models/enums.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/models/pupil_identity_extensions.dart'; import 'package:school_data_hub_flutter/features/pupil/domain/pupil_proxy_manager.dart'; +import 'package:school_data_hub_flutter/features/workbooks/domain/pupil_workbook_manager.dart'; import 'package:watch_it/watch_it.dart'; typedef SiblingsResolver = List Function(PupilProxy pupil); @@ -238,7 +239,8 @@ class PupilProxy with ChangeNotifier { List? get competenceGoals => _pupilData.competenceGoals; - List? get pupilWorkbooks => _pupilData.pupilWorkbooks; + List? get pupilWorkbooks => + di().getPupilWorkbooks(pupilId); List? get pupilBookLendings => _pupilData.pupilBookLendings; diff --git a/school_data_hub_flutter/lib/features/school/domain/school_data_manager.dart b/school_data_hub_flutter/lib/features/school/domain/school_data_manager.dart index 6c83d9d2..52632b3c 100644 --- a/school_data_hub_flutter/lib/features/school/domain/school_data_manager.dart +++ b/school_data_hub_flutter/lib/features/school/domain/school_data_manager.dart @@ -48,6 +48,7 @@ class SchoolDataMainManager extends ChangeNotifier { return this; } + @override void dispose() { _dataManager.dispose(); _crudManager.dispose(); diff --git a/school_data_hub_flutter/lib/features/workbooks/domain/pupil_workbook_manager.dart b/school_data_hub_flutter/lib/features/workbooks/domain/pupil_workbook_manager.dart index d53a15f5..fe65cc6a 100644 --- a/school_data_hub_flutter/lib/features/workbooks/domain/pupil_workbook_manager.dart +++ b/school_data_hub_flutter/lib/features/workbooks/domain/pupil_workbook_manager.dart @@ -12,6 +12,19 @@ class PupilWorkbookManager with ChangeNotifier { final Map> _pupilWorkbooks = {}; final _pupilWorkbookApiService = PupilWorkbookApiService(); + Future init() async { + final pupilWorkbooks = await _pupilWorkbookApiService + .fetchAllPupilWorkbooks(); + if (pupilWorkbooks == null) { + return this; + } + for (var pupilWorkbook in pupilWorkbooks) { + addPupilWorkbook(pupilWorkbook.pupilId, pupilWorkbook); + } + + return this; + } + List getPupilWorkbooks(int pupilId) { return _pupilWorkbooks[pupilId] ?? []; } @@ -49,18 +62,26 @@ class PupilWorkbookManager with ChangeNotifier { //- create Future postPupilWorkbook( - int pupilId, int isbn, String createdBy) async { + int pupilId, + int isbn, + String createdBy, + ) async { final createdBy = _hubSessionManager.userName!; - final PupilWorkbook? responsePupil = - await _pupilWorkbookApiService.postNewPupilWorkbook( - pupilId: pupilId, isbn: isbn, createdBy: createdBy); + final PupilWorkbook? responsePupil = await _pupilWorkbookApiService + .postNewPupilWorkbook( + pupilId: pupilId, + isbn: isbn, + createdBy: createdBy, + ); if (responsePupil == null) { return; } addPupilWorkbook(pupilId, responsePupil); _notificationService.showSnackBar( - NotificationType.success, 'Arbeitsheft erstellt'); + NotificationType.success, + 'Arbeitsheft erstellt', + ); return; } @@ -81,13 +102,14 @@ class PupilWorkbookManager with ChangeNotifier { //- update - Future updatePupilWorkbook( - {required PupilWorkbook pupilWorkbook, - ({String? value})? comment, - int? score, - String? createdBy, - DateTime? createdAt, - DateTime? finishedAt}) async { + Future updatePupilWorkbook({ + required PupilWorkbook pupilWorkbook, + ({String? value})? comment, + int? score, + String? createdBy, + DateTime? createdAt, + DateTime? finishedAt, + }) async { final PupilWorkbook pupilWorkbookToUpdate = pupilWorkbook.copyWith( comment: comment != null ? comment.value : pupilWorkbook.comment, score: score ?? pupilWorkbook.score, @@ -109,8 +131,9 @@ class PupilWorkbookManager with ChangeNotifier { // - TODO: check this AI code //// Update the local collection if (_pupilWorkbooks.containsKey(pupilWorkbook.pupilId)) { - final index = _pupilWorkbooks[pupilWorkbook.pupilId]! - .indexWhere((wb) => wb.isbn == pupilWorkbook.isbn); + final index = _pupilWorkbooks[pupilWorkbook.pupilId]!.indexWhere( + (wb) => wb.isbn == pupilWorkbook.isbn, + ); if (index != -1) { _pupilWorkbooks[pupilWorkbook.pupilId]![index] = updatedPupilWorkbook; notifyListeners(); @@ -121,7 +144,9 @@ class PupilWorkbookManager with ChangeNotifier { } _notificationService.showSnackBar( - NotificationType.success, 'Arbeitsheft aktualisiert'); + NotificationType.success, + 'Arbeitsheft aktualisiert', + ); return; } @@ -130,9 +155,7 @@ class PupilWorkbookManager with ChangeNotifier { Future deletePupilWorkbook(int pupilId, int pupilWorkbookId) async { final response = await ClientHelper.apiCall( - call: () => _pupilWorkbookApiService.deletePupilWorkbook( - pupilWorkbookId, - ), + call: () => _pupilWorkbookApiService.deletePupilWorkbook(pupilWorkbookId), errorMessage: 'Fehler beim Löschen des Arbeitshefts', ); if (response == null) { @@ -149,7 +172,9 @@ class PupilWorkbookManager with ChangeNotifier { } _notificationService.showSnackBar( - NotificationType.success, 'Arbeitsheft gelöscht'); + NotificationType.success, + 'Arbeitsheft gelöscht', + ); return; } diff --git a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart index c32515cb..b13b3332 100644 --- a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart +++ b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/pupil_workbook_card.dart @@ -11,12 +11,12 @@ import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialo import 'package:school_data_hub_flutter/common/widgets/dialogs/long_textfield_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/short_textfield_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/grades_widget.dart'; +import 'package:school_data_hub_flutter/common/widgets/unencrypted_image_in_card.dart'; import 'package:school_data_hub_flutter/core/models/datetime_extensions.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; import 'package:school_data_hub_flutter/features/learning/domain/enums.dart'; import 'package:school_data_hub_flutter/features/learning/presentation/widgets/competence_check_dropdown.dart'; import 'package:school_data_hub_flutter/features/workbooks/domain/pupil_workbook_manager.dart'; -import 'package:school_data_hub_flutter/features/workbooks/domain/workbook_manager.dart'; import 'package:watch_it/watch_it.dart'; class PupilWorkbookCard extends WatchingWidget { @@ -37,9 +37,7 @@ class PupilWorkbookCard extends WatchingWidget { @override Widget build(BuildContext context) { - final Workbook workbook = di().getWorkbookByIsbn( - pupilWorkbook.workbook!.isbn, - )!; + final Workbook workbook = pupilWorkbook.workbook!; return ClipRRect( borderRadius: BorderRadius.circular(20), child: Card( @@ -62,7 +60,7 @@ class PupilWorkbookCard extends WatchingWidget { if (result == true) { di().deletePupilWorkbook( pupilId, - workbook.isbn, + pupilWorkbook.id!, ); } }, @@ -104,37 +102,24 @@ class PupilWorkbookCard extends WatchingWidget { NotificationType.warning, 'Not implemented yet', ); - // await di() - // .deleteAuthorizationFile( - // pupil.internalId, - // authorizationId, - // pupilAuthorization.fileId!, - // ); }, - child: - // workbook.imageUrl != null - // ? Provider.value( - // updateShouldNotify: (oldValue, newValue) => - // oldValue.documentUrl != - // newValue.documentUrl, - // value: DocumentImageData( - // documentTag: workbook.imageUrl!, - // documentUrl: - // '${di().env!.serverUrl}${WorkbookApiService().getWorkbookImage(workbook.isbn)}', - // size: 100), - // child: const DocumentImage(), - // ) - // : - SizedBox( - height: 100, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.asset( - 'assets/document_camera.png', + child: pupilWorkbook.workbook!.imageUrl != null + ? UnencryptedImageInCard( + cacheKey: pupilWorkbook.isbn.toString(), + path: pupilWorkbook.workbook!.imageUrl, + size: 100, + ) + : SizedBox( + height: 100, + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Image.asset( + 'assets/document_camera.png', + ), ), ), - ), ), + const Gap(10), ], ), @@ -186,9 +171,12 @@ class PupilWorkbookCard extends WatchingWidget { Row( children: [ Text( - RootCompetenceType - .stringToValue[workbook.subject]! - .value, + workbook.subject != null + ? RootCompetenceType + .stringToValue[workbook + .subject]! + .value + : 'Fach nicht bekannt', overflow: TextOverflow.fade, style: const TextStyle( fontSize: 16, diff --git a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart index 5b56fb2c..c6146aed 100644 --- a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart +++ b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/widgets/workbook_card.dart @@ -3,16 +3,21 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:school_data_hub_client/school_data_hub_client.dart'; +import 'package:school_data_hub_flutter/app_utils/create_and_crop_image_file.dart'; import 'package:school_data_hub_flutter/app_utils/extensions/isbn_extensions.dart'; import 'package:school_data_hub_flutter/common/services/notification_service.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_content.dart'; import 'package:school_data_hub_flutter/common/widgets/custom_expansion_tile/custom_expansion_tile_switch.dart'; import 'package:school_data_hub_flutter/common/widgets/dialogs/confirmation_dialog.dart'; +import 'package:school_data_hub_flutter/common/widgets/dialogs/information_dialog.dart'; import 'package:school_data_hub_flutter/common/widgets/grades_widget.dart'; import 'package:school_data_hub_flutter/common/widgets/unencrypted_image_in_card.dart'; -import 'package:school_data_hub_flutter/app_utils/create_and_crop_image_file.dart'; +import 'package:school_data_hub_flutter/core/models/datetime_extensions.dart'; import 'package:school_data_hub_flutter/core/session/hub_session_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/domain/pupil_proxy_manager.dart'; +import 'package:school_data_hub_flutter/features/pupil/presentation/widgets/avatar.dart'; +import 'package:school_data_hub_flutter/features/workbooks/domain/pupil_workbook_manager.dart'; import 'package:school_data_hub_flutter/features/workbooks/domain/workbook_manager.dart'; import 'package:school_data_hub_flutter/features/workbooks/presentation/new_workbook_page/new_workbook_page.dart'; import 'package:watch_it/watch_it.dart'; @@ -26,6 +31,13 @@ class WorkbookCard extends WatchingWidget { final expansionTileController = createOnce( () => CustomExpansionTileController(), ); + + final pupilWorkbookManager = watch(di()); + final pupilWorkbooks = pupilWorkbookManager + .getAllPupilWorkbooks() + .where((pw) => pw.isbn == workbook.isbn) + .toList(); + return ClipRRect( borderRadius: BorderRadius.circular(20), child: Card( @@ -40,11 +52,14 @@ class WorkbookCard extends WatchingWidget { // )); // }, onLongPress: () async { - // if (!di().isAdmin.value) { - // informationDialog(context, 'Keine Berechtigung', - // 'Arbeitshefte können nur von Admins bearbeitet werden!'); - // return; - // } + if (!di().isAdmin) { + informationDialog( + context, + 'Keine Berechtigung', + 'Arbeitshefte können nur von Admins bearbeitet werden!', + ); + return; + } final bool? result = await confirmationDialog( context: context, title: 'Arbeitsheft löschen', @@ -57,193 +72,249 @@ class WorkbookCard extends WatchingWidget { }, child: Padding( padding: const EdgeInsets.only(top: 8.0, bottom: 5), - child: Row( + child: Column( children: [ - const Gap(15), - Column( - mainAxisAlignment: MainAxisAlignment.center, + Row( children: [ - const Gap(10), - InkWell( - onTap: () async { - final File? file = await createAndCropImageFile( - context, - ); - if (file == null) return; - // TODO: implement when ready - di().showSnackBar( - NotificationType.warning, - 'Not implemented yet', - ); - // await di() - // .postWorkbookFile(file, workbook.isbn); - }, - onLongPress: () async { - final bool? result = await confirmationDialog( - context: context, - title: 'Bild löschen', - message: 'Bild löschen?', - ); - if (result != true) return; - // TODO: implement when ready - di().showSnackBar( - NotificationType.warning, - 'Not implemented yet', - ); + const Gap(15), + Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Gap(10), + InkWell( + onTap: () async { + final File? file = await createAndCropImageFile( + context, + ); + if (file == null) return; + // TODO: implement when ready + di().showSnackBar( + NotificationType.warning, + 'Not implemented yet', + ); + // await di() + // .postWorkbookFile(file, workbook.isbn); + }, + onLongPress: () async { + final bool? result = await confirmationDialog( + context: context, + title: 'Bild löschen', + message: 'Bild löschen?', + ); + if (result != true) return; + // TODO: implement when ready + di().showSnackBar( + NotificationType.warning, + 'Not implemented yet', + ); - // await di() - // .deleteWorkbookFile(workbook.isbn); - }, - child: workbook.imageUrl != null - ? UnencryptedImageInCard( - cacheKey: workbook.isbn.toString(), - path: workbook.imageUrl, - size: 75, - ) - : SizedBox( - height: 100, - child: ClipRRect( - borderRadius: BorderRadius.circular(5), - child: Image.asset( - 'assets/document_camera.png', - ), - ), - ), - ), - const Gap(10), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - top: 8.0, - left: 15, - bottom: 8, + // await di() + // .deleteWorkbookFile(workbook.isbn); + }, + child: UnencryptedImageInCard( + cacheKey: workbook.isbn.toString(), + path: workbook.imageUrl, + size: 75, + ), + ), + const Gap(10), + ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + Expanded( + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 15, + bottom: 8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: InkWell( - onLongPress: (di().isAdmin) - ? () async { - Navigator.of(context).push( - MaterialPageRoute( - builder: (ctx) => NewWorkbookPage( - workbook: workbook, - name: workbook.name, - isbn: workbook.isbn, - subject: workbook.subject, - level: workbook.level, - isEdit: true, - ), - ), - ); - } - : () {}, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - workbook.name, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, + Row( + children: [ + Expanded( + child: InkWell( + onLongPress: + (di().isAdmin) + ? () async { + Navigator.of(context).push( + MaterialPageRoute( + builder: (ctx) => + NewWorkbookPage( + workbook: workbook, + name: workbook.name, + isbn: workbook.isbn, + subject: workbook.subject, + level: workbook.level, + isEdit: true, + ), + ), + ); + } + : () {}, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + workbook.name, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), ), ), ), - ), - ), - const Gap(10), - ], - ), - const Gap(5), - Row( - children: [ - const Text('ISBN:'), - const Gap(10), - SelectableText( - workbook.isbn.displayAsIsbn(), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + const Gap(10), + ], ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Kompetenzbereich(e):'), - const Gap(10), - Text( - workbook.subject ?? 'nicht angegeben', - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + const Gap(5), + Row( + children: [ + const Text('ISBN:'), + const Gap(10), + SelectableText( + workbook.isbn.displayAsIsbn(), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Kompetenzstufe:'), - const Gap(10), - workbook.level != null - ? GradesWidget( - stringWithGrades: workbook.level!, - ) - : const Text( - 'nicht angegeben', - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + const Gap(5), + Row( + children: [ + const Text('Kompetenzbereich(e):'), + const Gap(10), + Text( + workbook.subject ?? 'nicht angegeben', + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, ), - ], - ), - const Gap(5), - Row( - children: [ - const Text('Bestand:'), - const Gap(10), - Text( - workbook.amount.toString(), - overflow: TextOverflow.fade, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), + ), + ], ), - const Spacer(), - CustomExpansionTileSwitch( - expansionSwitchWidget: const Icon( - Icons.arrow_downward, - ), - customExpansionTileController: - expansionTileController, + const Gap(5), + Row( + children: [ + const Text('Kompetenzstufe:'), + const Gap(10), + workbook.level != null + ? GradesWidget( + stringWithGrades: workbook.level!, + ) + : const Text( + 'nicht angegeben', + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], ), const Gap(5), + Row( + children: [ + const Text('Bestand:'), + const Gap(10), + Text( + workbook.amount.toString(), + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const Spacer(), + CustomExpansionTileSwitch( + expansionSwitchWidget: const Icon( + Icons.arrow_downward, + ), + customExpansionTileController: + expansionTileController, + ), + const Gap(5), + ], + ), + const Gap(10), ], ), - const Gap(10), - CustomExpansionTileContent( - tileController: expansionTileController, - widgetList: [ - // TODO: Add cards with pupilworkbooks - ], - ), - ], + ), ), - ), + ], + ), + CustomExpansionTileContent( + tileController: expansionTileController, + widgetList: [ + for (final pupilWorkbook in pupilWorkbooks) + Builder( + builder: (context) { + final pupil = di() + .getPupilByPupilId(pupilWorkbook.pupilId); + if (pupil == null) { + return const SizedBox.shrink(); + } + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + AvatarWithBadges(pupil: pupil, size: 50), + const Gap(10), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '${pupil.firstName} ${pupil.lastName}', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const Gap(4), + Text( + '${pupilWorkbook.createdBy} - ${pupilWorkbook.createdAt.formatDateForUser()}', + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + if (pupilWorkbook.comment != null && + pupilWorkbook.comment!.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + top: 4, + ), + child: Text( + pupilWorkbook.comment!, + style: const TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ], ), ], ), diff --git a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/workbook_list_page.dart b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/workbook_list_page.dart index 984dc909..15e266cc 100644 --- a/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/workbook_list_page.dart +++ b/school_data_hub_flutter/lib/features/workbooks/presentation/workbook_list_page/workbook_list_page.dart @@ -33,16 +33,9 @@ class WorkbookListPage extends WatchingWidget { title: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - Icons.note_alt_rounded, - size: 25, - color: Colors.white, - ), + Icon(Icons.note_alt_rounded, size: 25, color: Colors.white), Gap(10), - Text( - 'Arbeitshefte', - style: AppStyles.appBarTextStyle, - ), + Text('Arbeitshefte', style: AppStyles.appBarTextStyle), ], ), ), @@ -66,14 +59,15 @@ class WorkbookListPage extends WatchingWidget { children: [ Padding( padding: const EdgeInsets.only( - left: 10.0, top: 15.0, right: 10.00), + left: 10.0, + top: 15.0, + right: 10.00, + ), child: Row( children: [ const Text( 'Gesamt:', - style: TextStyle( - fontSize: 13, - ), + style: TextStyle(fontSize: 13), ), const Gap(10), Text( @@ -93,10 +87,11 @@ class WorkbookListPage extends WatchingWidget { children: [ Expanded( child: PupilSearchTextField( - searchType: SearchType.workbook, - hintText: 'Arbeitsheft suchen', - refreshFunction: - di().fetchWorkbooks), + searchType: SearchType.workbook, + hintText: 'Arbeitsheft suchen', + refreshFunction: + di().fetchWorkbooks, + ), ), //--------------------------------- InkWell( @@ -134,7 +129,9 @@ class WorkbookListPage extends WatchingWidget { itemCount: workbooks.length, itemBuilder: (BuildContext context, int index) { return WorkbookCard( - workbook: workbooks[index]); + key: ValueKey(workbooks[index].isbn), + workbook: workbooks[index], + ); }, ), ), diff --git a/school_data_hub_server/lib/server.dart b/school_data_hub_server/lib/server.dart index d9846191..d53ef1c4 100644 --- a/school_data_hub_server/lib/server.dart +++ b/school_data_hub_server/lib/server.dart @@ -188,8 +188,9 @@ void run(List args) async { if (emailAdmin != null && emailAdmin.isNotEmpty) { final success = await MailerService.instance.sendNotification( recipient: emailAdmin, - subject: 'Server Started', - message: 'School Data Hub Server has started successfully.\n\n' + subject: '[${pod.runMode}] Server Started', + message: + 'School Data Hub Server has started successfully in ${pod.runMode} mode.\n\n' 'Timestamp: ${DateTime.now().toIso8601String()}\n' 'User count: $userCount', ); diff --git a/school_data_hub_server/lib/src/_features/workbooks/endpoints/pupil_workbooks_endpoint.dart b/school_data_hub_server/lib/src/_features/workbooks/endpoints/pupil_workbooks_endpoint.dart index b8bb7b2b..f2889993 100644 --- a/school_data_hub_server/lib/src/_features/workbooks/endpoints/pupil_workbooks_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/workbooks/endpoints/pupil_workbooks_endpoint.dart @@ -1,5 +1,5 @@ -import 'package:school_data_hub_server/src/_features/workbooks/endpoints/workbooks_endpoint.dart'; import 'package:school_data_hub_server/src/generated/protocol.dart'; +import 'package:school_data_hub_server/src/utils/isbn_api.dart'; import 'package:serverpod/serverpod.dart'; class PupilWorkbooksEndpoint extends Endpoint { @@ -8,32 +8,35 @@ class PupilWorkbooksEndpoint extends Endpoint { //- create - Future postPupilWorkbook( + Future postPupilWorkbook( Session session, int isbn, int pupilId, String createdBy) async { // Insert a new pupil workbook into the database final result = await session.db.transaction((transaction) async { Workbook? workbook = await Workbook.db.findFirstRow( session, where: (t) => t.isbn.equals(isbn), + transaction: transaction, ); if (workbook == null) { - // If the workbook does not exist, create a new one - final workbooksEndpoint = session.server.endpoints - .getConnectorByName( - 'WorkbooksEndpoint', - ) - ?.endpoint as WorkbooksEndpoint; + // If the workbook does not exist, fetch data from ISBN API and create it + final IsbnApiData isbnApiData = + await IsbnApi.fetchIsbnApiData(session, isbn); - // Post the workbook using the WorkbooksEndpoint - await workbooksEndpoint.fetchWorkbookByIsbn( + workbook = await Workbook.db.insertRow( session, - isbn, + Workbook( + isbn: isbn, + name: isbnApiData.title, + imageUrl: isbnApiData.imagePath, + ), + transaction: transaction, ); } + final pupilWorkbook = PupilWorkbook( score: 0, pupilId: pupilId, - workbookId: workbook!.id!, + workbookId: workbook.id!, isbn: isbn, createdBy: createdBy, createdAt: DateTime.now().toUtc(), @@ -46,8 +49,13 @@ class PupilWorkbooksEndpoint extends Endpoint { Workbook.db.attachRow.assignedPupils( session, workbook, pupilWorkbookInDatabase, transaction: transaction); - - return pupilWorkbookInDatabase; + final pupilWorkbookWithWorkbook = await PupilWorkbook.db.findFirstRow( + session, + where: (t) => t.id.equals(pupilWorkbookInDatabase.id!), + include: PupilWorkbook.include(workbook: Workbook.include()), + transaction: transaction, + ); + return pupilWorkbookWithWorkbook; }); return result; @@ -57,7 +65,8 @@ class PupilWorkbooksEndpoint extends Endpoint { Future> fetchPupilWorkbooks(Session session) async { // Fetch all pupil workbooks - final pupilWorkbooks = await PupilWorkbook.db.find(session); + final pupilWorkbooks = await PupilWorkbook.db.find(session, + include: PupilWorkbook.include(workbook: Workbook.include())); return pupilWorkbooks; } @@ -73,6 +82,7 @@ class PupilWorkbooksEndpoint extends Endpoint { } //- update + Future updatePupilWorkbook( Session session, PupilWorkbook pupilWorkbook) async { // Update an existing pupil workbook diff --git a/school_data_hub_server/lib/src/_features/workbooks/endpoints/workbooks_endpoint.dart b/school_data_hub_server/lib/src/_features/workbooks/endpoints/workbooks_endpoint.dart index d22af5b0..30a1d5d3 100644 --- a/school_data_hub_server/lib/src/_features/workbooks/endpoints/workbooks_endpoint.dart +++ b/school_data_hub_server/lib/src/_features/workbooks/endpoints/workbooks_endpoint.dart @@ -21,6 +21,8 @@ class WorkbooksEndpoint extends Endpoint { return result; } + //- read + Future fetchWorkbookByIsbn( Session session, int isbn, @@ -45,13 +47,13 @@ class WorkbooksEndpoint extends Endpoint { return book; } - //- read Future> fetchWorkbooks(Session session) async { final workbooks = await Workbook.db.find(session); return workbooks; } //- update + Future updateWorkbook(Session session, Workbook workbook) async { final result = await session.db.transaction((transaction) async { final workbookId = await Workbook.db.updateRow( @@ -66,6 +68,7 @@ class WorkbooksEndpoint extends Endpoint { } //- delete + Future deleteWorkbook(Session session, int id) async { // Check if the workbook exists final workbook = await Workbook.db.findFirstRow( diff --git a/school_data_hub_server/lib/src/schemas/pupil_schemas.dart b/school_data_hub_server/lib/src/schemas/pupil_schemas.dart index ee76419d..25b6f498 100644 --- a/school_data_hub_server/lib/src/schemas/pupil_schemas.dart +++ b/school_data_hub_server/lib/src/schemas/pupil_schemas.dart @@ -13,6 +13,7 @@ class PupilSchemas { ), supportCategoryStatuses: SupportCategoryStatus.includeList(), supportGoals: SupportGoal.includeList(), + competenceGoals: CompetenceGoal.includeList(), competenceChecks: CompetenceCheck.includeList( include: CompetenceCheck.include( documents: HubDocument.includeList(), diff --git a/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart b/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart index 48786df3..4fa9fb96 100644 --- a/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart +++ b/school_data_hub_server/test/integration/test_tools/serverpod_test_tools.dart @@ -7210,7 +7210,7 @@ class _PupilWorkbooksEndpoint { final _i2.SerializationManager _serializationManager; - _i3.Future<_i54.PupilWorkbook> postPupilWorkbook( + _i3.Future<_i54.PupilWorkbook?> postPupilWorkbook( _i1.TestSessionBuilder sessionBuilder, int isbn, int pupilId, @@ -7237,7 +7237,7 @@ class _PupilWorkbooksEndpoint { var _localReturnValue = await (_localCallContext.method.call( _localUniqueSession, _localCallContext.arguments, - ) as _i3.Future<_i54.PupilWorkbook>); + ) as _i3.Future<_i54.PupilWorkbook?>); return _localReturnValue; } finally { await _localUniqueSession.close();