feat: Adds session management and reception detail view
Updates the sign-in API endpoint to `/simpos/v1/sign_in`. Captures the `set-cookie` header from successful sign-in responses and stores it as a `sessionId`. Persists the session ID using secure storage and includes it in subsequent API requests via the `Cookie` header for improved session management. Extends the `AuthModel` to include the new `sessionId` field. Enables navigation from the reception list to a new reception details page, passing the selected reception's ID. Refactors the `StockPickingCard` into a dedicated component and adds loading indicators to the reception list.
This commit is contained in:
parent
41a660db9a
commit
9435907c28
@ -44,7 +44,7 @@ class ApiCalls {
|
||||
}) async {
|
||||
try {
|
||||
final response = await dioService.post(
|
||||
path: '/sign_in',
|
||||
path: '/simpos/v1/sign_in',
|
||||
data: {
|
||||
"params": {
|
||||
"login": email,
|
||||
@ -56,7 +56,14 @@ class ApiCalls {
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data;
|
||||
if (data['result']['success'] == true) {
|
||||
return Result.success(AuthModel.fromJson(data['result']['data']));
|
||||
String? cookie;
|
||||
if (response.headers.map.containsKey('set-cookie')) {
|
||||
cookie = response.headers.map['set-cookie']?.firstOrNull;
|
||||
}
|
||||
final auth = AuthModel.fromJson(
|
||||
data['result']['data'],
|
||||
).copyWith(sessionId: cookie);
|
||||
return Result.success(auth);
|
||||
} else {
|
||||
return Result.error(Error(data['result']['success']));
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ abstract class AuthModel with _$AuthModel {
|
||||
String? refreshToken,
|
||||
String? name,
|
||||
String? username,
|
||||
String? sessionId,
|
||||
}) = _AuthModel;
|
||||
|
||||
factory AuthModel.fromJson(Map<String, dynamic> json) =>
|
||||
|
@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$AuthModel {
|
||||
|
||||
@JsonKey(name: 'access_token') String? get accessToken;@JsonKey(name: 'db_name') String? get dbName; int? get uid; String? get refreshToken; String? get name; String? get username;
|
||||
@JsonKey(name: 'access_token') String? get accessToken;@JsonKey(name: 'db_name') String? get dbName; int? get uid; String? get refreshToken; String? get name; String? get username; String? get sessionId;
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@ -27,16 +27,16 @@ $AuthModelCopyWith<AuthModel> get copyWith => _$AuthModelCopyWithImpl<AuthModel>
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username);
|
||||
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username,sessionId);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthModel(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username)';
|
||||
return 'AuthModel(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username, sessionId: $sessionId)';
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ abstract mixin class $AuthModelCopyWith<$Res> {
|
||||
factory $AuthModelCopyWith(AuthModel value, $Res Function(AuthModel) _then) = _$AuthModelCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username
|
||||
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username, String? sessionId
|
||||
});
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ class _$AuthModelCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,Object? sessionId = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dbName: freezed == dbName ? _self.dbName : dbName // ignore: cast_nullable_to_non_nullable
|
||||
@ -72,6 +72,7 @@ as String?,uid: freezed == uid ? _self.uid : uid // ignore: cast_nullable_to_non
|
||||
as int?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String?,username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
|
||||
as String?,sessionId: freezed == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
@ -83,7 +84,7 @@ as String?,
|
||||
@JsonSerializable(createToJson: false)
|
||||
|
||||
class _AuthModel implements AuthModel {
|
||||
_AuthModel({@JsonKey(name: 'access_token') this.accessToken, @JsonKey(name: 'db_name') this.dbName, this.uid, this.refreshToken, this.name, this.username});
|
||||
_AuthModel({@JsonKey(name: 'access_token') this.accessToken, @JsonKey(name: 'db_name') this.dbName, this.uid, this.refreshToken, this.name, this.username, this.sessionId});
|
||||
factory _AuthModel.fromJson(Map<String, dynamic> json) => _$AuthModelFromJson(json);
|
||||
|
||||
@override@JsonKey(name: 'access_token') final String? accessToken;
|
||||
@ -92,6 +93,7 @@ class _AuthModel implements AuthModel {
|
||||
@override final String? refreshToken;
|
||||
@override final String? name;
|
||||
@override final String? username;
|
||||
@override final String? sessionId;
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@ -103,16 +105,16 @@ _$AuthModelCopyWith<_AuthModel> get copyWith => __$AuthModelCopyWithImpl<_AuthMo
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthModel&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.dbName, dbName) || other.dbName == dbName)&&(identical(other.uid, uid) || other.uid == uid)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.name, name) || other.name == name)&&(identical(other.username, username) || other.username == username)&&(identical(other.sessionId, sessionId) || other.sessionId == sessionId));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username);
|
||||
int get hashCode => Object.hash(runtimeType,accessToken,dbName,uid,refreshToken,name,username,sessionId);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthModel(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username)';
|
||||
return 'AuthModel(accessToken: $accessToken, dbName: $dbName, uid: $uid, refreshToken: $refreshToken, name: $name, username: $username, sessionId: $sessionId)';
|
||||
}
|
||||
|
||||
|
||||
@ -123,7 +125,7 @@ abstract mixin class _$AuthModelCopyWith<$Res> implements $AuthModelCopyWith<$Re
|
||||
factory _$AuthModelCopyWith(_AuthModel value, $Res Function(_AuthModel) _then) = __$AuthModelCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username
|
||||
@JsonKey(name: 'access_token') String? accessToken,@JsonKey(name: 'db_name') String? dbName, int? uid, String? refreshToken, String? name, String? username, String? sessionId
|
||||
});
|
||||
|
||||
|
||||
@ -140,7 +142,7 @@ class __$AuthModelCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = freezed,Object? dbName = freezed,Object? uid = freezed,Object? refreshToken = freezed,Object? name = freezed,Object? username = freezed,Object? sessionId = freezed,}) {
|
||||
return _then(_AuthModel(
|
||||
accessToken: freezed == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,dbName: freezed == dbName ? _self.dbName : dbName // ignore: cast_nullable_to_non_nullable
|
||||
@ -148,6 +150,7 @@ as String?,uid: freezed == uid ? _self.uid : uid // ignore: cast_nullable_to_non
|
||||
as int?,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable
|
||||
as String?,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String?,username: freezed == username ? _self.username : username // ignore: cast_nullable_to_non_nullable
|
||||
as String?,sessionId: freezed == sessionId ? _self.sessionId : sessionId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
@ -13,4 +13,5 @@ _AuthModel _$AuthModelFromJson(Map<String, dynamic> json) => _AuthModel(
|
||||
refreshToken: json['refreshToken'] as String?,
|
||||
name: json['name'] as String?,
|
||||
username: json['username'] as String?,
|
||||
sessionId: json['sessionId'] as String?,
|
||||
);
|
||||
|
@ -5,3 +5,4 @@ export 'product_scanned_component.dart';
|
||||
export 'outline_button_component.dart';
|
||||
export 'quick_action_component.dart';
|
||||
export 'main_appbar_component.dart';
|
||||
export 'stock_picking_card_component.dart';
|
81
lib/components/stock_picking_card_component.dart
Normal file
81
lib/components/stock_picking_card_component.dart
Normal file
@ -0,0 +1,81 @@
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class StockPickingCard extends StatelessWidget {
|
||||
const StockPickingCard({
|
||||
super.key,
|
||||
required this.reference,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.contact,
|
||||
required this.origin,
|
||||
required this.status,
|
||||
});
|
||||
final String? reference;
|
||||
final String? from;
|
||||
final String? to;
|
||||
final String? contact;
|
||||
final String? origin;
|
||||
final String? status;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 3,
|
||||
color: AppTheme.of(context).primaryBackground,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.receipt_long, color: Colors.blue),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
reference ?? 'Référence inconnue',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_infoRow(Icons.call_made, "De", from),
|
||||
_infoRow(Icons.call_received, "Vers", to),
|
||||
_infoRow(Icons.person, "Contact", contact),
|
||||
_infoRow(Icons.insert_drive_file, "Document d’origine", origin),
|
||||
_infoRow(Icons.check_circle, "Statut", status),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(IconData icon, String label, String? value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 20, color: Colors.grey[700]),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
"$label : ",
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value ?? "-",
|
||||
style: const TextStyle(color: Colors.black87),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -66,7 +66,7 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
|
||||
final res = await ApiCalls.signIn(email: email, password: password);
|
||||
res.when(
|
||||
(auth) async {
|
||||
setTokenInLocal(auth.accessToken ?? '', auth);
|
||||
setTokenInLocal(auth);
|
||||
onSuccess?.call();
|
||||
},
|
||||
(error) {
|
||||
@ -87,10 +87,10 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setTokenInLocal(String token, AuthModel auth) async {
|
||||
Future<void> setTokenInLocal(AuthModel auth) async {
|
||||
await Future.wait([
|
||||
tokenProvider.setToken(token),
|
||||
tokenProvider.setRefreshToken(token),
|
||||
tokenProvider.setToken(auth.accessToken ?? ''),
|
||||
tokenProvider.setRefreshToken(auth.accessToken ?? ''),
|
||||
userConnectedProvider.set(
|
||||
UserStruct(
|
||||
id: auth.uid.toString(),
|
||||
@ -102,7 +102,7 @@ class LoginPageModel extends StateNotifier<LoginPageState> {
|
||||
]);
|
||||
|
||||
state = state.copyWith(loading: false, status: LoginPageStateStatus.logged);
|
||||
debugPrint(token);
|
||||
debugPrint(auth.accessToken);
|
||||
}
|
||||
|
||||
Future<void> logOut() async {
|
||||
|
@ -1,3 +1,4 @@
|
||||
export 'reception/reception_page.dart';
|
||||
export 'delivery/delivery_page.dart';
|
||||
export 'inventory/inventory_page.dart';
|
||||
export 'reception/reception_details_page.dart';
|
||||
|
108
lib/pages/operation/reception/reception_details_page.dart
Normal file
108
lib/pages/operation/reception/reception_details_page.dart
Normal file
@ -0,0 +1,108 @@
|
||||
import 'package:barcode_scanner/backend/schema/stock_picking/stock_picking_model.dart';
|
||||
import 'package:barcode_scanner/components/components.dart';
|
||||
import 'package:barcode_scanner/pages/operation/reception/reception_page_model.dart';
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ReceptionDetailsPage extends ConsumerStatefulWidget {
|
||||
const ReceptionDetailsPage({super.key, required this.receptionId});
|
||||
final int receptionId;
|
||||
|
||||
@override
|
||||
ConsumerState<ReceptionDetailsPage> createState() =>
|
||||
_ReceptionDetailsPageState();
|
||||
}
|
||||
|
||||
class _ReceptionDetailsPageState extends ConsumerState<ReceptionDetailsPage> {
|
||||
final globalKey = GlobalKey<ScaffoldState>();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
ref.read(receptionPageModelProvider.notifier).getUserConnected();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: globalKey,
|
||||
drawer: DrawerComponent(isOperationExpanded: true),
|
||||
appBar: MainAppbarComponent(
|
||||
scaffoledKey: globalKey,
|
||||
title: "Réceptions",
|
||||
subTitle: "Opérations d'Entrepôt",
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
QuickActionComponent(),
|
||||
const SizedBox(height: 16),
|
||||
Consumer(
|
||||
builder: (_, WidgetRef ref, _) {
|
||||
final state = ref.watch(receptionPageModelProvider);
|
||||
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 record =
|
||||
state.receptions?.result?.records![index];
|
||||
return GestureDetector(
|
||||
onTap: () {},
|
||||
child: StockPickingCard(
|
||||
reference: record?.name ?? '',
|
||||
from: record?.locationId?.completeName,
|
||||
to: record?.locationDestId?.completeName,
|
||||
contact: record?.partnerId?.displayName,
|
||||
origin: record?.origin,
|
||||
status: record?.state,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'package:barcode_scanner/backend/schema/stock_picking/stock_picking_model.dart';
|
||||
import 'package:barcode_scanner/components/components.dart';
|
||||
import 'package:barcode_scanner/pages/operation/reception/reception_page_model.dart';
|
||||
import 'package:barcode_scanner/router/go_secure_router_builder.dart';
|
||||
import 'package:barcode_scanner/themes/app_theme.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -25,7 +26,6 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = ref.watch(receptionPageModelProvider);
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.of(context).primaryBackground,
|
||||
key: globalKey,
|
||||
@ -42,50 +42,71 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
|
||||
const SizedBox(height: 16),
|
||||
QuickActionComponent(),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
color: AppTheme.of(context).primaryBackground,
|
||||
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 récentes',
|
||||
style: AppTheme.of(
|
||||
context,
|
||||
).bodyMedium.copyWith(fontWeight: FontWeight.bold),
|
||||
Consumer(
|
||||
builder: (_, WidgetRef ref, _) {
|
||||
final state = ref.watch(receptionPageModelProvider);
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
state.receptions?.result?.records?.isEmpty == true
|
||||
? Text('No data')
|
||||
: ListView.builder(
|
||||
primary: true,
|
||||
shrinkWrap: true,
|
||||
itemCount:
|
||||
(state.receptions?.result?.records ??
|
||||
<StockPickingRecordModel>[])
|
||||
.length,
|
||||
itemBuilder: (context, index) {
|
||||
final record =
|
||||
state.receptions?.result?.records![index];
|
||||
return StockPickingCard(
|
||||
reference: record?.name ?? '',
|
||||
from: record?.locationId?.completeName,
|
||||
to: record?.locationDestId?.completeName,
|
||||
contact: record?.partnerId?.displayName,
|
||||
origin: record?.origin,
|
||||
status: record?.state,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -93,68 +114,3 @@ class _ReceptionPageState extends ConsumerState<ReceptionPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StockPickingCard extends StatelessWidget {
|
||||
const StockPickingCard({
|
||||
super.key,
|
||||
required this.reference,
|
||||
required this.from,
|
||||
required this.to,
|
||||
required this.contact,
|
||||
required this.origin,
|
||||
required this.status,
|
||||
});
|
||||
final String? reference;
|
||||
final String? from;
|
||||
final String? to;
|
||||
final String? contact;
|
||||
final String? origin;
|
||||
final String? status;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
reference ?? 'Référence inconnue',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_infoRow("De", from),
|
||||
_infoRow("Vers", to),
|
||||
_infoRow("Contact", contact),
|
||||
_infoRow("Document d’origine", origin),
|
||||
_infoRow("Statut", status),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _infoRow(String label, String? value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"$label : ",
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value ?? "-",
|
||||
style: const TextStyle(color: Colors.black87),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ final appSecureRoutes = $appRoutes;
|
||||
TypedGoRoute<ReceptionRoute>(path: 'ReceptionPage'),
|
||||
TypedGoRoute<DeliveryRoute>(path: 'DeliveryPage'),
|
||||
TypedGoRoute<InventoryRoute>(path: 'InventoryPage'),
|
||||
TypedGoRoute<ReceptionDetailsRoute>(path: 'ReceptionDetailsPage'),
|
||||
],
|
||||
)
|
||||
class SecureRoute extends GoRouteData with _$SecureRoute {
|
||||
@ -110,3 +111,12 @@ class InventoryRoute extends GoRouteData with _$InventoryRoute {
|
||||
child: InventoryPage(),
|
||||
);
|
||||
}
|
||||
|
||||
class ReceptionDetailsRoute extends GoRouteData with _$ReceptionDetailsRoute {
|
||||
const ReceptionDetailsRoute({required this.receptionId});
|
||||
final int receptionId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) =>
|
||||
ReceptionDetailsPage(receptionId: receptionId);
|
||||
}
|
||||
|
@ -40,6 +40,11 @@ RouteBase get $secureRoute => GoRouteData.$route(
|
||||
|
||||
factory: _$InventoryRoute._fromState,
|
||||
),
|
||||
GoRouteData.$route(
|
||||
path: 'ReceptionDetailsPage',
|
||||
|
||||
factory: _$ReceptionDetailsRoute._fromState,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -211,3 +216,31 @@ mixin _$InventoryRoute on GoRouteData {
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
mixin _$ReceptionDetailsRoute on GoRouteData {
|
||||
static ReceptionDetailsRoute _fromState(GoRouterState state) =>
|
||||
ReceptionDetailsRoute(
|
||||
receptionId: int.parse(state.uri.queryParameters['reception-id']!)!,
|
||||
);
|
||||
|
||||
ReceptionDetailsRoute get _self => this as ReceptionDetailsRoute;
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location(
|
||||
'/SecurePage/ReceptionDetailsPage',
|
||||
queryParams: {'reception-id': _self.receptionId.toString()},
|
||||
);
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import 'package:flutter/widgets.dart';
|
||||
class DioService {
|
||||
DioService({bool addAuthorization = false, required this.tokenProvider}) {
|
||||
_options = BaseOptions(
|
||||
baseUrl: AppConstants.baseUrl,
|
||||
baseUrl: AppConstants.domain,
|
||||
connectTimeout: const Duration(seconds: 45),
|
||||
receiveTimeout: const Duration(seconds: 45),
|
||||
headers: {
|
||||
@ -37,8 +37,10 @@ class DioService {
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
final token = await tokenProvider.getToken();
|
||||
final sessionId = await tokenProvider.getSessionId();
|
||||
if (token != null) {
|
||||
options.headers.addAll({"Authorization": "Bearer $token"});
|
||||
options.headers.addAll({"Cookie": sessionId});
|
||||
debugPrint('Authorization ${options.headers["Authorization"]}');
|
||||
final url = Uri.parse(
|
||||
"${options.baseUrl}${options.path}",
|
||||
|
@ -15,12 +15,17 @@ class TokenProvider {
|
||||
TokenProvider(this._storage);
|
||||
final FlutterSecureStorage _storage;
|
||||
static const String tokenKey = "tokenKey";
|
||||
static const String sessionIdKey = "sessionIdKey";
|
||||
static const String refreshTokenKey = "refreshTokenKey";
|
||||
|
||||
Future<void> setToken(String token) async {
|
||||
return _storage.write(key: tokenKey, value: token);
|
||||
}
|
||||
|
||||
Future<void> setSessionId(String sessionId) async {
|
||||
return _storage.write(key: sessionIdKey, value: sessionId);
|
||||
}
|
||||
|
||||
Future<void> setRefreshToken(String token) async {
|
||||
return _storage.write(key: refreshTokenKey, value: token);
|
||||
}
|
||||
@ -32,6 +37,11 @@ class TokenProvider {
|
||||
return token;
|
||||
}
|
||||
|
||||
Future<String?> getSessionId() async {
|
||||
final value = await _storage.read(key: sessionIdKey);
|
||||
return value;
|
||||
}
|
||||
|
||||
Future<String?> getRefreshToken() async {
|
||||
final refreshToken = await _storage.read(key: refreshTokenKey);
|
||||
debugPrint("Refresh token: $refreshToken");
|
||||
@ -42,6 +52,7 @@ class TokenProvider {
|
||||
Future<void> deleteToken() async {
|
||||
await _storage.delete(key: tokenKey);
|
||||
await _storage.delete(key: refreshTokenKey);
|
||||
await _storage.delete(key: sessionIdKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user