Skip to content

Commit

Permalink
Merge pull request #82 from pvdthings/dev
Browse files Browse the repository at this point in the history
Release: Item Repair Page
  • Loading branch information
dillonfagan authored Oct 17, 2024
2 parents 65ed8a5 + d4f2fcb commit db7f5f4
Show file tree
Hide file tree
Showing 14 changed files with 275 additions and 3 deletions.
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pvdthings-api",
"version": "1.21.1",
"version": "1.21.2",
"description": "",
"main": "server.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions apps/api/services/inventory/mapItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ function mapItem(record) {

return {
id: record.id,
thingId: record.get('Thing')?.[0],
number: Number(record.get('ID')),
name: record.get('Name')[0],
name_es: record.get('name_es')?.[0],
Expand Down
3 changes: 2 additions & 1 deletion apps/api/services/inventory/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const mapItem = require('./mapItem');
const items = base(Table.Inventory);

const inventoryFields = [
'ID',
'ID',
'Thing',
'Name',
'Brand',
'Description',
Expand Down
4 changes: 4 additions & 0 deletions apps/librarian/lib/core/api/inventory.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
part of 'api.dart';

Future<Response> fetchInventoryItems() async {
return await DioClient.instance.get('/inventory');
}

Future<Response> fetchInventoryItem({required int number}) async {
return await DioClient.instance.get('/inventory/$number');
}
Expand Down
3 changes: 3 additions & 0 deletions apps/librarian/lib/core/api/models/item_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'manual_model.dart';
class ItemModel {
ItemModel({
required this.id,
required this.thingId,
required this.number,
required this.name,
required this.available,
Expand All @@ -20,6 +21,7 @@ class ItemModel {
});

final String id;
final String thingId;
final int number;
final String name;
final String? description;
Expand All @@ -43,6 +45,7 @@ class ItemModel {
factory ItemModel.fromJson(Map<String, dynamic> json) {
return ItemModel(
id: json['id'] as String,
thingId: json['thingId'] as String,
number: json['number'] as int,
name: json['name'] as String? ?? 'Unknown Thing',
description: json['description'] as String?,
Expand Down
6 changes: 6 additions & 0 deletions apps/librarian/lib/core/data/inventory_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ class InventoryRepository extends Notifier<Future<List<ThingModel>>> {
return DetailedThingModel.fromJson(response.data as Map<String, dynamic>);
}

Future<List<ItemModel>> getItems() async {
final response = await api.fetchInventoryItems();
final objects = response.data as List;
return objects.map((e) => ItemModel.fromJson(e)).toList();
}

Future<ItemModel?> getItem({required int number}) async {
try {
final response = await api.fetchInventoryItem(number: number);
Expand Down
6 changes: 6 additions & 0 deletions apps/librarian/lib/dashboard/pages/dashboard_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:librarian_app/modules/borrowers/list/searchable_borrowers_list.d
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';
import 'package:librarian_app/modules/things/maintenance/view.dart';
import 'package:librarian_app/modules/things/details/inventory_details_page.dart';
import 'package:librarian_app/modules/things/details/inventory/inventory_list/searchable_inventory_list.dart';
import 'package:librarian_app/modules/things/create/create_thing_dialog.dart';
Expand Down Expand Up @@ -94,6 +95,11 @@ class _DashboardPageState extends ConsumerState<DashboardPage> {
},
),
),
const DashboardModule(
title: 'Item Repair',
desktopLayout: MaintenanceView(),
mobileLayout: null,
),
const DashboardModule(
title: 'Actions',
desktopLayout: librarian_actions.Actions(),
Expand Down
6 changes: 6 additions & 0 deletions apps/librarian/lib/dashboard/widgets/desktop_dashboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class DesktopDashboard extends StatelessWidget {
label: Text('Things'),
padding: EdgeInsets.symmetric(vertical: 8),
),
NavigationRailDestination(
selectedIcon: Icon(Icons.healing),
icon: Icon(Icons.healing_outlined),
label: Text('Repair'),
padding: EdgeInsets.symmetric(vertical: 8),
),
NavigationRailDestination(
selectedIcon: Icon(Icons.electric_bolt),
icon: Icon(Icons.electric_bolt_outlined),
Expand Down
50 changes: 50 additions & 0 deletions apps/librarian/lib/modules/things/maintenance/providers/items.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:librarian_app/core/api/models/item_model.dart';
import 'package:librarian_app/modules/things/providers/items.dart';
import 'package:librarian_app/modules/things/providers/things_repository_provider.dart';

final items = Provider((ref) async {
final items = await ref.watch(allItems);
final things = (await ref.watch(thingsRepositoryProvider));
final hiddenMap = {for (final e in things) e.id: e.hidden};

return RepairItemsViewModel(
damagedItems: items
.where((item) => item.condition == damaged)
.map((item) => RepairItemModel(
item: item,
isThingHidden: hiddenMap[item.thingId],
))
.toList(),
inRepairItems: items
.where((item) => item.condition == inRepair)
.map((item) => RepairItemModel(
item: item,
isThingHidden: hiddenMap[item.thingId],
))
.toList(),
);
});

const damaged = 'Damaged';
const inRepair = 'In Repair';

class RepairItemsViewModel {
const RepairItemsViewModel({
required this.damagedItems,
required this.inRepairItems,
});

final List<RepairItemModel> damagedItems;
final List<RepairItemModel> inRepairItems;
}

class RepairItemModel {
const RepairItemModel({
required this.item,
this.isThingHidden,
});

final ItemModel item;
final bool? isThingHidden;
}
169 changes: 169 additions & 0 deletions apps/librarian/lib/modules/things/maintenance/view.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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:skeletonizer/skeletonizer.dart';

import '../providers/item_details_orchestrator.dart';

class MaintenanceView extends ConsumerWidget {
const MaintenanceView({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
return FutureBuilder(
future: ref.watch(items),
builder: (context, snapshot) {
final damagedItems = snapshot.data?.damagedItems ?? [];
final inRepairItems = snapshot.data?.inRepairItems ?? [];

return Skeletonizer(
enabled: snapshot.connectionState == ConnectionState.waiting,
child: MaintenanceKanban(
damagedItems: damagedItems,
inRepairItems: inRepairItems,
onTapItem: (item) {
final orchestrator = ref.read(itemDetailsOrchestrator);
orchestrator.openItem(
context,
item: item.item,
hiddenLocked: item.isThingHidden ?? false,
);
},
),
);
},
);
}
}

class MaintenanceKanban extends StatelessWidget {
const MaintenanceKanban({
super.key,
required this.damagedItems,
required this.inRepairItems,
this.onTapItem,
});

final List<RepairItemModel> damagedItems;
final List<RepairItemModel> inRepairItems;
final void Function(RepairItemModel)? onTapItem;

@override
Widget build(BuildContext context) {
return GridView.count(
childAspectRatio: 1 / 1,
crossAxisCount: 2,
crossAxisSpacing: 4,
children: [
KanbanColumn(
title: 'Damaged',
items: damagedItems,
onTapItem: onTapItem,
),
KanbanColumn(
title: 'In Repair',
items: inRepairItems,
onTapItem: onTapItem,
),
],
);
}
}

class KanbanColumn extends StatelessWidget {
const KanbanColumn({
super.key,
required this.title,
required this.items,
this.onTapItem,
});

final String title;
final List<RepairItemModel> items;
final void Function(RepairItemModel)? onTapItem;

@override
Widget build(BuildContext context) {
return Card.outlined(
clipBehavior: Clip.antiAlias,
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
trailing: Text(pluralize(items.length, 'Item')),
),
const Divider(height: 1),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.count(
crossAxisCount: 4,
children: items
.map((item) => ItemCard(
number: item.item.number,
imageUrl: item.item.imageUrls.firstOrNull,
onTap: () => onTapItem?.call(item),
))
.toList(),
),
),
),
],
),
);
}
}

class ItemCard extends StatelessWidget {
const ItemCard({
super.key,
required this.number,
this.imageUrl,
this.onTap,
});

final int number;
final String? imageUrl;
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: Text(
'#$number',
style: Theme.of(context).textTheme.titleMedium,
),
),
],
),
),
);
}
}
8 changes: 8 additions & 0 deletions apps/librarian/lib/modules/things/providers/items.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'things_repository_provider.dart';

final allItems = Provider((ref) async {
ref.watch(thingsRepositoryProvider);
return await ref.read(thingsRepositoryProvider.notifier).getItems();
});
9 changes: 9 additions & 0 deletions apps/librarian/lib/utils/pluralize.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
String pluralize(int count, String word) {
var string = '$count $word';

if (count > 1 || count == 0) {
string += 's';
}

return string;
}
8 changes: 8 additions & 0 deletions apps/librarian/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
skeletonizer:
dependency: "direct main"
description:
name: skeletonizer
sha256: "3b202e4fa9c49b017d368fb0e570d4952bcd19972b67b2face071bdd68abbfae"
url: "https://pub.dev"
source: hosted
version: "1.4.2"
sky_engine:
dependency: transitive
description: flutter
Expand Down
3 changes: 2 additions & 1 deletion apps/librarian/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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+18
version: 1.0.0+19

environment:
sdk: '>=3.0.0'
Expand Down Expand Up @@ -38,6 +38,7 @@ dependencies:
riverpod_annotation: ^2.1.6
collection: ^1.17.2
url_launcher: ^6.2.5
skeletonizer: ^1.4.2

dev_dependencies:
flutter_test:
Expand Down

0 comments on commit db7f5f4

Please sign in to comment.