feat: Implements visual status for pickings

Introduces an `isDone` extension property for stock picking records to simplify status checks.

Enhances the `StockPickingCard` to visually represent the picking's completion status with a distinct icon and color (green for 'done', grey for 'in progress'), improving clarity at a glance. Information row values are now consistently uppercased.

Conditionally displays scan-related actions (QuickActionComponent and FloatingActionButton) in the reception details page, making them available only when the picking is not yet completed. This guides users by preventing interactions on finished operations.
This commit is contained in:
mandreshope 2025-07-30 10:49:51 +03:00
parent 61047f266d
commit d09d8ace8c
4 changed files with 101 additions and 64 deletions

View File

@ -100,3 +100,7 @@ abstract class StockPickingTypeModel with _$StockPickingTypeModel {
factory StockPickingTypeModel.fromJson(Map<String, dynamic> json) =>
_$StockPickingTypeModelFromJson(json);
}
extension StockPickingRecordModelExt on StockPickingRecordModel {
bool get isDone => state == "done";
}

View File

@ -10,8 +10,10 @@ class StockPickingCard extends StatelessWidget {
required this.contact,
required this.origin,
required this.status,
required this.isDone,
this.margin,
});
final bool isDone;
final String? reference;
final String? from;
final String? to;
@ -48,17 +50,46 @@ class StockPickingCard extends StatelessWidget {
],
),
const SizedBox(height: 12),
_infoStateRow(isDone, "Statut", status),
_infoRow(Icons.call_made, "De", from),
_infoRow(Icons.call_received, "Vers", to),
_infoRow(Icons.person, "Contact", contact),
_infoRow(Icons.insert_drive_file, "Document dorigine", origin),
_infoRow(Icons.check_circle, "Statut", status),
],
),
),
);
}
Widget _infoStateRow(bool isDone, String label, String? value) {
var color = Colors.grey[700];
var icon = Icons.refresh;
if (isDone) {
icon = Icons.check_circle;
color = Colors.green;
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
Icon(icon, size: 20, color: color),
const SizedBox(width: 8),
Text(
"$label : ",
style: TextStyle(fontWeight: FontWeight.bold, color: color),
),
Expanded(
child: Text(
(value ?? "-").toUpperCase(),
style: TextStyle(color: color, fontWeight: FontWeight.bold),
),
),
],
),
);
}
Widget _infoRow(IconData icon, String label, String? value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
@ -72,7 +103,7 @@ class StockPickingCard extends StatelessWidget {
),
Expanded(
child: Text(
value ?? "-",
(value ?? "-").toUpperCase(),
style: const TextStyle(color: Colors.black87),
),
),

View File

@ -1,3 +1,4 @@
import 'package:e_scan/backend/schema/stock_picking/stock_picking_model.dart';
import 'package:e_scan/components/components.dart';
import 'package:e_scan/pages/operation/reception/reception_details_page_model.dart';
import 'package:e_scan/router/go_secure_router_builder.dart';
@ -43,13 +44,16 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
child: ListView(
children: [
const SizedBox(height: 16),
QuickActionComponent(
onTapScan: () {
ScannerRoute().push(context);
},
),
const SizedBox(height: 16),
if (reception?.isDone == false) ...[
QuickActionComponent(
onTapScan: () {
ScannerRoute().push(context);
},
),
const SizedBox(height: 16),
],
StockPickingCard(
isDone: reception?.isDone == true,
margin: EdgeInsets.symmetric(horizontal: 5),
reference: reception?.name ?? '',
from: reception?.locationId?.completeName,
@ -61,6 +65,13 @@ class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
],
),
),
floatingActionButton: reception?.isDone == false
? FloatingActionButton(
backgroundColor: AppTheme.of(context).primary,
onPressed: () {},
child: Icon(Icons.qr_code, color: AppTheme.of(context).white),
)
: null,
);
}
}

View File

@ -52,63 +52,54 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
if (state.loadingReceptions) {
return Center(child: LoadingProgressComponent());
}
return Card(
color: AppTheme.of(context).secondaryBackground,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(color: AppTheme.of(context).alternate),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
spacing: 10,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Réceptions',
style: AppTheme.of(
context,
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
),
state.receptions?.result?.records?.isEmpty == true
? Text('No data')
: ListView.builder(
physics: NeverScrollableScrollPhysics(),
primary: true,
shrinkWrap: true,
itemCount:
(state.receptions?.result?.records ??
<StockPickingRecordModel>[])
.length,
itemBuilder: (context, index) {
final reception =
state.receptions?.result?.records![index];
return GestureDetector(
onTap: () {
final id = reception?.id;
if (id == null) return;
ReceptionDetailsRoute(
receptionId: id,
).push(context);
},
child: StockPickingCard(
reference: reception?.name ?? '',
from: reception?.locationId?.completeName,
to: reception
?.locationDestId
?.completeName,
contact:
reception?.partnerId?.displayName,
origin: reception?.origin,
status: reception?.state,
),
);
return Column(
spacing: 10,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(
// 'Réceptions',
// style: AppTheme.of(
// context,
// ).bodyMedium.copyWith(fontWeight: FontWeight.bold),
// ),
state.receptions?.result?.records?.isEmpty == true
? Text('No data')
: ListView.builder(
physics: NeverScrollableScrollPhysics(),
primary: true,
shrinkWrap: true,
itemCount:
(state.receptions?.result?.records ??
<StockPickingRecordModel>[])
.length,
itemBuilder: (context, index) {
final reception =
state.receptions?.result?.records![index];
return GestureDetector(
onTap: () {
final id = reception?.id;
if (id == null) return;
ReceptionDetailsRoute(
receptionId: id,
).push(context);
},
),
],
),
),
child: StockPickingCard(
margin: EdgeInsets.symmetric(
horizontal: 5,
vertical: 5,
),
isDone: reception?.isDone == true,
reference: reception?.name ?? '',
from: reception?.locationId?.completeName,
to: reception?.locationDestId?.completeName,
contact: reception?.partnerId?.displayName,
origin: reception?.origin,
status: reception?.state,
),
);
},
),
],
);
},
),