diff --git a/lib/routes.dart b/lib/routes.dart index e1b5fd9..d4149a0 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -161,7 +161,10 @@ GoRouter buildRouter() { return ScaffoldWithNavBar(child: child); }, routes: [ - GoRoute(path: '/workspace', builder: (context, state) => const WorkspaceScreen()), + GoRoute( + path: '/workspace', + builder: (context, state) => const WorkspaceScreen(), + ), GoRoute(path: '/', builder: (context, state) => const HomeScreen()), GoRoute( path: '/home', @@ -249,11 +252,10 @@ GoRouter buildRouter() { }, ), GoRoute( - path: '/groups/:groupId/batches/:batchId', + path: '/groups/batches/:batchId', builder: (context, state) { final batchId = state.pathParameters['batchId']!; - final groupId = state.pathParameters['groupId']!; - return BatchDetailScreen(batchId: batchId, groupId: groupId); + return BatchDetailScreen(batchId: batchId); }, ), GoRoute( diff --git a/lib/screens/groups/screens/batch_create_screen.dart b/lib/screens/groups/screens/batch_create_screen.dart index c1fc1d9..e1fd1a3 100644 --- a/lib/screens/groups/screens/batch_create_screen.dart +++ b/lib/screens/groups/screens/batch_create_screen.dart @@ -122,7 +122,7 @@ class _BatchCreateScreenState extends State { } if (!mounted) return; - context.pushReplacement('/groups/${widget.group.id}/batches/${batch.id}'); + context.pushReplacement('/groups/batches/${batch.id}'); } else { AppSnackBar.showError(context, result.error ?? 'Failed to create batch.'); } diff --git a/lib/screens/groups/screens/batch_detail_screen.dart b/lib/screens/groups/screens/batch_detail_screen.dart index 2b5d942..fac2735 100644 --- a/lib/screens/groups/screens/batch_detail_screen.dart +++ b/lib/screens/groups/screens/batch_detail_screen.dart @@ -15,12 +15,10 @@ import 'package:url_launcher/url_launcher.dart'; class BatchDetailScreen extends StatefulWidget { final String batchId; - final String groupId; const BatchDetailScreen({ super.key, required this.batchId, - required this.groupId, }); @override diff --git a/lib/screens/groups/screens/group_detail_screen.dart b/lib/screens/groups/screens/group_detail_screen.dart index 58f1e06..e31d8a7 100644 --- a/lib/screens/groups/screens/group_detail_screen.dart +++ b/lib/screens/groups/screens/group_detail_screen.dart @@ -1142,7 +1142,7 @@ class _GroupDetailScreenState extends State borderRadius: BorderRadius.circular(12), onTap: inSelectMode ? () => batchController.toggleBatchSelection(batch.id!) - : () => context.push('/groups/${group.id}/batches/${batch.id}'), + : () => context.push('/groups/batches/${batch.id}'), child: Padding( padding: const EdgeInsets.all(10), child: Row( diff --git a/lib/screens/history/history_controller.dart b/lib/screens/history/history_controller.dart index 307b89f..8d72633 100644 --- a/lib/screens/history/history_controller.dart +++ b/lib/screens/history/history_controller.dart @@ -108,11 +108,11 @@ class HistoryController extends ChangeNotifier { Future searchTransactions( String workspaceId, { - String? search, + String? billName, String? status, String? type, }) async { - model.searchQuery = search ?? ''; + model.searchQuery = billName ?? ''; model.statusFilter = status; model.typeFilter = type; final params = { @@ -121,13 +121,8 @@ class HistoryController extends ChangeNotifier { 'size': '20', 'sort': 'createdAt,desc', }; - if (search != null && search.isNotEmpty) { - params['creditAccount'] = search; - params['creditEmail'] = search; - params['creditPhone'] = search; - params['creditName'] = search; - params['billName'] = search; - params['amount'] = search; + if (billName != null && billName.isNotEmpty) { + params['billName'] = billName; } if (status != null && status.isNotEmpty) { params['status'] = status; @@ -149,12 +144,7 @@ class HistoryController extends ChangeNotifier { 'sort': 'createdAt,desc', }; if (model.searchQuery.isNotEmpty) { - params['creditAccount'] = model.searchQuery; - params['creditEmail'] = model.searchQuery; - params['creditPhone'] = model.searchQuery; - params['creditName'] = model.searchQuery; params['billName'] = model.searchQuery; - params['amount'] = model.searchQuery; } if (model.statusFilter != null && model.statusFilter!.isNotEmpty) { params['status'] = model.statusFilter!; diff --git a/lib/screens/history/history_screen.dart b/lib/screens/history/history_screen.dart index dd20bdc..eebf143 100644 --- a/lib/screens/history/history_screen.dart +++ b/lib/screens/history/history_screen.dart @@ -231,7 +231,7 @@ class _HistoryScreenState extends State prefs.getString("workspaceId") ?? ''; controller.searchTransactions( workspaceId, - search: _searchController.text.isNotEmpty + billName: _searchController.text.isNotEmpty ? _searchController.text : null, status: controller.model.statusFilter, @@ -356,7 +356,7 @@ class _HistoryScreenState extends State final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( workspaceId, - search: value.isNotEmpty ? value : null, + billName: value.isNotEmpty ? value : null, status: controller.model.statusFilter, type: controller.model.typeFilter, ); @@ -501,7 +501,7 @@ class _HistoryScreenState extends State final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( workspaceId, - search: _searchController.text.isNotEmpty + billName: _searchController.text.isNotEmpty ? _searchController.text : null, type: controller.model.typeFilter, @@ -514,7 +514,7 @@ class _HistoryScreenState extends State final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( workspaceId, - search: _searchController.text.isNotEmpty + billName: _searchController.text.isNotEmpty ? _searchController.text : null, status: controller.model.statusFilter, diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 9999aa1..2652871 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -123,7 +123,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { if (prefs.getString("token") != null) { setState(() { _isLoggedIn = true; - initials = prefs.getString("initials")!; + initials = prefs.getString("workspaceInitials")!; }); } @@ -347,24 +347,28 @@ class _HomeScreenState extends State with TickerProviderStateMixin { end: Alignment.bottomLeft, ), ), - child: Center( - child: Text( - initials, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: isDark ? Colors.white : Colors.black87, + child: InkWell( + child: Center( + child: Text( + initials, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: isDark ? Colors.white : Colors.black87, + ), ), ), + onTap: () => context.go('/workspace'), ), ), + SizedBox(width: 5), IconButton( icon: Icon( - Icons.logout_rounded, + Icons.logout, color: isDark ? Colors.white54 : Colors.black54, size: 20, ), - onPressed: _showLogoutDialog, + onPressed: () => _showLogoutDialog(), ), ], ]; diff --git a/lib/screens/receipt/receipt_screen.dart b/lib/screens/receipt/receipt_screen.dart index 0ec2ec4..480b797 100644 --- a/lib/screens/receipt/receipt_screen.dart +++ b/lib/screens/receipt/receipt_screen.dart @@ -533,6 +533,15 @@ class _ReceiptScreenState extends State isLoading: false, onPressed: _captureImage, ), + if (_transaction?.batchId != null) + _modernActionButton( + icon: Icons.batch_prediction_rounded, + label: "View Batch", + color: const Color.fromARGB(255, 60, 99, 10), + isLoading: false, + onPressed: () => + context.push('/groups/batches/${_transaction?.batchId}'), + ), if (integrationStatus != "SUCCESS" && paymentStatus == "SUCCESS") ...[ _modernActionButton( icon: Icons.refresh_rounded, @@ -1111,4 +1120,4 @@ class _DetailItem { this.isWarning = false, this.trailing, }); -} \ No newline at end of file +} diff --git a/lib/screens/transactions/transaction_model.dart b/lib/screens/transactions/transaction_model.dart index 84ae77d..22db389 100644 --- a/lib/screens/transactions/transaction_model.dart +++ b/lib/screens/transactions/transaction_model.dart @@ -51,6 +51,8 @@ class TransactionModel { final String? id; final DateTime? createdAt; final String userId; + final String workspaceId; + final String? batchId; final String trace; final Decimal amount; final Decimal charge; @@ -99,6 +101,8 @@ class TransactionModel { this.id, this.createdAt, required this.userId, + required this.workspaceId, + this.batchId, required this.trace, required this.amount, required this.charge, diff --git a/lib/screens/transactions/transaction_model.g.dart b/lib/screens/transactions/transaction_model.g.dart index 150be64..1e83909 100644 --- a/lib/screens/transactions/transaction_model.g.dart +++ b/lib/screens/transactions/transaction_model.g.dart @@ -14,6 +14,8 @@ TransactionModel _$TransactionModelFromJson(Map json) => const DateTimeJsonConverter().fromJson, ), userId: json['userId'] as String, + workspaceId: json['workspaceId'] as String, + batchId: json['batchId'] as String?, trace: json['trace'] as String, amount: const DecimalJsonConverter().fromJson(json['amount'] as num), charge: const DecimalJsonConverter().fromJson(json['charge'] as num), @@ -74,6 +76,8 @@ Map _$TransactionModelToJson( const DateTimeJsonConverter().toJson, ), 'userId': instance.userId, + 'workspaceId': instance.workspaceId, + 'batchId': instance.batchId, 'trace': instance.trace, 'amount': const DecimalJsonConverter().toJson(instance.amount), 'charge': const DecimalJsonConverter().toJson(instance.charge), diff --git a/lib/screens/workspaces/workspace_screen.dart b/lib/screens/workspaces/workspace_screen.dart index da59928..27ae543 100644 --- a/lib/screens/workspaces/workspace_screen.dart +++ b/lib/screens/workspaces/workspace_screen.dart @@ -1,6 +1,8 @@ +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:qpay/screens/workspaces/workspace_model.dart'; import 'package:qpay/screens/workspaces/workspace_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; @@ -27,7 +29,6 @@ class _WorkspaceScreenState extends State late Animation _slideAnimation; AuthState authState = AuthState(); - bool _navigated = false; @override void initState() { @@ -86,8 +87,6 @@ class _WorkspaceScreenState extends State } void _evaluateNavigation(WorkspaceProvider provider) { - if (_navigated) return; - final workspaces = provider.workspaces; if (workspaces.length == 1) { @@ -104,20 +103,16 @@ class _WorkspaceScreenState extends State // 2+ workspaces: stay on this screen so the user can pick one. } - void _onWorkspaceSelected(String workspaceId) async { - if (_navigated) return; - _navigated = true; - + void _onWorkspaceSelected(WorkspaceModel ws) async { final prefs = await SharedPreferences.getInstance(); - await prefs.setString('workspaceId', workspaceId); + await prefs.setString('workspaceId', ws.id); + await prefs.setString('workspaceInitials', ws.name[0]); if (!mounted) return; _navigateToHome(); } void _navigateToHome() { - if (_navigated) return; - _navigated = true; context.go('/'); } @@ -149,68 +144,73 @@ class _WorkspaceScreenState extends State final workspaces = provider.workspaces; return SafeArea( - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Spacer(flex: 2), - // Header - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: theme.colorScheme.primary.withValues( - alpha: 0.1, + child: Center( + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: SizedBox( + width: kIsWeb ? 600 : double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 48), + // Header + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + Icons.business_rounded, + color: theme.colorScheme.primary, + size: 28, + ), ), - borderRadius: BorderRadius.circular(16), - ), - child: Icon( - Icons.business_rounded, - color: theme.colorScheme.primary, - size: 28, - ), + const SizedBox(height: 24), + Text( + 'Select a workspace', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + Text( + 'Choose the workspace you want to work in.', + style: theme.textTheme.bodyMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 32), + // Workspace list + Expanded( + child: ListView.separated( + itemCount: workspaces.length, + separatorBuilder: (_, __) => + const SizedBox(height: 12), + itemBuilder: (context, index) { + final ws = workspaces[index]; + return _WorkspaceCard( + name: ws.name, + description: ws.description, + email: ws.email, + phone: ws.phone, + onTap: () => _onWorkspaceSelected(ws), + ); + }, + ), + ), + const SizedBox(height: 24), + ], ), - const SizedBox(height: 24), - Text( - 'Select a workspace', - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - color: Colors.black87, - ), - ), - const SizedBox(height: 8), - Text( - 'Choose the workspace you want to work in.', - style: theme.textTheme.bodyMedium?.copyWith( - color: Colors.grey.shade600, - ), - ), - const SizedBox(height: 32), - // Workspace list - Expanded( - child: ListView.separated( - itemCount: workspaces.length, - separatorBuilder: (_, __) => - const SizedBox(height: 12), - itemBuilder: (context, index) { - final ws = workspaces[index]; - return _WorkspaceCard( - name: ws.name, - description: ws.description, - email: ws.email, - phone: ws.phone, - onTap: () => _onWorkspaceSelected(ws.id), - ); - }, - ), - ), - const Spacer(flex: 1), - ], + ), ), ), ),