diff --git a/apps/librarian/lib/dashboard/layouts/borrowers_desktop_layout.dart b/apps/librarian/lib/dashboard/layouts/members_desktop_layout.dart similarity index 62% rename from apps/librarian/lib/dashboard/layouts/borrowers_desktop_layout.dart rename to apps/librarian/lib/dashboard/layouts/members_desktop_layout.dart index e684536..318e848 100644 --- a/apps/librarian/lib/dashboard/layouts/borrowers_desktop_layout.dart +++ b/apps/librarian/lib/dashboard/layouts/members_desktop_layout.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrower_details_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_filter_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/selected_borrower_provider.dart'; -import 'package:librarian_app/modules/borrowers/details/borrower_details_pane.dart'; +import 'package:librarian_app/modules/members/providers/borrower_details_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_filter_provider.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/modules/members/details/member_details_pane.dart'; import 'package:librarian_app/widgets/fields/search_field.dart'; import 'package:librarian_app/widgets/panes/list_pane.dart'; import 'package:librarian_app/widgets/panes/pane_header.dart'; -import '../../modules/borrowers/list/borrowers_list_view.dart'; +import '../../modules/members/list/members_list_view.dart'; -class BorrowersDesktopLayout extends ConsumerWidget { - const BorrowersDesktopLayout({super.key}); +class MembersDesktopLayout extends ConsumerWidget { + const MembersDesktopLayout({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -30,10 +30,10 @@ class BorrowersDesktopLayout extends ConsumerWidget { }, ), ), - child: const BorrowersListView(), + child: const MembersListView(), ), Expanded( - child: BorrowerDetailsPane( + child: MemberDetailsPane( borrowerFuture: ref.watch(borrowerDetailsProvider), ), ), diff --git a/apps/librarian/lib/dashboard/pages/dashboard_page.dart b/apps/librarian/lib/dashboard/pages/dashboard_page.dart index 0ab22fd..b5118a2 100644 --- a/apps/librarian/lib/dashboard/pages/dashboard_page.dart +++ b/apps/librarian/lib/dashboard/pages/dashboard_page.dart @@ -5,9 +5,9 @@ import 'package:librarian_app/dashboard/providers/create_loan_controller.dart'; import 'package:librarian_app/dashboard/providers/workspace.dart'; import 'package:librarian_app/modules/authentication/providers/auth_service_provider.dart'; import 'package:librarian_app/modules/authentication/providers/user_tray.dart'; -import 'package:librarian_app/modules/borrowers/details/needs_attention_page.dart'; -import 'package:librarian_app/dashboard/layouts/borrowers_desktop_layout.dart'; -import 'package:librarian_app/modules/borrowers/list/searchable_borrowers_list.dart'; +import 'package:librarian_app/modules/members/details/needs_attention_page.dart'; +import 'package:librarian_app/dashboard/layouts/members_desktop_layout.dart'; +import 'package:librarian_app/modules/members/list/searchable_members_list.dart'; import 'package:librarian_app/dashboard/providers/end_drawer_provider.dart'; import 'package:librarian_app/dashboard/widgets/create_menu_item.dart'; import 'package:librarian_app/dashboard/layouts/inventory_desktop_layout.dart'; @@ -74,12 +74,12 @@ class _DashboardPageState extends ConsumerState { ), ), DashboardModule( - title: 'Borrowers', - desktopLayout: const BorrowersDesktopLayout(), - mobileLayout: SearchableBorrowersList( + title: 'Members', + desktopLayout: const MembersDesktopLayout(), + mobileLayout: SearchableMembersList( onTapBorrower: (borrower) { Navigator.push(context, MaterialPageRoute(builder: (_) { - return NeedsAttentionPage(borrower: borrower); + return NeedsAttentionPage(member: borrower); })); }, ), @@ -219,7 +219,7 @@ class _DashboardPageState extends ConsumerState { NavigationDestination( selectedIcon: Icon(Icons.people), icon: Icon(Icons.people_outlined), - label: "Borrowers", + label: "Members", ), NavigationDestination( selectedIcon: Icon(Icons.build), diff --git a/apps/librarian/lib/dashboard/providers/create_loan_controller.dart b/apps/librarian/lib/dashboard/providers/create_loan_controller.dart index a228f34..8a1ec5c 100644 --- a/apps/librarian/lib/dashboard/providers/create_loan_controller.dart +++ b/apps/librarian/lib/dashboard/providers/create_loan_controller.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/dashboard/providers/workspace.dart'; import 'package:librarian_app/dashboard/widgets/workspace_window.dart'; import 'package:librarian_app/modules/loans/checkout/checkout_page.dart'; -import 'package:librarian_app/modules/loans/checkout/checkout_stepper.dart'; +import 'package:librarian_app/modules/loans/checkout/stepper/checkout_stepper.dart'; import 'package:librarian_app/utils/media_query.dart'; class CreateLoanController { diff --git a/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart b/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart index 6156dee..eb1639d 100644 --- a/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart +++ b/apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart @@ -33,7 +33,7 @@ class DesktopDashboard extends StatelessWidget { NavigationRailDestination( selectedIcon: Icon(Icons.people), icon: Icon(Icons.people_outlined), - label: Text('Borrowers'), + label: Text('Members'), padding: EdgeInsets.symmetric(vertical: 8), ), NavigationRailDestination( diff --git a/apps/librarian/lib/modules/loans/checkout/checkout_page.dart b/apps/librarian/lib/modules/loans/checkout/checkout_page.dart index 30a0d51..062d72f 100644 --- a/apps/librarian/lib/modules/loans/checkout/checkout_page.dart +++ b/apps/librarian/lib/modules/loans/checkout/checkout_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:librarian_app/modules/loans/checkout/checkout_stepper.dart'; +import 'package:librarian_app/modules/loans/checkout/stepper/checkout_stepper.dart'; class CheckoutPage extends StatelessWidget { const CheckoutPage({super.key}); diff --git a/apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart b/apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart deleted file mode 100644 index bf0d3b8..0000000 --- a/apps/librarian/lib/modules/loans/checkout/checkout_stepper.dart +++ /dev/null @@ -1,288 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/core/api/models/borrower_model.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; -import 'package:librarian_app/modules/borrowers/details/borrower_issues.dart'; -import 'package:librarian_app/modules/loans/checkout/borrower_search_delegate.dart'; -import 'package:librarian_app/modules/loans/checkout/suggested_things_dialog.dart'; -import 'package:librarian_app/modules/loans/details/loan_details_page.dart'; -import 'package:librarian_app/modules/loans/providers/loans_controller_provider.dart'; -import 'package:librarian_app/modules/loans/checkout/eye_protection_dialog.dart'; -import 'package:librarian_app/utils/media_query.dart'; -import 'package:librarian_app/widgets/filled_progress_button.dart'; -import 'package:librarian_app/core/api/models/item_model.dart'; -import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; -import 'package:librarian_app/core/api/models/thing_summary_model.dart'; -import 'package:librarian_app/modules/loans/checkout/connected_thing_search_field.dart'; - -import 'checkout_details.dart'; - -class CheckoutStepper extends ConsumerStatefulWidget { - const CheckoutStepper({super.key, this.onFinish}); - - final void Function()? onFinish; - - @override - ConsumerState createState() => _CheckoutStepperState(); -} - -class _CheckoutStepperState extends ConsumerState { - int _index = 0; - BorrowerModel? _borrower; - DateTime _dueDate = DateTime.now().add(const Duration(days: 7)); - - bool _didPromptForEyeProtection = false; - - final List _things = []; - - void Function()? _onStepContinueFactory(int index) { - switch (index) { - case 0: - if (_borrower == null || !_borrower!.active) { - return null; - } - - return () { - setState(() => _index++); - }; - case 1: - if (_things.isEmpty) { - return null; - } - - return () { - setState(() => _index++); - }; - default: - return _finish; - } - } - - void _finish() async { - final success = await ref.read(loansControllerProvider).openLoan( - borrowerId: _borrower!.id, - thingIds: _things.map((e) => e.id).toList(), - dueDate: _dueDate); - - Future.delayed(Duration.zero, () { - widget.onFinish?.call(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(success ? 'Success!' : 'Failed to create loan records'), - ), - ); - - if (isMobile(context)) { - Navigator.of(context).push(MaterialPageRoute(builder: (context) { - return const LoanDetailsPage(); - })); - } - }); - } - - @override - Widget build(BuildContext context) { - return Stepper( - currentStep: _index, - controlsBuilder: (context, details) { - return Padding( - padding: const EdgeInsets.only(top: 16), - child: Row( - children: [ - details.stepIndex != 2 - ? FilledButton( - onPressed: details.onStepContinue, - child: const Text('Continue'), - ) - : FilledProgressButton( - onPressed: details.onStepContinue, - child: const Text('Confirm'), - ), - ], - ), - ); - }, - onStepTapped: (value) { - if (value < _index) { - setState(() => _index = value); - } - }, - onStepContinue: _onStepContinueFactory(_index), - onStepCancel: _index > 0 - ? () { - setState(() => _index--); - } - : null, - steps: [ - Step( - title: const Text('Select Borrower'), - subtitle: _borrower != null ? Text(_borrower!.name) : null, - content: Column( - children: [ - _SelectBorrowerTextField( - text: _borrower?.name, - onSelected: (borrower) { - if (borrower != null) { - setState(() => _borrower = borrower); - } - }, - ), - if (_borrower != null && !_borrower!.active) ...[ - const SizedBox(height: 16), - BorrowerIssues( - borrowerId: _borrower!.id, - issues: _borrower!.issues, - onRecordCashPayment: (success) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - success ? 'Success!' : 'Failed to record payment'), - ), - ); - - if (success) { - ref - .read(borrowersRepositoryProvider.notifier) - .getBorrower(_borrower!.id) - .then((b) { - setState(() => _borrower = b); - }); - } - }, - ), - ], - ], - ), - isActive: _index >= 0, - ), - Step( - title: const Text('Add Things'), - subtitle: Text( - '${_things.length} Thing${_things.length == 1 ? '' : 's'} Added'), - content: Column( - children: [ - ConnectedThingSearchField( - controller: ThingSearchController( - context: context, - onMatchFound: (thing) { - setState(() => _things.add(thing)); - - if (thing.eyeProtection && !_didPromptForEyeProtection) { - showDialog( - context: context, - builder: (_) => const EyeProtectionDialog(), - ); - _didPromptForEyeProtection = true; - } - - if (thing.linkedThingIds.isNotEmpty) { - showDialog( - context: context, - builder: (_) => SuggestedThingsDialog( - thingName: thing.name, - thingIds: thing.linkedThingIds, - ), - ); - } - }, - repository: ref.read(thingsRepositoryProvider.notifier), - ), - ), - const SizedBox(height: 8), - ListView.builder( - itemCount: _things.length, - itemBuilder: (context, index) { - final thing = _things[index]; - return Card( - child: ListTile( - leading: Text('#${thing.number}'), - title: Text(thing.name), - trailing: IconButton( - icon: const Icon(Icons.remove_circle_rounded), - onPressed: () { - setState(() => _things.remove(thing)); - }, - tooltip: 'Remove #${thing.number}', - ), - ), - ); - }, - shrinkWrap: true, - ), - ], - ), - isActive: _index >= 1, - ), - Step( - title: const Text('Confirm Details'), - content: Padding( - padding: const EdgeInsets.only(top: 8), - child: CheckoutDetails( - borrower: _borrower, - things: _things - .map((t) => ThingSummaryModel( - id: t.id, - name: t.name, - number: t.number, - images: [], - )) - .toList(), - dueDate: _dueDate, - onDueDateUpdated: (newDate) { - setState(() => _dueDate = newDate); - }, - ), - ), - isActive: _index >= 2, - ), - ], - ); - } -} - -class _SelectBorrowerTextField extends ConsumerStatefulWidget { - const _SelectBorrowerTextField({ - required this.text, - required this.onSelected, - }); - - final String? text; - final void Function(BorrowerModel? borrower) onSelected; - - @override - ConsumerState<_SelectBorrowerTextField> createState() => - _SelectBorrowerTextFieldState(); -} - -class _SelectBorrowerTextFieldState - extends ConsumerState<_SelectBorrowerTextField> { - bool _isLoading = false; - - @override - Widget build(BuildContext context) { - return TextField( - controller: TextEditingController(text: widget.text), - canRequestFocus: false, - decoration: InputDecoration( - labelText: _isLoading ? 'Loading...' : 'Borrower', - prefixIcon: const Icon(Icons.person_rounded), - ), - enabled: !_isLoading, - onTap: () { - setState(() => _isLoading = true); - - ref.invalidate(borrowersRepositoryProvider); - ref.read(borrowersRepositoryProvider).then((borrowers) async { - return await showSearch( - context: context, - delegate: BorrowerSearchDelegate(borrowers), - useRootNavigator: true, - ); - }).then((borrower) { - widget.onSelected(borrower); - setState(() => _isLoading = false); - }); - }, - ); - } -} diff --git a/apps/librarian/lib/modules/loans/checkout/pick_things.dart b/apps/librarian/lib/modules/loans/checkout/pick_things.dart deleted file mode 100644 index 4c37a55..0000000 --- a/apps/librarian/lib/modules/loans/checkout/pick_things.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/core/api/models/item_model.dart'; -import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; - -import '../../../widgets/fields/submit_text_field.dart'; -import 'thing_list_tile.dart'; - -class PickThingsView extends ConsumerStatefulWidget { - const PickThingsView({ - super.key, - required this.pickedThings, - required this.onThingPicked, - }); - - final List pickedThings; - final Function(ItemModel thing) onThingPicked; - - @override - ConsumerState createState() => _PickThingsViewState(); -} - -class _PickThingsViewState extends ConsumerState { - final _searchController = TextEditingController(); - bool _isLoading = false; - - Future _onSearchSubmitted(String value) async { - setState(() => _isLoading = true); - - final thingsRepository = ref.read(thingsRepositoryProvider.notifier); - final match = await thingsRepository.getItem(number: int.parse(value)); - - setState(() => _isLoading = false); - - if (match != null) { - if (!match.available) { - _showThingCheckedOutDialog(match); - } else { - widget.onThingPicked(match); - } - } else { - _showUnknownThingDialog(value); - } - - _searchController.clear(); - } - - void _showThingCheckedOutDialog(ItemModel thing) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text("Thing Unavailable"), - content: Text( - "Thing #${thing.number} is checked out or not available for lending."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () => Navigator.pop(context), - ) - ], - ); - }, - ); - } - - void _showUnknownThingDialog(String searchValue) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text("Thing #$searchValue does not exist"), - content: const Text("Try another number."), - actions: [ - TextButton( - child: const Text("OK"), - onPressed: () => Navigator.pop(context), - ) - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - if (_isLoading) { - return const Center(child: CircularProgressIndicator()); - } - - final pickedThings = widget.pickedThings; - pickedThings.sort((a, b) => a.number.compareTo(b.number)); - - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: SubmitTextField( - labelText: "Thing ID", - prefixIcon: const Icon(Icons.search), - controller: _searchController, - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - onSubmitted: _onSearchSubmitted, - onChanged: (_) => {}, - ), - ), - pickedThings.isNotEmpty - ? ListView.builder( - itemCount: pickedThings.length, - itemBuilder: (context, index) { - final thing = pickedThings[index]; - - return ThingListTile( - number: thing.number, - name: thing.name, - available: thing.available, - selected: true, - onTap: () => widget.onThingPicked(thing), - ); - }, - shrinkWrap: true, - ) - : const Expanded( - child: Center(child: Text('Add things to check out.')), - ), - ], - ); - } -} diff --git a/apps/librarian/lib/modules/loans/checkout/borrower_search_delegate.dart b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart similarity index 89% rename from apps/librarian/lib/modules/loans/checkout/borrower_search_delegate.dart rename to apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart index d39fdf2..6a59250 100644 --- a/apps/librarian/lib/modules/loans/checkout/borrower_search_delegate.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import '../../../core/api/models/borrower_model.dart'; -import '../../borrowers/list/borrowers_list.dart'; +import '../../../../../core/api/models/borrower_model.dart'; +import '../../../../members/list/members_list.dart'; class BorrowerSearchDelegate extends SearchDelegate { BorrowerSearchDelegate(this.borrowers); @@ -44,7 +44,7 @@ class BorrowerSearchDelegate extends SearchDelegate { .where((b) => b.name.toLowerCase().contains(query.toLowerCase())) .toList(); - return BorrowersList( + return MembersList( borrowers: results, onTap: (borrower) { close(context, borrower); diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart new file mode 100644 index 0000000..3455b68 --- /dev/null +++ b/apps/librarian/lib/modules/loans/checkout/stepper/borrower/borrower_step.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; +import 'package:librarian_app/modules/members/details/issues.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/loans/checkout/stepper/borrower/borrower_search_delegate.dart'; + +Step buildBorrowerStep({ + required BuildContext context, + required WidgetRef ref, + required bool isActive, + required BorrowerModel? borrower, + required void Function(BorrowerModel?) onBorrowerSelected, +}) { + return Step( + title: const Text('Select Borrower'), + subtitle: borrower != null ? Text(borrower.name) : null, + content: Column( + children: [ + _SelectBorrowerTextField( + text: borrower?.name, + onSelected: onBorrowerSelected, + ), + if (borrower != null && !borrower.active) ...[ + const SizedBox(height: 16), + MemberIssues( + borrowerId: borrower.id, + issues: borrower.issues, + onRecordCashPayment: (success) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text(success ? 'Success!' : 'Failed to record payment'), + ), + ); + + if (success) { + ref + .read(borrowersRepositoryProvider.notifier) + .getBorrower(borrower.id) + .then(onBorrowerSelected); + } + }, + ), + ], + ], + ), + isActive: isActive, + ); +} + +class _SelectBorrowerTextField extends ConsumerStatefulWidget { + const _SelectBorrowerTextField({ + required this.text, + required this.onSelected, + }); + + final String? text; + final void Function(BorrowerModel? borrower) onSelected; + + @override + ConsumerState<_SelectBorrowerTextField> createState() => + _SelectBorrowerTextFieldState(); +} + +class _SelectBorrowerTextFieldState + extends ConsumerState<_SelectBorrowerTextField> { + bool _isLoading = false; + + @override + Widget build(BuildContext context) { + return TextField( + controller: TextEditingController(text: widget.text), + canRequestFocus: false, + decoration: InputDecoration( + labelText: _isLoading ? 'Loading...' : 'Borrower', + prefixIcon: const Icon(Icons.person_rounded), + ), + enabled: !_isLoading, + onTap: () { + setState(() => _isLoading = true); + + ref.invalidate(borrowersRepositoryProvider); + ref.read(borrowersRepositoryProvider).then((borrowers) async { + return await showSearch( + context: context, + delegate: BorrowerSearchDelegate(borrowers), + useRootNavigator: true, + ); + }).then((borrower) { + widget.onSelected(borrower); + setState(() => _isLoading = false); + }); + }, + ); + } +} diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/checkout_stepper.dart b/apps/librarian/lib/modules/loans/checkout/stepper/checkout_stepper.dart new file mode 100644 index 0000000..6e25a8d --- /dev/null +++ b/apps/librarian/lib/modules/loans/checkout/stepper/checkout_stepper.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; +import 'package:librarian_app/modules/loans/details/loan_details_page.dart'; +import 'package:librarian_app/modules/loans/providers/loans_controller_provider.dart'; +import 'package:librarian_app/utils/media_query.dart'; +import 'package:librarian_app/widgets/filled_progress_button.dart'; +import 'package:librarian_app/core/api/models/item_model.dart'; + +import 'borrower/borrower_step.dart'; +import 'confirm/confirm_step.dart'; +import 'items/items_step.dart'; + +class CheckoutStepper extends ConsumerStatefulWidget { + const CheckoutStepper({super.key, this.onFinish}); + + final void Function()? onFinish; + + @override + ConsumerState createState() => _CheckoutStepperState(); +} + +class _CheckoutStepperState extends ConsumerState { + int stepIndex = 0; + BorrowerModel? borrower; + DateTime dueDate = DateTime.now().add(const Duration(days: 7)); + + bool didPromptForEyeProtection = false; + + final List items = []; + + void Function()? _onStepContinueFactory(int index) { + switch (index) { + case 0: + if (borrower == null || !borrower!.active) { + return null; + } + + return () { + setState(() => stepIndex++); + }; + case 1: + if (items.isEmpty) { + return null; + } + + return () { + setState(() => stepIndex++); + }; + default: + return _finish; + } + } + + void _finish() async { + final success = await ref.read(loansControllerProvider).openLoan( + borrowerId: borrower!.id, + thingIds: items.map((e) => e.id).toList(), + dueDate: dueDate); + + Future.delayed(Duration.zero, () { + widget.onFinish?.call(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(success ? 'Success!' : 'Failed to create loan records'), + ), + ); + + if (isMobile(context)) { + Navigator.of(context).push(MaterialPageRoute(builder: (context) { + return const LoanDetailsPage(); + })); + } + }); + } + + @override + Widget build(BuildContext context) { + return Stepper( + currentStep: stepIndex, + controlsBuilder: (context, details) { + return Padding( + padding: const EdgeInsets.only(top: 16), + child: Row( + children: [ + details.stepIndex != 2 + ? FilledButton( + onPressed: details.onStepContinue, + child: const Text('Continue'), + ) + : FilledProgressButton( + onPressed: details.onStepContinue, + child: const Text('Confirm'), + ), + ], + ), + ); + }, + onStepTapped: (value) { + if (value < stepIndex) { + setState(() => stepIndex = value); + } + }, + onStepContinue: _onStepContinueFactory(stepIndex), + onStepCancel: stepIndex > 0 + ? () { + setState(() => stepIndex--); + } + : null, + steps: [ + buildBorrowerStep( + context: context, + ref: ref, + isActive: stepIndex >= 0, + borrower: borrower, + onBorrowerSelected: (b) { + setState(() => borrower = b); + }, + ), + buildItemsStep( + context: context, + ref: ref, + isActive: stepIndex >= 1, + didPromptForEyeProtection: didPromptForEyeProtection, + items: items, + onAddItem: (item) { + setState(() => items.add(item)); + }, + onRemoveItem: (item) { + setState(() => items.remove(item)); + }, + onPromptForEyeProtection: () { + setState(() => didPromptForEyeProtection = true); + }, + ), + buildConfirmStep( + isActive: stepIndex >= 2, + borrower: borrower, + items: items, + dueDate: dueDate, + onDueDateUpdated: (newDate) { + setState(() => dueDate = newDate); + }, + ), + ], + ); + } +} diff --git a/apps/librarian/lib/modules/loans/checkout/checkout_details.dart b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart similarity index 98% rename from apps/librarian/lib/modules/loans/checkout/checkout_details.dart rename to apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart index 043189e..a747019 100644 --- a/apps/librarian/lib/modules/loans/checkout/checkout_details.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/checkout_details.dart @@ -50,7 +50,7 @@ class CheckoutDetails extends StatelessWidget { padding: const EdgeInsets.only(bottom: 16), child: Detail( prefixIcon: const Icon(Icons.build_rounded), - label: 'Thing', + label: 'Item', value: '#${thing.number} ${thing.name}', ), ); diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/confirm/confirm_step.dart b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/confirm_step.dart new file mode 100644 index 0000000..1a972bc --- /dev/null +++ b/apps/librarian/lib/modules/loans/checkout/stepper/confirm/confirm_step.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:librarian_app/core/api/models/borrower_model.dart'; +import 'package:librarian_app/core/api/models/item_model.dart'; +import 'package:librarian_app/core/api/models/thing_summary_model.dart'; + +import 'checkout_details.dart'; + +Step buildConfirmStep({ + required bool isActive, + required BorrowerModel? borrower, + required List items, + required DateTime dueDate, + required void Function(DateTime) onDueDateUpdated, +}) { + return Step( + title: const Text('Confirm Details'), + content: Padding( + padding: const EdgeInsets.only(top: 8), + child: CheckoutDetails( + borrower: borrower, + things: items + .map((t) => ThingSummaryModel( + id: t.id, + name: t.name, + number: t.number, + images: [], + )) + .toList(), + dueDate: dueDate, + onDueDateUpdated: onDueDateUpdated, + ), + ), + isActive: isActive, + ); +} diff --git a/apps/librarian/lib/modules/loans/checkout/connected_thing_search_field.dart b/apps/librarian/lib/modules/loans/checkout/stepper/items/connected_thing_search_field.dart similarity index 90% rename from apps/librarian/lib/modules/loans/checkout/connected_thing_search_field.dart rename to apps/librarian/lib/modules/loans/checkout/stepper/items/connected_thing_search_field.dart index 5edd9ec..d8ce017 100644 --- a/apps/librarian/lib/modules/loans/checkout/connected_thing_search_field.dart +++ b/apps/librarian/lib/modules/loans/checkout/stepper/items/connected_thing_search_field.dart @@ -30,10 +30,10 @@ class ConnectedThingSearchField extends StatelessWidget { controller: _textController, onSubmitted: (_) => _submit(), decoration: InputDecoration( - hintText: 'Enter Thing Number', + hintText: 'Enter Item Number', prefixIcon: const Icon(Icons.numbers), suffixIcon: IconButton( - tooltip: 'Add Thing', + tooltip: 'Add Item', onPressed: () => _submit(), icon: const Icon(Icons.add_rounded), ), @@ -79,9 +79,9 @@ class ThingSearchController { context: context, builder: (context) { return AlertDialog( - title: const Text("Thing Unavailable"), + title: const Text("Item Unavailable"), content: Text( - "Thing #${thing.number} is checked out or not available for lending."), + "Item #${thing.number} is checked out or not available for lending."), actions: [ TextButton( child: const Text("OK"), @@ -98,7 +98,7 @@ class ThingSearchController { context: context, builder: (context) { return AlertDialog( - title: Text("Thing #$searchValue does not exist"), + title: Text("Item #$searchValue does not exist."), content: const Text("Try another number."), actions: [ TextButton( diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/items/existing_item_dialog.dart b/apps/librarian/lib/modules/loans/checkout/stepper/items/existing_item_dialog.dart new file mode 100644 index 0000000..e1d99ad --- /dev/null +++ b/apps/librarian/lib/modules/loans/checkout/stepper/items/existing_item_dialog.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class ExistingItemDialog extends StatelessWidget { + const ExistingItemDialog({super.key, required this.number}); + + final int number; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Item #$number Already Added'), + content: const Text("The item can't be added again."), + actions: [ + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('OK'), + ), + ], + ); + } +} diff --git a/apps/librarian/lib/modules/loans/checkout/eye_protection_dialog.dart b/apps/librarian/lib/modules/loans/checkout/stepper/items/eye_protection_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/checkout/eye_protection_dialog.dart rename to apps/librarian/lib/modules/loans/checkout/stepper/items/eye_protection_dialog.dart diff --git a/apps/librarian/lib/modules/loans/checkout/stepper/items/items_step.dart b/apps/librarian/lib/modules/loans/checkout/stepper/items/items_step.dart new file mode 100644 index 0000000..34c510b --- /dev/null +++ b/apps/librarian/lib/modules/loans/checkout/stepper/items/items_step.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:librarian_app/core/api/models/item_model.dart'; +import 'package:librarian_app/modules/things/providers/things_repository_provider.dart'; +import 'package:librarian_app/widgets/item_card.dart'; + +import 'connected_thing_search_field.dart'; +import 'existing_item_dialog.dart'; +import 'eye_protection_dialog.dart'; +import 'suggested_things_dialog.dart'; + +Step buildItemsStep({ + required BuildContext context, + required WidgetRef ref, + required bool isActive, + required bool didPromptForEyeProtection, + required List items, + required void Function(ItemModel) onAddItem, + required void Function(ItemModel) onRemoveItem, + required void Function() onPromptForEyeProtection, +}) { + return Step( + title: const Text('Add Items'), + subtitle: Text('${items.length} Item${items.length == 1 ? '' : 's'} Added'), + content: Column( + children: [ + ConnectedThingSearchField( + controller: ThingSearchController( + context: context, + onMatchFound: (thing) { + if (items.any((t) => t.id == thing.id)) { + showDialog( + context: context, + builder: (context) { + return ExistingItemDialog(number: thing.number); + }, + ); + return; + } + + onAddItem(thing); + + if (thing.eyeProtection && !didPromptForEyeProtection) { + showDialog( + context: context, + builder: (_) => const EyeProtectionDialog(), + ); + onPromptForEyeProtection(); + } + + if (thing.linkedThingIds.isNotEmpty) { + showDialog( + context: context, + builder: (_) => SuggestedThingsDialog( + thingName: thing.name, + thingIds: thing.linkedThingIds, + ), + ); + } + }, + repository: ref.read(thingsRepositoryProvider.notifier), + ), + ), + const SizedBox(height: 16.0), + GridView.count( + crossAxisCount: 8, + shrinkWrap: true, + children: items.map((item) { + return ItemCard( + number: item.number, + imageUrl: item.imageUrls.firstOrNull, + trailing: IconButton( + icon: const Icon(Icons.remove_circle), + onPressed: () => onRemoveItem(item), + padding: EdgeInsets.zero, + tooltip: 'Remove', + ), + ); + }).toList(), + ), + ], + ), + isActive: isActive, + ); +} diff --git a/apps/librarian/lib/modules/loans/checkout/suggested_things_dialog.dart b/apps/librarian/lib/modules/loans/checkout/stepper/items/suggested_things_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/loans/checkout/suggested_things_dialog.dart rename to apps/librarian/lib/modules/loans/checkout/stepper/items/suggested_things_dialog.dart diff --git a/apps/librarian/lib/modules/loans/checkout/thing_list_tile.dart b/apps/librarian/lib/modules/loans/checkout/thing_list_tile.dart deleted file mode 100644 index 1839305..0000000 --- a/apps/librarian/lib/modules/loans/checkout/thing_list_tile.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -class ThingListTile extends StatelessWidget { - final int number; - final String name; - final bool available; - final bool selected; - final Function()? onTap; - - const ThingListTile({ - super.key, - required this.number, - required this.name, - required this.available, - required this.selected, - this.onTap, - }); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(name), - subtitle: available - ? const Text( - "Available", - style: TextStyle(color: Colors.green), - ) - : const Text( - "Checked out", - style: TextStyle(color: Colors.orange), - ), - leading: Text("#$number"), - trailing: selected - ? IconButton( - icon: const Icon(Icons.remove_circle_rounded), - tooltip: 'Remove', - onPressed: onTap, - ) - : null, - ); - } -} diff --git a/apps/librarian/lib/modules/borrowers/details/contact_card.dart b/apps/librarian/lib/modules/members/details/contact_card.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/details/contact_card.dart rename to apps/librarian/lib/modules/members/details/contact_card.dart diff --git a/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart b/apps/librarian/lib/modules/members/details/issues.dart similarity index 89% rename from apps/librarian/lib/modules/borrowers/details/borrower_issues.dart rename to apps/librarian/lib/modules/members/details/issues.dart index 35d3cd6..2b45828 100644 --- a/apps/librarian/lib/modules/borrowers/details/borrower_issues.dart +++ b/apps/librarian/lib/modules/members/details/issues.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; import '../../../core/api/models/issue_model.dart'; import '../payments/dues_dialog.dart'; -class BorrowerIssues extends ConsumerWidget { +class MemberIssues extends ConsumerWidget { final String borrowerId; final List issues; final void Function(bool success) onRecordCashPayment; - const BorrowerIssues({ + const MemberIssues({ super.key, required this.borrowerId, required this.issues, @@ -26,7 +26,7 @@ class BorrowerIssues extends ConsumerWidget { duesNotPaidIssue, isOk: !issues.contains(duesNotPaidIssue), trailing: _PayDuesButton( - borrowerId: borrowerId, + memberId: borrowerId, onRecordCashPayment: onRecordCashPayment, ), ), @@ -86,11 +86,11 @@ class _IssueTile extends StatelessWidget { class _PayDuesButton extends ConsumerWidget { const _PayDuesButton({ - required this.borrowerId, + required this.memberId, required this.onRecordCashPayment, }); - final String borrowerId; + final String memberId; final void Function(bool success) onRecordCashPayment; @override @@ -107,7 +107,7 @@ class _PayDuesButton extends ConsumerWidget { onConfirmPayment: (cash) async { final result = await ref .read(borrowersRepositoryProvider.notifier) - .recordPayment(borrowerId: borrowerId, cash: cash); + .recordPayment(borrowerId: memberId, cash: cash); onRecordCashPayment(result); }, diff --git a/apps/librarian/lib/modules/borrowers/details/issues_card.dart b/apps/librarian/lib/modules/members/details/issues_card.dart similarity index 89% rename from apps/librarian/lib/modules/borrowers/details/issues_card.dart rename to apps/librarian/lib/modules/members/details/issues_card.dart index 036ecc9..3f5fae6 100644 --- a/apps/librarian/lib/modules/borrowers/details/issues_card.dart +++ b/apps/librarian/lib/modules/members/details/issues_card.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/issue_model.dart'; -import 'package:librarian_app/modules/borrowers/details/borrower_issues.dart'; +import 'package:librarian_app/modules/members/details/issues.dart'; import 'package:librarian_app/widgets/details_card/card_header.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; @@ -19,7 +19,7 @@ class IssuesCard extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return DetailsCard( header: const CardHeader(title: 'Issues'), - body: BorrowerIssues( + body: MemberIssues( borrowerId: borrowerId, issues: issues, onRecordCashPayment: (success) { diff --git a/apps/librarian/lib/modules/borrowers/details/borrower_details.dart b/apps/librarian/lib/modules/members/details/member_details.dart similarity index 74% rename from apps/librarian/lib/modules/borrowers/details/borrower_details.dart rename to apps/librarian/lib/modules/members/details/member_details.dart index cf9939a..b8886dc 100644 --- a/apps/librarian/lib/modules/borrowers/details/borrower_details.dart +++ b/apps/librarian/lib/modules/members/details/member_details.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/details/contact_card.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrower_details_provider.dart'; -import 'package:librarian_app/modules/borrowers/details/issues_card.dart'; -import 'package:librarian_app/modules/borrowers/details/payments_card.dart'; +import 'package:librarian_app/modules/members/details/contact_card.dart'; +import 'package:librarian_app/modules/members/providers/borrower_details_provider.dart'; +import 'package:librarian_app/modules/members/details/issues_card.dart'; +import 'package:librarian_app/modules/members/details/payments_card.dart'; -class BorrowerDetails extends ConsumerWidget { - const BorrowerDetails({super.key}); +class MemberDetails extends ConsumerWidget { + const MemberDetails({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/apps/librarian/lib/modules/borrowers/details/borrower_details_pane.dart b/apps/librarian/lib/modules/members/details/member_details_pane.dart similarity index 93% rename from apps/librarian/lib/modules/borrowers/details/borrower_details_pane.dart rename to apps/librarian/lib/modules/members/details/member_details_pane.dart index a71acbe..7d41429 100644 --- a/apps/librarian/lib/modules/borrowers/details/borrower_details_pane.dart +++ b/apps/librarian/lib/modules/members/details/member_details_pane.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/edited_borrower_details_providers.dart'; -import 'package:librarian_app/modules/borrowers/details/borrower_details.dart'; +import 'package:librarian_app/modules/members/providers/edited_borrower_details_providers.dart'; +import 'package:librarian_app/modules/members/details/member_details.dart'; import 'package:librarian_app/widgets/dialogs/save_dialog.dart'; import 'package:librarian_app/widgets/panes/pane_header.dart'; import '../../../core/api/models/borrower_model.dart'; -class BorrowerDetailsPane extends ConsumerWidget { +class MemberDetailsPane extends ConsumerWidget { final Future borrowerFuture; - const BorrowerDetailsPane({ + const MemberDetailsPane({ super.key, required this.borrowerFuture, }); @@ -97,7 +97,7 @@ class BorrowerDetailsPane extends ConsumerWidget { child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(16), - child: BorrowerDetails(), + child: MemberDetails(), ), ), ), diff --git a/apps/librarian/lib/modules/borrowers/details/needs_attention_page.dart b/apps/librarian/lib/modules/members/details/needs_attention_page.dart similarity index 58% rename from apps/librarian/lib/modules/borrowers/details/needs_attention_page.dart rename to apps/librarian/lib/modules/members/details/needs_attention_page.dart index 4703146..ba9020f 100644 --- a/apps/librarian/lib/modules/borrowers/details/needs_attention_page.dart +++ b/apps/librarian/lib/modules/members/details/needs_attention_page.dart @@ -4,15 +4,15 @@ import 'package:librarian_app/core/api/models/borrower_model.dart'; import 'needs_attention_view.dart'; class NeedsAttentionPage extends StatelessWidget { - const NeedsAttentionPage({super.key, required this.borrower}); + const NeedsAttentionPage({super.key, required this.member}); - final BorrowerModel borrower; + final BorrowerModel member; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(borrower.name)), - body: NeedsAttentionView(borrower: borrower), + appBar: AppBar(title: Text(member.name)), + body: NeedsAttentionView(member: member), ); } } diff --git a/apps/librarian/lib/modules/borrowers/details/needs_attention_view.dart b/apps/librarian/lib/modules/members/details/needs_attention_view.dart similarity index 85% rename from apps/librarian/lib/modules/borrowers/details/needs_attention_view.dart rename to apps/librarian/lib/modules/members/details/needs_attention_view.dart index 966aa07..0ba5f01 100644 --- a/apps/librarian/lib/modules/borrowers/details/needs_attention_view.dart +++ b/apps/librarian/lib/modules/members/details/needs_attention_view.dart @@ -2,22 +2,22 @@ import 'package:flutter/material.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; class NeedsAttentionView extends StatelessWidget { - final BorrowerModel borrower; + final BorrowerModel member; - const NeedsAttentionView({super.key, required this.borrower}); + const NeedsAttentionView({super.key, required this.member}); @override Widget build(BuildContext context) { - if (borrower.issues.isEmpty) { + if (member.issues.isEmpty) { return const Center(child: Text('Ready to borrow!')); } return Padding( padding: const EdgeInsets.all(8), child: ListView.builder( - itemCount: borrower.issues.length, + itemCount: member.issues.length, itemBuilder: (context, index) { - final reason = borrower.issues[index]; + final reason = member.issues[index]; return Card( child: Padding( diff --git a/apps/librarian/lib/modules/borrowers/details/payments_card.dart b/apps/librarian/lib/modules/members/details/payments_card.dart similarity index 92% rename from apps/librarian/lib/modules/borrowers/details/payments_card.dart rename to apps/librarian/lib/modules/members/details/payments_card.dart index 2ed5aaa..0d2e0bf 100644 --- a/apps/librarian/lib/modules/borrowers/details/payments_card.dart +++ b/apps/librarian/lib/modules/members/details/payments_card.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import 'package:librarian_app/widgets/details_card/card_body.dart'; import 'package:librarian_app/widgets/details_card/card_header.dart'; import 'package:librarian_app/widgets/details_card/details_card.dart'; diff --git a/apps/librarian/lib/modules/borrowers/list/borrowers_list.dart b/apps/librarian/lib/modules/members/list/members_list.dart similarity index 83% rename from apps/librarian/lib/modules/borrowers/list/borrowers_list.dart rename to apps/librarian/lib/modules/members/list/members_list.dart index 1a63110..a3fe5c3 100644 --- a/apps/librarian/lib/modules/borrowers/list/borrowers_list.dart +++ b/apps/librarian/lib/modules/members/list/members_list.dart @@ -3,12 +3,12 @@ import 'package:librarian_app/utils/media_query.dart'; import '../../../core/api/models/borrower_model.dart'; -class BorrowersList extends StatefulWidget { +class MembersList extends StatefulWidget { final List borrowers; final BorrowerModel? selected; final void Function(BorrowerModel borrower)? onTap; - const BorrowersList({ + const MembersList({ super.key, required this.borrowers, this.selected, @@ -16,10 +16,10 @@ class BorrowersList extends StatefulWidget { }); @override - State createState() => _BorrowersListState(); + State createState() => _MembersListState(); } -class _BorrowersListState extends State { +class _MembersListState extends State { final _scrollController = ScrollController(); @override diff --git a/apps/librarian/lib/modules/borrowers/list/borrowers_list_view.dart b/apps/librarian/lib/modules/members/list/members_list_view.dart similarity index 71% rename from apps/librarian/lib/modules/borrowers/list/borrowers_list_view.dart rename to apps/librarian/lib/modules/members/list/members_list_view.dart index bd3a480..a9a79f1 100644 --- a/apps/librarian/lib/modules/borrowers/list/borrowers_list_view.dart +++ b/apps/librarian/lib/modules/members/list/members_list_view.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/edited_borrower_details_providers.dart'; -import 'package:librarian_app/modules/borrowers/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_provider.dart'; +import 'package:librarian_app/modules/members/providers/edited_borrower_details_providers.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import '../../../core/api/models/borrower_model.dart'; -import 'borrowers_list.dart'; +import 'members_list.dart'; -class BorrowersListView extends ConsumerWidget { - const BorrowersListView({super.key, this.onTap}); +class MembersListView extends ConsumerWidget { + const MembersListView({super.key, this.onTap}); final void Function(BorrowerModel)? onTap; @@ -29,7 +29,7 @@ class BorrowersListView extends ConsumerWidget { return const Center(child: Text('No results found')); } - return BorrowersList( + return MembersList( borrowers: snapshot.data!, selected: ref.watch(selectedBorrowerProvider), onTap: (borrower) { diff --git a/apps/librarian/lib/modules/borrowers/list/searchable_borrowers_list.dart b/apps/librarian/lib/modules/members/list/searchable_members_list.dart similarity index 76% rename from apps/librarian/lib/modules/borrowers/list/searchable_borrowers_list.dart rename to apps/librarian/lib/modules/members/list/searchable_members_list.dart index e359ee7..967592c 100644 --- a/apps/librarian/lib/modules/borrowers/list/searchable_borrowers_list.dart +++ b/apps/librarian/lib/modules/members/list/searchable_members_list.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_filter_provider.dart'; -import 'package:librarian_app/modules/borrowers/list/borrowers_list_view.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_filter_provider.dart'; +import 'package:librarian_app/modules/members/list/members_list_view.dart'; import '../../../widgets/fields/submit_text_field.dart'; -class SearchableBorrowersList extends ConsumerWidget { +class SearchableMembersList extends ConsumerWidget { final void Function(BorrowerModel borrower)? onTapBorrower; - const SearchableBorrowersList({ + const SearchableMembersList({ super.key, this.onTapBorrower, }); @@ -35,7 +35,7 @@ class SearchableBorrowersList extends ConsumerWidget { ), ), Expanded( - child: BorrowersListView(onTap: onTapBorrower), + child: MembersListView(onTap: onTapBorrower), ), ], ); diff --git a/apps/librarian/lib/modules/borrowers/payments/dues_dialog.dart b/apps/librarian/lib/modules/members/payments/dues_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/payments/dues_dialog.dart rename to apps/librarian/lib/modules/members/payments/dues_dialog.dart diff --git a/apps/librarian/lib/modules/borrowers/payments/record_payment_dialog.dart b/apps/librarian/lib/modules/members/payments/record_payment_dialog.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/payments/record_payment_dialog.dart rename to apps/librarian/lib/modules/members/payments/record_payment_dialog.dart diff --git a/apps/librarian/lib/modules/borrowers/providers/borrower_details_provider.dart b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart similarity index 72% rename from apps/librarian/lib/modules/borrowers/providers/borrower_details_provider.dart rename to apps/librarian/lib/modules/members/providers/borrower_details_provider.dart index 1d0b820..41b41a8 100644 --- a/apps/librarian/lib/modules/borrowers/providers/borrower_details_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrower_details_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import '../../../core/api/models/borrower_model.dart'; diff --git a/apps/librarian/lib/modules/borrowers/providers/borrowers_filter_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_filter_provider.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/providers/borrowers_filter_provider.dart rename to apps/librarian/lib/modules/members/providers/borrowers_filter_provider.dart diff --git a/apps/librarian/lib/modules/borrowers/providers/borrowers_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart similarity index 73% rename from apps/librarian/lib/modules/borrowers/providers/borrowers_provider.dart rename to apps/librarian/lib/modules/members/providers/borrowers_provider.dart index 0a331a7..dffd8a1 100644 --- a/apps/librarian/lib/modules/borrowers/providers/borrowers_provider.dart +++ b/apps/librarian/lib/modules/members/providers/borrowers_provider.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/core/api/models/borrower_model.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_filter_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_filter_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; final borrowersProvider = Provider>>((ref) async { final searchFilter = ref.watch(borrowersFilterProvider); diff --git a/apps/librarian/lib/modules/borrowers/providers/borrowers_repository_provider.dart b/apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/providers/borrowers_repository_provider.dart rename to apps/librarian/lib/modules/members/providers/borrowers_repository_provider.dart diff --git a/apps/librarian/lib/modules/borrowers/providers/edited_borrower_details_providers.dart b/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart similarity index 80% rename from apps/librarian/lib/modules/borrowers/providers/edited_borrower_details_providers.dart rename to apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart index d166a9d..c9dfd59 100644 --- a/apps/librarian/lib/modules/borrowers/providers/edited_borrower_details_providers.dart +++ b/apps/librarian/lib/modules/members/providers/edited_borrower_details_providers.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrower_details_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/borrowers_repository_provider.dart'; -import 'package:librarian_app/modules/borrowers/providers/selected_borrower_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrower_details_provider.dart'; +import 'package:librarian_app/modules/members/providers/borrowers_repository_provider.dart'; +import 'package:librarian_app/modules/members/providers/selected_borrower_provider.dart'; import 'package:librarian_app/modules/loans/providers/loans_repository_provider.dart'; final phoneProvider = StateProvider((ref) => null); diff --git a/apps/librarian/lib/modules/borrowers/providers/selected_borrower_provider.dart b/apps/librarian/lib/modules/members/providers/selected_borrower_provider.dart similarity index 100% rename from apps/librarian/lib/modules/borrowers/providers/selected_borrower_provider.dart rename to apps/librarian/lib/modules/members/providers/selected_borrower_provider.dart diff --git a/apps/librarian/lib/modules/things/maintenance/view.dart b/apps/librarian/lib/modules/things/maintenance/view.dart index 63c6bcc..b908f29 100644 --- a/apps/librarian/lib/modules/things/maintenance/view.dart +++ b/apps/librarian/lib/modules/things/maintenance/view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:librarian_app/modules/things/maintenance/providers/items.dart'; import 'package:librarian_app/utils/pluralize.dart'; -import 'package:librarian_app/widgets/no_image.dart'; +import 'package:librarian_app/widgets/item_card.dart'; import 'package:skeletonizer/skeletonizer.dart'; import '../providers/item_details_orchestrator.dart'; @@ -122,67 +122,3 @@ class KanbanColumn extends StatelessWidget { ); } } - -class ItemCard extends StatelessWidget { - const ItemCard({ - super.key, - required this.number, - this.imageUrl, - this.notes, - this.onTap, - }); - - final int number; - final String? imageUrl; - final String? notes; - final void Function()? onTap; - - @override - Widget build(BuildContext context) { - return Card( - clipBehavior: Clip.antiAlias, - color: Theme.of(context).colorScheme.secondaryContainer, - child: InkWell( - onTap: onTap, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: Container( - color: Theme.of(context).canvasColor.withOpacity(0.5), - child: imageUrl != null - ? Image.network( - imageUrl!, - fit: BoxFit.cover, - ) - : const NoImage(), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '#$number', - style: Theme.of(context).textTheme.titleMedium, - ), - if (notes != null) - Tooltip( - message: '#$number: $notes', - textStyle: Theme.of(context) - .textTheme - .bodyLarge - ?.copyWith(color: Colors.black, fontSize: 18), - child: const Icon(Icons.info), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/apps/librarian/lib/widgets/item_card.dart b/apps/librarian/lib/widgets/item_card.dart new file mode 100644 index 0000000..659bc91 --- /dev/null +++ b/apps/librarian/lib/widgets/item_card.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import 'no_image.dart'; + +class ItemCard extends StatelessWidget { + const ItemCard({ + super.key, + required this.number, + this.imageUrl, + this.notes, + this.trailing, + this.onTap, + }); + + final int number; + final String? imageUrl; + final String? notes; + final Widget? trailing; + final void Function()? onTap; + + @override + Widget build(BuildContext context) { + return Card( + clipBehavior: Clip.antiAlias, + color: Theme.of(context).colorScheme.secondaryContainer, + child: InkWell( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Container( + color: Theme.of(context).canvasColor.withOpacity(0.5), + child: imageUrl != null + ? Image.network( + imageUrl!, + fit: BoxFit.cover, + ) + : const NoImage(), + ), + ), + Padding( + padding: trailing == null + ? const EdgeInsets.all(8.0) + : const EdgeInsets.only(left: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '#$number', + style: Theme.of(context).textTheme.titleMedium, + ), + if (notes != null) + Tooltip( + message: '#$number: $notes', + textStyle: Theme.of(context) + .textTheme + .bodyLarge + ?.copyWith(color: Colors.black, fontSize: 18), + child: const Icon(Icons.info), + ), + if (trailing != null) trailing!, + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/apps/librarian/pubspec.yaml b/apps/librarian/pubspec.yaml index 0b686ab..74bae62 100644 --- a/apps/librarian/pubspec.yaml +++ b/apps/librarian/pubspec.yaml @@ -10,7 +10,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. -version: 1.0.0+20 +version: 1.0.0+21 environment: sdk: '>=3.0.0'