Skip to content

Commit

Permalink
Merge pull request #51 from pvdthings/librarian-ux-improvements
Browse files Browse the repository at this point in the history
Librarian UX Improvements
  • Loading branch information
dillonfagan authored Aug 14, 2024
2 parents e0e30b0 + 7715fa5 commit 58e3eb4
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 203 deletions.
69 changes: 44 additions & 25 deletions apps/librarian/lib/core/api/models/issue_model.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
class Issue {
final IssueType type;
final String title;
final String okTitle;
final String? explanation;
final String? okExplanation;
final String? instructions;
final String? graphicUrl;

const Issue({
required this.title,
required this.okTitle,
this.explanation,
this.okExplanation,
this.instructions,
this.graphicUrl,
required this.type,
Expand All @@ -18,34 +22,49 @@ class Issue {
}

static final _reasonMap = <String, Issue>{
'duesNotPaid': const Issue(
title: "Dues Not Paid",
explanation: "Annual dues must be paid before borrowing.",
instructions:
"The borrower can pay their dues from the library's website or by scanning the QR code.",
graphicUrl: "qr_givebutter.png",
type: IssueType.duesNotPaid,
),
'overdueLoan': const Issue(
title: "Overdue Loan",
explanation:
"All overdue items must be returned before they can borrow again.",
type: IssueType.overdueLoan,
),
'suspended': const Issue(
title: "Suspended",
explanation: "This person has been suspended from borrowing.",
type: IssueType.suspended,
),
'needsLiabilityWaiver': const Issue(
title: "Needs Liability Waiver",
explanation:
"This borrower needs to sign our library's liability waiver before they can check anything out.",
type: IssueType.needsLiabilityWaiver,
),
'duesNotPaid': duesNotPaidIssue,
'overdueLoan': overdueLoanIssue,
'suspended': suspendedIssue,
'needsLiabilityWaiver': needsLiabilityWaiverIssue,
};
}

const duesNotPaidIssue = Issue(
title: "Dues Not Paid",
okTitle: "Dues Paid",
explanation: "Annual dues must be paid before borrowing.",
okExplanation: "Annual dues have been paid.",
instructions:
"The borrower can pay their dues from the library's website or by scanning the QR code.",
graphicUrl: "qr_givebutter.png",
type: IssueType.duesNotPaid,
);

const overdueLoanIssue = Issue(
title: "Overdue Loan",
okTitle: "No Overdue Loans",
explanation: "All overdue items must be returned before borrowing again.",
okExplanation: "All loans have been returned.",
type: IssueType.overdueLoan,
);

const needsLiabilityWaiverIssue = Issue(
title: "Needs Liability Waiver",
okTitle: "Liability Waiver Signed",
explanation:
"A liability waiver must be signed before checking anything out.",
okExplanation: "Liability waived!",
type: IssueType.needsLiabilityWaiver,
);

const suspendedIssue = Issue(
title: "Suspended",
okTitle: "In Good Standing",
explanation: "This person is no longer a member of the library.",
okExplanation: "This member is in good standing.",
type: IssueType.suspended,
);

enum IssueType {
duesNotPaid,
overdueLoan,
Expand Down
12 changes: 6 additions & 6 deletions apps/librarian/lib/core/data/borrowers_repository.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/core/api/api.dart';
import 'package:librarian_app/core/api/api.dart' as api;
import 'package:librarian_app/core/api/models/payment_model.dart';

import '../api/models/borrower_model.dart';
Expand All @@ -10,7 +10,7 @@ class BorrowersRepository extends Notifier<Future<List<BorrowerModel>>> {
Future<List<BorrowerModel>> build() async => await getBorrowers();

Future<List<BorrowerModel>> getBorrowers() async {
final response = await fetchBorrowers();
final response = await api.fetchBorrowers();
return (response.data as List)
.map((json) => BorrowerModel.fromJson(json))
.toList();
Expand All @@ -22,13 +22,13 @@ class BorrowersRepository extends Notifier<Future<List<BorrowerModel>>> {
}

Future<BorrowerModel?> getBorrowerDetails(String id) async {
final response = await fetchBorrower(id);
final response = await api.fetchBorrower(id);
return BorrowerModel.fromJson(response.data as Map<String, dynamic>);
}

Future<bool> updateBorrower(String id, {String? email, String? phone}) async {
try {
await updateBorrower(id, email: email, phone: phone);
await api.updateBorrower(id, email: email, phone: phone);

ref.invalidateSelf();
return true;
Expand All @@ -38,7 +38,7 @@ class BorrowersRepository extends Notifier<Future<List<BorrowerModel>>> {
}

Future<List<PaymentModel>> getPayments(String borrowerId) async {
final response = await fetchPayments(borrowerId: borrowerId);
final response = await api.fetchPayments(borrowerId: borrowerId);
return (response.data as List)
.map((e) => PaymentModel.fromJson(e))
.toList();
Expand All @@ -49,7 +49,7 @@ class BorrowersRepository extends Notifier<Future<List<BorrowerModel>>> {
required double cash,
}) async {
try {
await recordCashPayment(
await api.recordCashPayment(
cash: cash,
borrowerId: borrowerId,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BorrowerDetails extends ConsumerWidget {
final borrower = snapshot.data!;

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ContactCard(
name: borrower.name,
Expand Down
130 changes: 94 additions & 36 deletions apps/librarian/lib/modules/borrowers/details/borrower_issues.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,103 @@ class BorrowerIssues extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
return ListView.builder(
itemCount: issues.length,
itemBuilder: (context, index) {
final issue = issues[index];

return ListTile(
leading: const Icon(
Icons.warning_rounded,
color: Colors.amber,
return ListView(
shrinkWrap: true,
children: [
_IssueTile(
duesNotPaidIssue,
isOk: !issues.contains(duesNotPaidIssue),
trailing: _PayDuesButton(
borrowerId: borrowerId,
onRecordCashPayment: onRecordCashPayment,
),
title: Text(issue.title),
subtitle: issue.explanation != null ? Text(issue.explanation!) : null,
trailing: issue.type == IssueType.duesNotPaid
? OutlinedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return DuesNotPaidDialog(
instructions: issue.instructions!,
imageUrl: issue.graphicUrl,
onConfirmPayment: (cash) async {
final result = await ref
.read(borrowersRepositoryProvider.notifier)
.recordPayment(
borrowerId: borrowerId, cash: cash);

onRecordCashPayment(result);
},
);
},
);
},
child: const Text('Fix'),
)
: null,
),
_IssueTile(
overdueLoanIssue,
isOk: !issues.contains(overdueLoanIssue),
),
_IssueTile(
needsLiabilityWaiverIssue,
isOk: !issues.contains(needsLiabilityWaiverIssue),
),
_IssueTile(
suspendedIssue,
isOk: !issues.contains(suspendedIssue),
),
],
);
}
}

class _IssueTile extends StatelessWidget {
const _IssueTile(
this.issue, {
this.isOk = false,
this.trailing,
});

final Issue issue;
final bool isOk;
final Widget? trailing;

@override
Widget build(BuildContext context) {
if (isOk) {
return ListTile(
leading: const Icon(
Icons.check,
color: Colors.green,
),
title: Text(issue.okTitle),
subtitle:
issue.okExplanation != null ? Text(issue.okExplanation!) : null,
);
}

return ListTile(
leading: const Icon(
Icons.warning_rounded,
color: Colors.amber,
),
title: Text(issue.title),
subtitle: issue.explanation != null ? Text(issue.explanation!) : null,
trailing: trailing,
);
}
}

class _PayDuesButton extends ConsumerWidget {
const _PayDuesButton({
required this.borrowerId,
required this.onRecordCashPayment,
});

final String borrowerId;
final void Function(bool success) onRecordCashPayment;

@override
Widget build(BuildContext context, WidgetRef ref) {
const issue = duesNotPaidIssue;
return OutlinedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return DuesNotPaidDialog(
instructions: issue.instructions!,
imageUrl: issue.graphicUrl,
onConfirmPayment: (cash) async {
final result = await ref
.read(borrowersRepositoryProvider.notifier)
.recordPayment(borrowerId: borrowerId, cash: cash);

onRecordCashPayment(result);
},
);
},
);
},
shrinkWrap: true,
child: const Text('Pay Dues'),
);
}
}
10 changes: 2 additions & 8 deletions apps/librarian/lib/modules/borrowers/details/contact_card.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/widgets/details_card/card_header.dart';
import 'package:librarian_app/widgets/details_card/card_body.dart';
import 'package:librarian_app/widgets/details_card/details_card.dart';

import '../providers/edited_borrower_details_providers.dart';
Expand All @@ -21,10 +21,7 @@ class ContactCard extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return DetailsCard(
header: const CardHeader(title: 'Contact Details'),
showDivider: true,
body: Padding(
padding: const EdgeInsets.all(16.0),
body: CardBody(
child: Column(
children: [
TextField(
Expand All @@ -34,7 +31,6 @@ class ContactCard extends ConsumerWidget {
icon: Icon(Icons.person_rounded),
labelText: 'Name',
border: OutlineInputBorder(),
constraints: BoxConstraints(maxWidth: 500),
),
),
const SizedBox(height: 16),
Expand All @@ -46,7 +42,6 @@ class ContactCard extends ConsumerWidget {
icon: Icon(Icons.email_rounded),
labelText: 'Email',
border: OutlineInputBorder(),
constraints: BoxConstraints(maxWidth: 500),
),
onChanged: (value) {
ref.read(emailProvider.notifier).state = value;
Expand All @@ -61,7 +56,6 @@ class ContactCard extends ConsumerWidget {
icon: Icon(Icons.phone_rounded),
labelText: 'Phone',
border: OutlineInputBorder(),
constraints: BoxConstraints(maxWidth: 500),
),
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
Expand Down
25 changes: 10 additions & 15 deletions apps/librarian/lib/modules/borrowers/details/issues_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,16 @@ class IssuesCard extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return DetailsCard(
header: const CardHeader(title: 'Issues'),
showDivider: issues.isNotEmpty,
body: Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: BorrowerIssues(
borrowerId: borrowerId,
issues: issues,
onRecordCashPayment: (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(success ? 'Success!' : 'Failed to record payment'),
),
);
},
),
body: BorrowerIssues(
borrowerId: borrowerId,
issues: issues,
onRecordCashPayment: (success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success ? 'Success!' : 'Failed to record payment'),
),
);
},
),
);
}
Expand Down
Loading

0 comments on commit 58e3eb4

Please sign in to comment.