feat(app): implement Product Scan review flow, dynamic batch expiry picker, custom PDFs, PO relationships, and aligned card layouts

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-07-09 12:36:06 +07:00
1 parent 9ff4a4a922
commit 577be04308
69 files changed
+5645 -1657

No files matched your search

+374
View File
@@ -0,0 +1,374 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/app_config.dart';
import '../auth/auth_provider.dart';
import 'scan_mode_provider.dart';
class CameraDrawer extends ConsumerStatefulWidget {
const CameraDrawer({super.key});
@override
ConsumerState<CameraDrawer> createState() => _CameraDrawerState();
}
class _CameraDrawerState extends ConsumerState<CameraDrawer> {
String _storeName = 'Store Name';
@override
void initState() {
super.initState();
_loadStoreName();
}
Future<void> _loadStoreName() async {
final prefs = await SharedPreferences.getInstance();
if (mounted) {
setState(() {
_storeName = prefs.getString('nama_toko') ?? 'Store Name';
});
}
}
@override
Widget build(BuildContext context) {
final activeMode = ref.watch(scanModeProvider);
return Drawer(
backgroundColor: Colors.white,
child: SafeArea(
child: Column(
children: [
// Close Button & Profile Row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Column(
children: [
// Close Button
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.close, color: Colors.grey),
onPressed: () => Navigator.pop(context),
),
),
const SizedBox(height: 8),
// Profile Row
Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(color: Colors.grey.shade200, width: 1),
),
child: ClipOval(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Image.asset(
'assets/logo.png',
fit: BoxFit.contain,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Prima Fresh Mart',
style: AppConfig.h3.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
_storeName,
style: AppConfig.bodySmall.copyWith(
color: Colors.grey.shade600,
fontSize: 13,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
const SizedBox(height: 16),
const Divider(height: 1, thickness: 1),
const SizedBox(height: 20),
// Mode Toggle Section (between DO and Product Scan)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mode',
style: AppConfig.bodySmall.copyWith(
color: Colors.grey.shade500,
fontWeight: FontWeight.w600,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
Container(
height: 48,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {
ref.read(scanModeProvider.notifier).state = 'DO';
},
child: Container(
decoration: BoxDecoration(
color: activeMode == 'DO' ? AppConfig.primaryColor : Colors.transparent,
borderRadius: BorderRadius.circular(8),
boxShadow: activeMode == 'DO'
? [
BoxShadow(
color: AppConfig.primaryColor.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: [],
),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
activeMode == 'DO' ? Icons.description : Icons.description_outlined,
size: 16,
color: activeMode == 'DO' ? Colors.white : Colors.grey.shade500,
),
const SizedBox(width: 6),
Text(
'DO Scan',
style: AppConfig.bodyMedium.copyWith(
fontWeight: activeMode == 'DO' ? FontWeight.bold : FontWeight.normal,
color: activeMode == 'DO' ? Colors.white : Colors.grey.shade600,
),
),
],
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
ref.read(scanModeProvider.notifier).state = 'Product';
},
child: Container(
decoration: BoxDecoration(
color: activeMode == 'Product' ? AppConfig.primaryColor : Colors.transparent,
borderRadius: BorderRadius.circular(8),
boxShadow: activeMode == 'Product'
? [
BoxShadow(
color: AppConfig.primaryColor.withOpacity(0.2),
blurRadius: 4,
offset: const Offset(0, 2),
)
]
: [],
),
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
activeMode == 'Product' ? Icons.inventory_2 : Icons.inventory_2_outlined,
size: 16,
color: activeMode == 'Product' ? Colors.white : Colors.grey.shade500,
),
const SizedBox(width: 6),
Text(
'Product Scan',
style: AppConfig.bodyMedium.copyWith(
fontWeight: activeMode == 'Product' ? FontWeight.bold : FontWeight.normal,
color: activeMode == 'Product' ? Colors.white : Colors.grey.shade600,
),
),
],
),
),
),
),
],
),
),
),
],
),
),
const SizedBox(height: 20),
// Menu Items List with Grouping Sections
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
children: [
_buildSectionHeader('DASHBOARD'),
_buildDrawerItem(
icon: Icons.home_outlined,
title: 'Home',
onTap: () => Navigator.pop(context),
),
_buildDrawerItem(
icon: Icons.history_outlined,
title: 'History',
onTap: () {
Navigator.pop(context);
context.push('/documents');
},
),
const SizedBox(height: 16),
_buildSectionHeader('USER PROFILE'),
_buildDrawerItem(
icon: Icons.person_outline,
title: 'Profile',
onTap: () {
Navigator.pop(context);
_showNotImplemented('Profile');
},
),
_buildDrawerItem(
icon: Icons.notifications_none_outlined,
title: 'Notifications',
onTap: () {
Navigator.pop(context);
_showNotImplemented('Notifications');
},
),
const SizedBox(height: 16),
_buildSectionHeader('SUPPORT & SETTINGS'),
_buildDrawerItem(
icon: Icons.settings_outlined,
title: 'Settings',
onTap: () {
Navigator.pop(context);
_showNotImplemented('Settings');
},
),
_buildDrawerItem(
icon: Icons.help_outline,
title: 'Help',
onTap: () {
Navigator.pop(context);
_showHelpDialog();
},
),
],
),
),
// Logout at the bottom
const Divider(height: 1),
Padding(
padding: const EdgeInsets.all(16.0),
child: _buildDrawerItem(
icon: Icons.logout_outlined,
title: 'Logout',
textColor: AppConfig.errorColor,
iconColor: AppConfig.errorColor,
onTap: () {
ref.read(authProvider.notifier).logout();
context.go('/login');
},
),
),
],
),
),
);
}
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0, bottom: 8.0),
child: Text(
title,
style: AppConfig.bodySmall.copyWith(
color: Colors.grey.shade400,
fontWeight: FontWeight.bold,
fontSize: 11,
letterSpacing: 1.0,
),
),
);
}
Widget _buildDrawerItem({
required IconData icon,
required String title,
required VoidCallback onTap,
Color? textColor,
Color? iconColor,
}) {
return ListTile(
leading: Icon(icon, color: iconColor ?? Colors.black87),
title: Text(
title,
style: AppConfig.bodyLarge.copyWith(
color: textColor ?? Colors.black87,
fontWeight: FontWeight.w500,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
onTap: onTap,
);
}
void _showNotImplemented(String featureName) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('$featureName feature coming soon!'),
behavior: SnackBarBehavior.floating,
),
);
}
void _showHelpDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Tentang Prima Mart'),
content: const SingleChildScrollView(
child: Text(
'www.primafreshmart.com merupakan sarana belanja online dari toko Prima Mart yang sudah melayani konsumen selama 10 tahun dan memiliki hampir 1200 gerai di Pulau Jawa, Bali dan Sumatera. Inovasi terbaru dari PT. Primafood International sebagai bagian dari PT. Charoen Pokphand Indonesia, Tbk.\n\nPT. Primafood International\nJl. Ancol Barat VIII no.1, Ancol, Pademangan, Jakarta Utara, DKI Jakarta.',
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Tutup'),
)
],
),
);
}
}