Fixed bugs
This commit is contained in:
@@ -8,6 +8,8 @@ class SmartSearchDropdown<T> extends StatefulWidget {
|
||||
final String hintText;
|
||||
final Widget Function(BuildContext, T)? itemBuilder;
|
||||
final bool Function(T, String)? filterFn;
|
||||
final Color? fillColor;
|
||||
final String? labelText;
|
||||
|
||||
const SmartSearchDropdown({
|
||||
super.key,
|
||||
@@ -18,6 +20,8 @@ class SmartSearchDropdown<T> extends StatefulWidget {
|
||||
this.hintText = 'Type to search...',
|
||||
this.itemBuilder,
|
||||
this.filterFn,
|
||||
this.fillColor,
|
||||
this.labelText,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -198,10 +202,12 @@ class _SmartSearchDropdownState<T> extends State<SmartSearchDropdown<T>> {
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: widget.labelText,
|
||||
labelStyle: TextStyle(color: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Colors.grey[600]),
|
||||
hintText: widget.hintText,
|
||||
suffixIcon: const Icon(Icons.arrow_drop_down, color: Colors.grey),
|
||||
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),
|
||||
enabledBorder: Theme.of(context).inputDecorationTheme.enabledBorder ?? OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
|
||||
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 '../../providers/business_provider.dart';
|
||||
import '../widgets/business_profile_form_sheet.dart';
|
||||
import '../../../inventory/providers/products_provider.dart';
|
||||
|
||||
class BusinessHubScreen extends ConsumerWidget {
|
||||
const BusinessHubScreen({super.key});
|
||||
@@ -126,17 +127,97 @@ class BusinessHubScreen extends ConsumerWidget {
|
||||
if (showInventory) ...[
|
||||
const SizedBox(height: 32),
|
||||
Text('Low Stock Alerts', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 16),
|
||||
// Placeholder for low stock items
|
||||
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)),
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final productsState = ref.watch(productsProvider);
|
||||
return productsState.when(
|
||||
data: (products) {
|
||||
final lowStockProducts = products.where((p) {
|
||||
if (!(p.trackInventory ?? false)) return false;
|
||||
if (p.currentStock == null) return false;
|
||||
final minStock = p.minStock ?? 0;
|
||||
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 'widgets/swipeable_account_card.dart';
|
||||
import 'widgets/budget_status_card.dart';
|
||||
import '../../inventory/presentation/daily_rates_screen.dart';
|
||||
import 'widgets/upcoming_dues_widget.dart';
|
||||
import 'widgets/statistics_tab.dart';
|
||||
import '../../../core/widgets/shimmer_loading.dart';
|
||||
@@ -176,6 +177,16 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(LucideIcons.trendingUp),
|
||||
tooltip: 'Daily Commodity Rates',
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const DailyRatesScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
Consumer(
|
||||
builder: (context, ref, child) {
|
||||
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),
|
||||
itemCount: commodities.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -314,7 +319,7 @@ class _DailyRatesScreenState extends ConsumerState<DailyRatesScreen> {
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
));
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (err, stack) => Center(child: Text('Error: $err')),
|
||||
|
||||
@@ -163,7 +163,7 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
left: 24,
|
||||
right: 24,
|
||||
top: 24,
|
||||
top: 48,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
@@ -173,8 +173,14 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
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)),
|
||||
],
|
||||
),
|
||||
@@ -305,8 +311,10 @@ class _AddCustomerSheetState extends ConsumerState<AddCustomerSheet> {
|
||||
return statesState.when(
|
||||
data: (states) {
|
||||
return SmartSearchDropdown<int>(
|
||||
labelText: 'State',
|
||||
hintText: 'Select State',
|
||||
value: _selectedStateId,
|
||||
fillColor: Colors.grey[100],
|
||||
items: states.map((s) => s.id).toList(),
|
||||
itemAsString: (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)),
|
||||
Row(
|
||||
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(
|
||||
icon: const Icon(LucideIcons.scanLine, color: Colors.blue),
|
||||
onPressed: _scanAndAddBarcode,
|
||||
@@ -595,10 +623,25 @@ class _InvoiceBuilderScreenState extends ConsumerState<InvoiceBuilderScreen> {
|
||||
child: Consumer(
|
||||
builder: (context, consumerRef, _) {
|
||||
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(
|
||||
child: DropdownButton<int>(
|
||||
value: _selectedWalletId ?? wallets.firstOrNull?.id,
|
||||
value: _selectedWalletId,
|
||||
isDense: true,
|
||||
hint: const Text('Wallet'),
|
||||
items: wallets
|
||||
@@ -626,7 +669,14 @@ class _InvoiceBuilderScreenState extends ConsumerState<InvoiceBuilderScreen> {
|
||||
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
||||
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
||||
.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
|
||||
Widget build(BuildContext context) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -102,7 +110,14 @@ class _ReceivePaymentSheetState extends ConsumerState<ReceivePaymentSheet> {
|
||||
items: ['Cash', 'UPI', 'Bank Transfer', 'Card']
|
||||
.map((m) => DropdownMenuItem(value: m, child: Text(m)))
|
||||
.toList(),
|
||||
onChanged: (val) => setState(() => _paymentMethod = val!),
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
setState(() {
|
||||
_paymentMethod = val;
|
||||
_selectedWalletId = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user