Fixed bugs
This commit is contained in:
@@ -8,6 +8,8 @@ class SmartSearchDropdown<T> extends StatefulWidget {
|
|||||||
final String hintText;
|
final String hintText;
|
||||||
final Widget Function(BuildContext, T)? itemBuilder;
|
final Widget Function(BuildContext, T)? itemBuilder;
|
||||||
final bool Function(T, String)? filterFn;
|
final bool Function(T, String)? filterFn;
|
||||||
|
final Color? fillColor;
|
||||||
|
final String? labelText;
|
||||||
|
|
||||||
const SmartSearchDropdown({
|
const SmartSearchDropdown({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -18,6 +20,8 @@ class SmartSearchDropdown<T> extends StatefulWidget {
|
|||||||
this.hintText = 'Type to search...',
|
this.hintText = 'Type to search...',
|
||||||
this.itemBuilder,
|
this.itemBuilder,
|
||||||
this.filterFn,
|
this.filterFn,
|
||||||
|
this.fillColor,
|
||||||
|
this.labelText,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -198,10 +202,12 @@ class _SmartSearchDropdownState<T> extends State<SmartSearchDropdown<T>> {
|
|||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
labelText: widget.labelText,
|
||||||
|
labelStyle: TextStyle(color: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Colors.grey[600]),
|
||||||
hintText: widget.hintText,
|
hintText: widget.hintText,
|
||||||
suffixIcon: const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
suffixIcon: const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
||||||
filled: Theme.of(context).inputDecorationTheme.filled ?? true,
|
filled: Theme.of(context).inputDecorationTheme.filled ?? true,
|
||||||
fillColor: Theme.of(context).inputDecorationTheme.fillColor ?? Colors.grey[100],
|
fillColor: widget.fillColor ?? (Theme.of(context).brightness == Brightness.dark ? Colors.black26 : Colors.grey[100]),
|
||||||
border: Theme.of(context).inputDecorationTheme.border ?? OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
border: Theme.of(context).inputDecorationTheme.border ?? OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
||||||
enabledBorder: Theme.of(context).inputDecorationTheme.enabledBorder ?? OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
enabledBorder: Theme.of(context).inputDecorationTheme.enabledBorder ?? OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
||||||
focusedBorder: Theme.of(context).inputDecorationTheme.focusedBorder ?? OutlineInputBorder(
|
focusedBorder: Theme.of(context).inputDecorationTheme.focusedBorder ?? OutlineInputBorder(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import '../../../sales/presentation/customers_list_screen.dart';
|
|||||||
import '../../../sales/presentation/invoices_list_screen.dart';
|
import '../../../sales/presentation/invoices_list_screen.dart';
|
||||||
import '../../providers/business_provider.dart';
|
import '../../providers/business_provider.dart';
|
||||||
import '../widgets/business_profile_form_sheet.dart';
|
import '../widgets/business_profile_form_sheet.dart';
|
||||||
|
import '../../../inventory/providers/products_provider.dart';
|
||||||
|
|
||||||
class BusinessHubScreen extends ConsumerWidget {
|
class BusinessHubScreen extends ConsumerWidget {
|
||||||
const BusinessHubScreen({super.key});
|
const BusinessHubScreen({super.key});
|
||||||
@@ -126,17 +127,97 @@ class BusinessHubScreen extends ConsumerWidget {
|
|||||||
if (showInventory) ...[
|
if (showInventory) ...[
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Text('Low Stock Alerts', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
|
Text('Low Stock Alerts', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
|
||||||
const SizedBox(height: 16),
|
Consumer(
|
||||||
// Placeholder for low stock items
|
builder: (context, ref, child) {
|
||||||
Container(
|
final productsState = ref.watch(productsProvider);
|
||||||
padding: const EdgeInsets.all(24),
|
return productsState.when(
|
||||||
decoration: BoxDecoration(
|
data: (products) {
|
||||||
color: Colors.grey.withOpacity(0.05),
|
final lowStockProducts = products.where((p) {
|
||||||
borderRadius: BorderRadius.circular(16),
|
if (!(p.trackInventory ?? false)) return false;
|
||||||
),
|
if (p.currentStock == null) return false;
|
||||||
child: const Center(
|
final minStock = p.minStock ?? 0;
|
||||||
child: Text('All products are adequately stocked.', style: TextStyle(color: Colors.grey)),
|
return p.currentStock! <= minStock;
|
||||||
),
|
}).toList();
|
||||||
|
|
||||||
|
if (lowStockProducts.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text('All products are adequately stocked.', style: TextStyle(color: Colors.grey)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: lowStockProducts.length > 5 ? 5 : lowStockProducts.length,
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final product = lowStockProducts[index];
|
||||||
|
final isOutOfStock = product.currentStock! <= 0;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isOutOfStock ? Colors.red.withOpacity(0.05) : Colors.orange.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: isOutOfStock ? Colors.red.withOpacity(0.2) : Colors.orange.withOpacity(0.2)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isOutOfStock ? Colors.red.withOpacity(0.1) : Colors.orange.withOpacity(0.1),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LucideIcons.alertTriangle,
|
||||||
|
color: isOutOfStock ? Colors.red : Colors.orange,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(product.name, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
if (product.sku != null && product.sku!.isNotEmpty)
|
||||||
|
Text('SKU: ${product.sku}', style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${product.currentStock} left',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: isOutOfStock ? Colors.red : Colors.orange.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Min: ${product.minStock ?? 0}',
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import '../../sales/domain/invoice.dart';
|
|||||||
import '../../sales/presentation/customers_list_screen.dart';
|
import '../../sales/presentation/customers_list_screen.dart';
|
||||||
import 'widgets/swipeable_account_card.dart';
|
import 'widgets/swipeable_account_card.dart';
|
||||||
import 'widgets/budget_status_card.dart';
|
import 'widgets/budget_status_card.dart';
|
||||||
|
import '../../inventory/presentation/daily_rates_screen.dart';
|
||||||
import 'widgets/upcoming_dues_widget.dart';
|
import 'widgets/upcoming_dues_widget.dart';
|
||||||
import 'widgets/statistics_tab.dart';
|
import 'widgets/statistics_tab.dart';
|
||||||
import '../../../core/widgets/shimmer_loading.dart';
|
import '../../../core/widgets/shimmer_loading.dart';
|
||||||
@@ -176,6 +177,16 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(LucideIcons.trendingUp),
|
||||||
|
tooltip: 'Daily Commodity Rates',
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const DailyRatesScreen()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
Consumer(
|
Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
final invitationsState = ref.watch(invitationProvider);
|
final invitationsState = ref.watch(invitationProvider);
|
||||||
|
|||||||
@@ -135,7 +135,12 @@ class _DailyRatesScreenState extends ConsumerState<DailyRatesScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
ref.invalidate(productCategoriesProvider);
|
||||||
|
await ref.read(productCategoriesProvider.future);
|
||||||
|
},
|
||||||
|
child: ListView.builder(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: commodities.length,
|
itemCount: commodities.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@@ -314,7 +319,7 @@ class _DailyRatesScreenState extends ConsumerState<DailyRatesScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
));
|
||||||
},
|
},
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (err, stack) => Center(child: Text('Error: $err')),
|
error: (err, stack) => Center(child: Text('Error: $err')),
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
|||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
left: 24,
|
left: 24,
|
||||||
right: 24,
|
right: 24,
|
||||||
top: 24,
|
top: 48,
|
||||||
),
|
),
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
@@ -173,8 +173,14 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(LucideIcons.chevronLeft, size: 28),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
Text(widget.customer == null ? "New Customer" : "Edit Customer", style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
Text(widget.customer == null ? "New Customer" : "Edit Customer", style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -305,8 +311,10 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
|||||||
return statesState.when(
|
return statesState.when(
|
||||||
data: (states) {
|
data: (states) {
|
||||||
return SmartSearchDropdown<int>(
|
return SmartSearchDropdown<int>(
|
||||||
|
labelText: 'State',
|
||||||
hintText: 'Select State',
|
hintText: 'Select State',
|
||||||
value: _selectedStateId,
|
value: _selectedStateId,
|
||||||
|
fillColor: Colors.grey[100],
|
||||||
items: states.map((s) => s.id).toList(),
|
items: states.map((s) => s.id).toList(),
|
||||||
itemAsString: (id) {
|
itemAsString: (id) {
|
||||||
final s = states.firstWhere((st) => st.id == id);
|
final s = states.firstWhere((st) => st.id == id);
|
||||||
|
|||||||
@@ -450,6 +450,34 @@ class _InvoiceBuilderScreenState extends ConsumerState<InvoiceBuilderScreen> {
|
|||||||
const Text('Line Items', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
const Text('Line Items', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final feature = ref.watch(businessFeatureProvider).value;
|
||||||
|
final isBarcode = feature?.barcodeSource == 'BARCODE';
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text('Scan by:', style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text('SKU', style: TextStyle(fontSize: 10, fontWeight: !isBarcode ? FontWeight.bold : FontWeight.normal, color: !isBarcode ? Colors.blue : Colors.grey)),
|
||||||
|
Transform.scale(
|
||||||
|
scale: 0.7,
|
||||||
|
child: Switch(
|
||||||
|
value: isBarcode,
|
||||||
|
activeColor: Colors.blue,
|
||||||
|
onChanged: (val) {
|
||||||
|
if (feature != null) {
|
||||||
|
ref.read(businessFeatureProvider.notifier).updateFeatures(
|
||||||
|
feature.copyWith(barcodeSource: val ? 'BARCODE' : 'SKU')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text('EAN', style: TextStyle(fontSize: 10, fontWeight: isBarcode ? FontWeight.bold : FontWeight.normal, color: isBarcode ? Colors.blue : Colors.grey)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(LucideIcons.scanLine, color: Colors.blue),
|
icon: const Icon(LucideIcons.scanLine, color: Colors.blue),
|
||||||
onPressed: _scanAndAddBarcode,
|
onPressed: _scanAndAddBarcode,
|
||||||
@@ -595,10 +623,25 @@ class _InvoiceBuilderScreenState extends ConsumerState<InvoiceBuilderScreen> {
|
|||||||
child: Consumer(
|
child: Consumer(
|
||||||
builder: (context, consumerRef, _) {
|
builder: (context, consumerRef, _) {
|
||||||
final walletsState = consumerRef.watch(walletProvider);
|
final walletsState = consumerRef.watch(walletProvider);
|
||||||
final wallets = walletsState.value ?? [];
|
final allWallets = walletsState.value ?? [];
|
||||||
|
|
||||||
|
final wallets = allWallets.where((w) {
|
||||||
|
if (_paymentMethod == 'Cash') {
|
||||||
|
return w.nature == 'CASH';
|
||||||
|
} else {
|
||||||
|
return w.nature == 'INCOME' || w.nature == 'SAVINGS';
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (wallets.isNotEmpty && (_selectedWalletId == null || !wallets.any((w) => w.id == _selectedWalletId))) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) setState(() => _selectedWalletId = wallets.first.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return DropdownButtonHideUnderline(
|
return DropdownButtonHideUnderline(
|
||||||
child: DropdownButton<int>(
|
child: DropdownButton<int>(
|
||||||
value: _selectedWalletId ?? wallets.firstOrNull?.id,
|
value: _selectedWalletId,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hint: const Text('Wallet'),
|
hint: const Text('Wallet'),
|
||||||
items: wallets
|
items: wallets
|
||||||
@@ -626,7 +669,14 @@ class _InvoiceBuilderScreenState extends ConsumerState<InvoiceBuilderScreen> {
|
|||||||
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
||||||
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (val) => setState(() => _paymentMethod = val!),
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() {
|
||||||
|
_paymentMethod = val;
|
||||||
|
_selectedWalletId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -31,9 +31,17 @@ class _ReceivePaymentSheetState extends ConsumerState<ReceivePaymentSheet> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletsState = ref.watch(walletProvider);
|
final walletsState = ref.watch(walletProvider);
|
||||||
final wallets = walletsState.value ?? [];
|
final allWallets = walletsState.value ?? [];
|
||||||
|
|
||||||
if (wallets.isNotEmpty && _selectedWalletId == null) {
|
final wallets = allWallets.where((w) {
|
||||||
|
if (_paymentMethod == 'Cash') {
|
||||||
|
return w.nature == 'CASH';
|
||||||
|
} else {
|
||||||
|
return w.nature == 'INCOME' || w.nature == 'SAVINGS';
|
||||||
|
}
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (wallets.isNotEmpty && (_selectedWalletId == null || !wallets.any((w) => w.id == _selectedWalletId))) {
|
||||||
_selectedWalletId = wallets.first.id;
|
_selectedWalletId = wallets.first.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +110,14 @@ class _ReceivePaymentSheetState extends ConsumerState<ReceivePaymentSheet> {
|
|||||||
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
||||||
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (val) => setState(() => _paymentMethod = val!),
|
onChanged: (val) {
|
||||||
|
if (val != null) {
|
||||||
|
setState(() {
|
||||||
|
_paymentMethod = val;
|
||||||
|
_selectedWalletId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user