Fixed web loading screen and setup related issues

This commit is contained in:
2026-09-01 07:38:45 +05:30
parent a0e7a898fa
commit bfe0c1efbd
14 changed files with 268 additions and 131 deletions

View File

@@ -1,4 +1,7 @@
flutter_icons: flutter_icons:
android: true android: true
ios: true ios: true
web:
generate: true
image_path: "assets/icon.png"
image_path: "assets/icon.png" image_path: "assets/icon.png"

View File

@@ -26,6 +26,8 @@ class DesktopSidebar extends ConsumerWidget {
final isDark = theme.brightness == Brightness.dark; final isDark = theme.brightness == Brightness.dark;
final isBusinessMode = ref.watch(businessModeProvider); final isBusinessMode = ref.watch(businessModeProvider);
final businessProfile = ref.watch(businessProfileProvider).asData?.value; final businessProfile = ref.watch(businessProfileProvider).asData?.value;
final nature = (businessProfile?.natureOfBusiness ?? businessProfile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
final ratesAsync = ref.watch(commodityRatesProvider); final ratesAsync = ref.watch(commodityRatesProvider);
return Container( return Container(
@@ -51,22 +53,23 @@ class DesktopSidebar extends ConsumerWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFFD4AF37), Color(0xFFAA771C)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: const Color(0xFFD4AF37).withValues(alpha: 0.3), color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 2),
), ),
], ],
), ),
child: const Center( child: ClipRRect(
child: Icon(LucideIcons.gem, color: Colors.white, size: 22), borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/icon.png',
width: 40,
height: 40,
fit: BoxFit.cover,
),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -276,8 +279,9 @@ class DesktopSidebar extends ConsumerWidget {
), ),
), ),
// 4. Live Metal Rate Widget in Sidebar // 4. Live Metal Rate Widget in Sidebar (Jewellery only)
ratesAsync.when( if (isJewellery)
ratesAsync.when(
data: (rates) { data: (rates) {
if (rates.isEmpty) return const SizedBox.shrink(); if (rates.isEmpty) return const SizedBox.shrink();
final goldRate = rates.firstWhere( final goldRate = rates.firstWhere(

View File

@@ -131,19 +131,28 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Container( Center(
width: 80, child: Container(
height: 80, width: 80,
decoration: const BoxDecoration( height: 80,
color: Colors.transparent, decoration: BoxDecoration(
shape: BoxShape.circle, borderRadius: BorderRadius.circular(20),
), boxShadow: [
child: Center( BoxShadow(
child: Image.asset( color: Colors.black.withValues(alpha: 0.08),
'assets/logo.png', blurRadius: 16,
width: 80, offset: const Offset(0, 4),
height: 80, ),
fit: BoxFit.contain, ],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.asset(
'assets/icon.png',
width: 80,
height: 80,
fit: BoxFit.cover,
),
), ),
), ),
), ),

View File

@@ -12,8 +12,7 @@ class SetupWizardScreen extends StatefulWidget {
State<SetupWizardScreen> createState() => _SetupWizardScreenState(); State<SetupWizardScreen> createState() => _SetupWizardScreenState();
} }
class _SetupWizardScreenState extends State<SetupWizardScreen> class _SetupWizardScreenState extends State<SetupWizardScreen> {
with SingleTickerProviderStateMixin {
bool _isLoading = false; bool _isLoading = false;
int _currentStep = 0; int _currentStep = 0;
String _accountType = 'INDIVIDUAL'; String _accountType = 'INDIVIDUAL';
@@ -40,6 +39,19 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
int get _totalSteps => _accountType == 'BUSINESS' ? 4 : 3; int get _totalSteps => _accountType == 'BUSINESS' ? 4 : 3;
@override
void dispose() {
_nameController.dispose();
_usernameController.dispose();
_businessNameController.dispose();
_addressController.dispose();
_emailController.dispose();
_gstController.dispose();
_panController.dispose();
_msmeController.dispose();
super.dispose();
}
Future<void> _checkUsername(String username) async { Future<void> _checkUsername(String username) async {
if (username.length < 3) return; if (username.length < 3) return;
setState(() => _checkingUsername = true); setState(() => _checkingUsername = true);
@@ -111,6 +123,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
} }
void _nextStep() { void _nextStep() {
FocusScope.of(context).unfocus();
if (_currentStep == 1) { if (_currentStep == 1) {
if (_nameController.text.trim().isEmpty) { if (_nameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -150,6 +163,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
} }
void _prevStep() { void _prevStep() {
FocusScope.of(context).unfocus();
if (_currentStep > 0) { if (_currentStep > 0) {
setState(() => _currentStep--); setState(() => _currentStep--);
} }
@@ -209,7 +223,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep0AccountType() { Widget _buildStep0AccountType() {
return Column( return Column(
key: const ValueKey(0),
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Text( const Text(
@@ -324,7 +337,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep1Personal() { Widget _buildStep1Personal() {
return Column( return Column(
key: const ValueKey(1),
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Text( const Text(
@@ -354,6 +366,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: _checkingUsername child: _checkingUsername
? const SizedBox( ? const SizedBox(
key: ValueKey('user_check_spinner'),
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
@@ -362,7 +375,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
_isUsernameAvailable _isUsernameAvailable
? LucideIcons.checkCircle2 ? LucideIcons.checkCircle2
: LucideIcons.xCircle, : LucideIcons.xCircle,
key: ValueKey(_isUsernameAvailable), key: ValueKey('user_avail_${_isUsernameAvailable}'),
color: _usernameController.text.isEmpty color: _usernameController.text.isEmpty
? Colors.transparent ? Colors.transparent
: (_isUsernameAvailable : (_isUsernameAvailable
@@ -388,7 +401,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep2Business() { Widget _buildStep2Business() {
return Column( return Column(
key: const ValueKey(2),
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Text( const Text(
@@ -410,6 +422,10 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
value: 'JEWELLERY', value: 'JEWELLERY',
child: Text('Jewellery', style: TextStyle(color: Colors.black87)), child: Text('Jewellery', style: TextStyle(color: Colors.black87)),
), ),
DropdownMenuItem(
value: 'ECOMMERCE',
child: Text('E-Commerce Seller', style: TextStyle(color: Colors.black87)),
),
DropdownMenuItem( DropdownMenuItem(
value: 'PROJECT_MANAGEMENT', value: 'PROJECT_MANAGEMENT',
child: Text( child: Text(
@@ -474,7 +490,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
Widget _buildStep3Consent() { Widget _buildStep3Consent() {
return Column( return Column(
key: const ValueKey(3),
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Text( const Text(
@@ -556,12 +571,11 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
); );
} }
Widget _buildCurrentStep() { int get _stackIndex {
if (_currentStep == 0) return _buildStep0AccountType(); if (_accountType == 'INDIVIDUAL' && _currentStep == 2) {
if (_currentStep == 1) return _buildStep1Personal(); return 3;
if (_accountType == 'BUSINESS' && _currentStep == 2) }
return _buildStep2Business(); return _currentStep;
return _buildStep3Consent(); // Step 2 (Individual) or Step 3 (Business)
} }
@override @override
@@ -587,28 +601,38 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
child: Center( child: Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600), constraints: const BoxConstraints(maxWidth: 600),
child: AnimatedSwitcher( child: IndexedStack(
duration: const Duration(milliseconds: 300), index: _stackIndex,
switchInCurve: Curves.easeOutCubic, children: [
switchOutCurve: Curves.easeInCubic, SingleChildScrollView(
transitionBuilder: (child, animation) { padding: const EdgeInsets.symmetric(
return SlideTransition( horizontal: 24.0,
position: Tween<Offset>( vertical: 16.0,
begin: const Offset(0.05, 0), ),
end: Offset.zero, child: _buildStep0AccountType(),
).animate(animation),
child: FadeTransition(opacity: animation, child: child),
);
},
child: SingleChildScrollView(
key: ValueKey(_currentStep),
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
), ),
child: _buildCurrentStep(), SingleChildScrollView(
), padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep1Personal(),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep2Business(),
),
SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
vertical: 16.0,
),
child: _buildStep3Consent(),
),
],
), ),
), ),
), ),

View File

@@ -13,6 +13,8 @@ class BusinessProfile {
final String? emailId; final String? emailId;
final String? panNumber; final String? panNumber;
final String? gstin; final String? gstin;
final String? natureOfBusiness;
final String? msmeNumber;
BusinessProfile({ BusinessProfile({
this.id, this.id,
@@ -29,13 +31,15 @@ class BusinessProfile {
this.emailId, this.emailId,
this.panNumber, this.panNumber,
this.gstin, this.gstin,
this.natureOfBusiness,
this.msmeNumber,
}); });
factory BusinessProfile.fromJson(Map<String, dynamic> json) { factory BusinessProfile.fromJson(Map<String, dynamic> json) {
return BusinessProfile( return BusinessProfile(
id: json['id'], id: json['id'],
userId: json['userId'], userId: json['userId'],
businessName: json['businessName'], businessName: json['businessName'] ?? '',
industry: json['industry'], industry: json['industry'],
taxNumber: json['taxNumber'], taxNumber: json['taxNumber'],
currency: json['currency'], currency: json['currency'],
@@ -47,6 +51,8 @@ class BusinessProfile {
emailId: json['emailId'], emailId: json['emailId'],
panNumber: json['panNumber'], panNumber: json['panNumber'],
gstin: json['gstin'], gstin: json['gstin'],
natureOfBusiness: json['natureOfBusiness'] ?? json['industry'],
msmeNumber: json['msmeNumber'],
); );
} }
@@ -66,6 +72,8 @@ class BusinessProfile {
'emailId': emailId, 'emailId': emailId,
'panNumber': panNumber, 'panNumber': panNumber,
'gstin': gstin, 'gstin': gstin,
'natureOfBusiness': natureOfBusiness,
'msmeNumber': msmeNumber,
}; };
} }
@@ -82,6 +90,8 @@ class BusinessProfile {
String? emailId, String? emailId,
String? panNumber, String? panNumber,
String? gstin, String? gstin,
String? natureOfBusiness,
String? msmeNumber,
}) { }) {
return BusinessProfile( return BusinessProfile(
id: id, id: id,
@@ -98,6 +108,8 @@ class BusinessProfile {
emailId: emailId ?? this.emailId, emailId: emailId ?? this.emailId,
panNumber: panNumber ?? this.panNumber, panNumber: panNumber ?? this.panNumber,
gstin: gstin ?? this.gstin, gstin: gstin ?? this.gstin,
natureOfBusiness: natureOfBusiness ?? this.natureOfBusiness,
msmeNumber: msmeNumber ?? this.msmeNumber,
); );
} }
} }

View File

@@ -34,6 +34,8 @@ class BusinessHubScreen extends ConsumerWidget {
final bool showInventory = featureState?.inventoryManagement ?? false; final bool showInventory = featureState?.inventoryManagement ?? false;
final bool showSales = featureState?.salesManagement ?? false; final bool showSales = featureState?.salesManagement ?? false;
final bool showPurchase = featureState?.purchaseManagement ?? false; final bool showPurchase = featureState?.purchaseManagement ?? false;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final bool isJewellery = nature == 'JEWELLERY';
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
@@ -200,7 +202,7 @@ class BusinessHubScreen extends ConsumerWidget {
), ),
isDesktop: isDesktop, isDesktop: isDesktop,
), ),
if (showInventory) if (showInventory && isJewellery)
_buildActionCard( _buildActionCard(
context, context,
'Daily Rates', 'Daily Rates',

View File

@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/product_categories_provider.dart'; import '../providers/product_categories_provider.dart';
import '../../../core/theme/nature_colors.dart'; import '../../../core/theme/nature_colors.dart';
import '../../business/providers/business_mode_provider.dart'; import '../../business/providers/business_mode_provider.dart';
import '../../business/providers/business_provider.dart';
import '../../../core/theme/app_theme.dart'; import '../../../core/theme/app_theme.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'dart:ui'; import 'dart:ui';
@@ -99,6 +100,18 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
final children = allCategories.where((c) => c.parentCategoryId == category.id).toList(); final children = allCategories.where((c) => c.parentCategoryId == category.id).toList();
final isLeaf = children.isEmpty && !category.hasChild; final isLeaf = children.isEmpty && !category.hasChild;
final hasCommodity = category.commodityCode != null && category.commodityCode != 'NONE' && category.commodityCode!.isNotEmpty;
final hasHsn = category.defaultHsn != null && category.defaultHsn!.isNotEmpty;
String? subtitleText;
if (hasCommodity && hasHsn) {
subtitleText = '${category.commodityCode} • HSN: ${category.defaultHsn}';
} else if (hasCommodity) {
subtitleText = category.commodityCode;
} else if (hasHsn) {
subtitleText = 'HSN: ${category.defaultHsn}';
}
// A nice frosted glass card // A nice frosted glass card
return Container( return Container(
margin: EdgeInsets.only(left: depth * 16.0, bottom: 8), margin: EdgeInsets.only(left: depth * 16.0, bottom: 8),
@@ -136,8 +149,8 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
child: const Icon(LucideIcons.tag, size: 20, color: Colors.blue), child: const Icon(LucideIcons.tag, size: 20, color: Colors.blue),
), ),
title: Text(category.name, style: const TextStyle(fontWeight: FontWeight.w600)), title: Text(category.name, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: category.commodityCode != null subtitle: subtitleText != null
? Text('${category.commodityCode} • HSN: ${category.defaultHsn ?? "N/A"}', ? Text(subtitleText,
style: TextStyle(fontSize: 12, color: Colors.grey.shade600)) style: TextStyle(fontSize: 12, color: Colors.grey.shade600))
: null, : null,
trailing: Row( trailing: Row(
@@ -186,35 +199,77 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
Widget _buildCategoryPopupMenu(ProductCategory category, bool isLeaf) { Widget _buildCategoryPopupMenu(ProductCategory category, bool isLeaf) {
return PopupMenuButton<String>( return PopupMenuButton<String>(
icon: const Icon(LucideIcons.moreVertical, color: Colors.grey), icon: const Icon(LucideIcons.ellipsisVertical, size: 18, color: Colors.grey),
onSelected: (value) { shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
if (value == 'edit') { onSelected: (val) {
if (val == 'edit') {
_showAddCategorySheet(context, category, ref, isEdit: true); _showAddCategorySheet(context, category, ref, isEdit: true);
} else if (value == 'add_sub') { } else if (val == 'addSub') {
_showAddCategorySheet(context, category, ref, isEdit: false); _showAddCategorySheet(context, category, ref, isEdit: false);
} else if (value == 'delete') { } else if (val == 'delete') {
if (category.id != null) { _confirmDelete(category);
ref.read(productCategoriesProvider.notifier).deleteCategory(category.id!);
}
} }
}, },
itemBuilder: (context) => [ itemBuilder: (context) => [
const PopupMenuItem(
value: 'edit',
child: Row(children: [Icon(LucideIcons.edit, size: 18), SizedBox(width: 8), Text('Edit')]),
),
if (!isLeaf) if (!isLeaf)
const PopupMenuItem( const PopupMenuItem(
value: 'add_sub', value: 'addSub',
child: Row(children: [Icon(LucideIcons.plus, size: 18), SizedBox(width: 8), Text('Add Subcategory')]), child: Row(
children: [
Icon(LucideIcons.folderPlus, size: 18, color: Colors.blue),
SizedBox(width: 12),
Text('Add Subcategory'),
],
),
), ),
const PopupMenuItem(
value: 'edit',
child: Row(
children: [
Icon(LucideIcons.pencil, size: 18, color: Colors.orange),
SizedBox(width: 12),
Text('Edit Details'),
],
),
),
const PopupMenuItem( const PopupMenuItem(
value: 'delete', value: 'delete',
child: Row(children: [Icon(LucideIcons.trash2, size: 18, color: Colors.red), SizedBox(width: 8), Text('Delete', style: TextStyle(color: Colors.red))]), child: Row(
children: [
Icon(LucideIcons.trash2, size: 18, color: Colors.red),
SizedBox(width: 12),
Text('Delete Category', style: TextStyle(color: Colors.red)),
],
),
), ),
], ],
); );
} }
void _confirmDelete(ProductCategory category) {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text('Delete Category?'),
content: Text('Are you sure you want to delete "${category.name}"? If it has subcategories or products, they will also be affected.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red, foregroundColor: Colors.white),
onPressed: () {
ref.read(productCategoriesProvider.notifier).deleteCategory(category.id!);
Navigator.pop(context);
},
child: const Text('Delete'),
),
],
),
);
}
} }
void _showAddCategorySheet(BuildContext context, ProductCategory? parentOrCategory, WidgetRef ref, {bool isEdit = false}) { void _showAddCategorySheet(BuildContext context, ProductCategory? parentOrCategory, WidgetRef ref, {bool isEdit = false}) {
@@ -251,7 +306,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
bool _huidRequired = false; bool _huidRequired = false;
String _commodityCode = 'XAU'; String _commodityCode = 'XAU';
String _makingChargeType = 'PER_GRAM'; String _makingChargeType = 'PER_GRAM';
String _baseUnit = 'g'; String _baseUnit = 'pcs';
final List<Map<String, String>> _commodities = [ final List<Map<String, String>> _commodities = [
{'code': 'XAU', 'label': 'XAU - Gold'}, {'code': 'XAU', 'label': 'XAU - Gold'},
@@ -264,6 +319,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final profile = ref.read(businessProfileProvider).value;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
_baseUnit = isJewellery ? 'g' : 'pcs';
_commodityCode = isJewellery ? 'XAU' : 'NONE';
if (widget.categoryToEdit != null) { if (widget.categoryToEdit != null) {
final c = widget.categoryToEdit!; final c = widget.categoryToEdit!;
_selectedParentId = c.parentCategoryId; _selectedParentId = c.parentCategoryId;
@@ -275,14 +337,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
_makingChargeController.text = c.defaultMakingCharge?.toString() ?? ''; _makingChargeController.text = c.defaultMakingCharge?.toString() ?? '';
_purityFactorController.text = formatPurity(c.purityFactor); _purityFactorController.text = formatPurity(c.purityFactor);
_huidRequired = c.huidRequired; _huidRequired = c.huidRequired;
_commodityCode = c.commodityCode ?? 'XAU'; _commodityCode = c.commodityCode ?? (isJewellery ? 'XAU' : 'NONE');
// Handle legacy or varying enum string values // Handle legacy or varying enum string values
_makingChargeType = c.makingChargeType ?? 'PER_GRAM'; _makingChargeType = c.makingChargeType ?? 'PER_GRAM';
if (_makingChargeType == 'PER_GM') _makingChargeType = 'PER_GRAM'; if (_makingChargeType == 'PER_GM') _makingChargeType = 'PER_GRAM';
_baseUnit = c.baseUnit ?? (isJewellery ? 'g' : 'pcs');
_baseUnit = c.baseUnit ?? 'g';
if (_baseUnit == 'gm') _baseUnit = 'g'; if (_baseUnit == 'gm') _baseUnit = 'g';
} }
} else if (widget.parent != null) { } else if (widget.parent != null) {
@@ -302,18 +363,22 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
void _submit() { void _submit() {
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
final profile = ref.read(businessProfileProvider).value;
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
final newCategory = ProductCategory( final newCategory = ProductCategory(
id: widget.categoryToEdit?.id, id: widget.categoryToEdit?.id,
name: _nameController.text.trim(), name: _nameController.text.trim(),
parentCategoryId: _selectedParentId, parentCategoryId: _selectedParentId,
hasChild: !_isLeaf, hasChild: !_isLeaf,
commodityCode: _isLeaf ? _commodityCode : null, commodityCode: _isLeaf ? (isJewellery ? _commodityCode : 'NONE') : null,
purityFactor: _isLeaf ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0, purityFactor: _isLeaf ? (isJewellery ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0) : 1.0,
defaultHsn: _isLeaf ? _hsnController.text.trim() : null, defaultHsn: _isLeaf ? _hsnController.text.trim() : null,
defaultGst: _isLeaf ? double.tryParse(_gstController.text) : null, defaultGst: _isLeaf ? double.tryParse(_gstController.text) : null,
huidRequired: _isLeaf ? _huidRequired : false, huidRequired: _isLeaf ? (isJewellery ? _huidRequired : false) : false,
defaultMakingCharge: _isLeaf ? double.tryParse(_makingChargeController.text) : null, defaultMakingCharge: _isLeaf && isJewellery ? double.tryParse(_makingChargeController.text) : null,
makingChargeType: _isLeaf ? _makingChargeType : null, makingChargeType: _isLeaf && isJewellery ? _makingChargeType : null,
baseUnit: _isLeaf ? _baseUnit : 'pcs', baseUnit: _isLeaf ? _baseUnit : 'pcs',
); );
@@ -330,6 +395,9 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).brightness == Brightness.dark;
final isBusinessMode = ref.watch(businessModeProvider); final isBusinessMode = ref.watch(businessModeProvider);
final businessProfile = ref.watch(businessProfileProvider).value;
final nature = (businessProfile?.natureOfBusiness ?? businessProfile?.industry ?? 'JEWELLERY').toUpperCase();
final isJewellery = nature == 'JEWELLERY';
return Container( return Container(
height: MediaQuery.of(context).size.height * 0.85, height: MediaQuery.of(context).size.height * 0.85,
@@ -461,7 +529,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
const Text('Product Defaults', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const Text('Product Defaults', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16), const SizedBox(height: 16),
if (isBusinessMode) ...[ if (isJewellery && isBusinessMode) ...[
Row( Row(
children: [ children: [
Expanded( Expanded(
@@ -516,51 +584,66 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
SwitchListTile( if (isJewellery) ...[
title: const Text('HUID Required'), SwitchListTile(
subtitle: const Text('Is Hallmark Unique Identification mandatory?'), title: const Text('HUID Required'),
value: _huidRequired, subtitle: const Text('Is Hallmark Unique Identification mandatory?'),
onChanged: (val) => setState(() => _huidRequired = val), value: _huidRequired,
contentPadding: EdgeInsets.zero, onChanged: (val) => setState(() => _huidRequired = val),
), contentPadding: EdgeInsets.zero,
const SizedBox(height: 16), ),
const SizedBox(height: 16),
Row(
children: [ Row(
Expanded( children: [
child: DropdownButtonFormField<String>( Expanded(
value: _makingChargeType, child: DropdownButtonFormField<String>(
decoration: InputDecoration( value: _makingChargeType,
labelText: 'Making Charge Type', decoration: const InputDecoration(
labelText: 'Making Charge Type',
),
items: const [
DropdownMenuItem(value: 'PER_GRAM', child: Text('Per Gram')),
DropdownMenuItem(value: 'PER_PIECE', child: Text('Per Piece')),
DropdownMenuItem(value: 'PERCENTAGE', child: Text('Percentage')),
],
onChanged: (val) => setState(() => _makingChargeType = val!),
), ),
items: const [
DropdownMenuItem(value: 'PER_GRAM', child: Text('Per Gram')),
DropdownMenuItem(value: 'PER_PIECE', child: Text('Per Piece')),
DropdownMenuItem(value: 'PERCENTAGE', child: Text('Percentage')),
],
onChanged: (val) => setState(() => _makingChargeType = val!),
), ),
), const SizedBox(width: 16),
const SizedBox(width: 16), Expanded(
Expanded( child: _buildTextField(
child: _buildTextField( controller: _makingChargeController,
controller: _makingChargeController, label: 'Default Charge',
label: 'Default Charge', keyboardType: const TextInputType.numberWithOptions(decimal: true),
keyboardType: const TextInputType.numberWithOptions(decimal: true), ),
), ),
), ],
], ),
), const SizedBox(height: 16),
const SizedBox(height: 16), ],
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: _baseUnit, value: _baseUnit,
decoration: InputDecoration( decoration: const InputDecoration(
labelText: 'Base Unit', labelText: 'Base Unit',
), ),
items: const [ items: [
DropdownMenuItem(value: 'g', child: Text('Grams (g)')), if (isJewellery) ...const [
DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')), DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')), DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')),
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')),
] else ...const [
DropdownMenuItem(value: 'pcs', child: Text('Pieces (pcs)')),
DropdownMenuItem(value: 'unit', child: Text('Units (unit)')),
DropdownMenuItem(value: 'box', child: Text('Boxes (box)')),
DropdownMenuItem(value: 'set', child: Text('Sets (set)')),
DropdownMenuItem(value: 'pair', child: Text('Pairs (pair)')),
DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
DropdownMenuItem(value: 'kg', child: Text('Kilograms (kg)')),
DropdownMenuItem(value: 'm', child: Text('Meters (m)')),
DropdownMenuItem(value: 'l', child: Text('Liters (l)')),
],
], ],
onChanged: (val) => setState(() => _baseUnit = val!), onChanged: (val) => setState(() => _baseUnit = val!),
), ),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

@@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="description" content="KIFI - Modern Financial Ledger & Jewellery Business Suite"> <meta name="description" content="KIFI - Modern Financial Ledger & Business ERP">
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
@@ -16,7 +16,7 @@
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>KIFI - Financial Ledger & Jewellery ERP</title> <title>KIFI - Financial Ledger & Business ERP</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<style> <style>
body { body {
@@ -85,9 +85,9 @@
</head> </head>
<body> <body>
<div class="splash-container"> <div class="splash-container">
<div class="splash-logo">💎</div> <img src="icons/Icon-192.png" alt="KIFI" style="width: 64px; height: 64px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.3); animation: pulse 2s infinite ease-in-out;">
<div class="splash-title">KIFI</div> <div class="splash-title">KIFI</div>
<div class="splash-sub">Financial Ledger & Jewellery ERP</div> <div class="splash-sub">Financial Ledger & Business ERP</div>
<div class="spinner"></div> <div class="spinner"></div>
</div> </div>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>

View File

@@ -32,4 +32,4 @@
"purpose": "maskable" "purpose": "maskable"
} }
] ]
} }