Fixed web loading screen and setup related issues
@@ -1,4 +1,7 @@
|
||||
flutter_icons:
|
||||
android: true
|
||||
ios: true
|
||||
web:
|
||||
generate: true
|
||||
image_path: "assets/icon.png"
|
||||
image_path: "assets/icon.png"
|
||||
|
||||
@@ -26,6 +26,8 @@ class DesktopSidebar extends ConsumerWidget {
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final isBusinessMode = ref.watch(businessModeProvider);
|
||||
final businessProfile = ref.watch(businessProfileProvider).asData?.value;
|
||||
final nature = (businessProfile?.natureOfBusiness ?? businessProfile?.industry ?? 'JEWELLERY').toUpperCase();
|
||||
final isJewellery = nature == 'JEWELLERY';
|
||||
final ratesAsync = ref.watch(commodityRatesProvider);
|
||||
|
||||
return Container(
|
||||
@@ -51,22 +53,23 @@ class DesktopSidebar extends ConsumerWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFFD4AF37), Color(0xFFAA771C)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFFD4AF37).withValues(alpha: 0.3),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(LucideIcons.gem, color: Colors.white, size: 22),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Image.asset(
|
||||
'assets/icon.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -276,7 +279,8 @@ class DesktopSidebar extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
// 4. Live Metal Rate Widget in Sidebar
|
||||
// 4. Live Metal Rate Widget in Sidebar (Jewellery only)
|
||||
if (isJewellery)
|
||||
ratesAsync.when(
|
||||
data: (rates) {
|
||||
if (rates.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
@@ -131,19 +131,28 @@ class _AuthScreenState extends ConsumerState<AuthScreen>
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Container(
|
||||
Center(
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
blurRadius: 16,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
child: Center(
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: Image.asset(
|
||||
'assets/logo.png',
|
||||
'assets/icon.png',
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.contain,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -12,8 +12,7 @@ class SetupWizardScreen extends StatefulWidget {
|
||||
State<SetupWizardScreen> createState() => _SetupWizardScreenState();
|
||||
}
|
||||
|
||||
class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _SetupWizardScreenState extends State<SetupWizardScreen> {
|
||||
bool _isLoading = false;
|
||||
int _currentStep = 0;
|
||||
String _accountType = 'INDIVIDUAL';
|
||||
@@ -40,6 +39,19 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
|
||||
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 {
|
||||
if (username.length < 3) return;
|
||||
setState(() => _checkingUsername = true);
|
||||
@@ -111,6 +123,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
}
|
||||
|
||||
void _nextStep() {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_currentStep == 1) {
|
||||
if (_nameController.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@@ -150,6 +163,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
}
|
||||
|
||||
void _prevStep() {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_currentStep > 0) {
|
||||
setState(() => _currentStep--);
|
||||
}
|
||||
@@ -209,7 +223,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
|
||||
Widget _buildStep0AccountType() {
|
||||
return Column(
|
||||
key: const ValueKey(0),
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
@@ -324,7 +337,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
|
||||
Widget _buildStep1Personal() {
|
||||
return Column(
|
||||
key: const ValueKey(1),
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
@@ -354,6 +366,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: _checkingUsername
|
||||
? const SizedBox(
|
||||
key: ValueKey('user_check_spinner'),
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
@@ -362,7 +375,7 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
_isUsernameAvailable
|
||||
? LucideIcons.checkCircle2
|
||||
: LucideIcons.xCircle,
|
||||
key: ValueKey(_isUsernameAvailable),
|
||||
key: ValueKey('user_avail_${_isUsernameAvailable}'),
|
||||
color: _usernameController.text.isEmpty
|
||||
? Colors.transparent
|
||||
: (_isUsernameAvailable
|
||||
@@ -388,7 +401,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
|
||||
Widget _buildStep2Business() {
|
||||
return Column(
|
||||
key: const ValueKey(2),
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
@@ -410,6 +422,10 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
value: 'JEWELLERY',
|
||||
child: Text('Jewellery', style: TextStyle(color: Colors.black87)),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 'ECOMMERCE',
|
||||
child: Text('E-Commerce Seller', style: TextStyle(color: Colors.black87)),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 'PROJECT_MANAGEMENT',
|
||||
child: Text(
|
||||
@@ -474,7 +490,6 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
|
||||
Widget _buildStep3Consent() {
|
||||
return Column(
|
||||
key: const ValueKey(3),
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
@@ -556,12 +571,11 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCurrentStep() {
|
||||
if (_currentStep == 0) return _buildStep0AccountType();
|
||||
if (_currentStep == 1) return _buildStep1Personal();
|
||||
if (_accountType == 'BUSINESS' && _currentStep == 2)
|
||||
return _buildStep2Business();
|
||||
return _buildStep3Consent(); // Step 2 (Individual) or Step 3 (Business)
|
||||
int get _stackIndex {
|
||||
if (_accountType == 'INDIVIDUAL' && _currentStep == 2) {
|
||||
return 3;
|
||||
}
|
||||
return _currentStep;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -587,28 +601,38 @@ class _SetupWizardScreenState extends State<SetupWizardScreen>
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 600),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
switchInCurve: Curves.easeOutCubic,
|
||||
switchOutCurve: Curves.easeInCubic,
|
||||
transitionBuilder: (child, animation) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.05, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
);
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
key: ValueKey(_currentStep),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: IndexedStack(
|
||||
index: _stackIndex,
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: _buildCurrentStep(),
|
||||
child: _buildStep0AccountType(),
|
||||
),
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,6 +13,8 @@ class BusinessProfile {
|
||||
final String? emailId;
|
||||
final String? panNumber;
|
||||
final String? gstin;
|
||||
final String? natureOfBusiness;
|
||||
final String? msmeNumber;
|
||||
|
||||
BusinessProfile({
|
||||
this.id,
|
||||
@@ -29,13 +31,15 @@ class BusinessProfile {
|
||||
this.emailId,
|
||||
this.panNumber,
|
||||
this.gstin,
|
||||
this.natureOfBusiness,
|
||||
this.msmeNumber,
|
||||
});
|
||||
|
||||
factory BusinessProfile.fromJson(Map<String, dynamic> json) {
|
||||
return BusinessProfile(
|
||||
id: json['id'],
|
||||
userId: json['userId'],
|
||||
businessName: json['businessName'],
|
||||
businessName: json['businessName'] ?? '',
|
||||
industry: json['industry'],
|
||||
taxNumber: json['taxNumber'],
|
||||
currency: json['currency'],
|
||||
@@ -47,6 +51,8 @@ class BusinessProfile {
|
||||
emailId: json['emailId'],
|
||||
panNumber: json['panNumber'],
|
||||
gstin: json['gstin'],
|
||||
natureOfBusiness: json['natureOfBusiness'] ?? json['industry'],
|
||||
msmeNumber: json['msmeNumber'],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +72,8 @@ class BusinessProfile {
|
||||
'emailId': emailId,
|
||||
'panNumber': panNumber,
|
||||
'gstin': gstin,
|
||||
'natureOfBusiness': natureOfBusiness,
|
||||
'msmeNumber': msmeNumber,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +90,8 @@ class BusinessProfile {
|
||||
String? emailId,
|
||||
String? panNumber,
|
||||
String? gstin,
|
||||
String? natureOfBusiness,
|
||||
String? msmeNumber,
|
||||
}) {
|
||||
return BusinessProfile(
|
||||
id: id,
|
||||
@@ -98,6 +108,8 @@ class BusinessProfile {
|
||||
emailId: emailId ?? this.emailId,
|
||||
panNumber: panNumber ?? this.panNumber,
|
||||
gstin: gstin ?? this.gstin,
|
||||
natureOfBusiness: natureOfBusiness ?? this.natureOfBusiness,
|
||||
msmeNumber: msmeNumber ?? this.msmeNumber,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ class BusinessHubScreen extends ConsumerWidget {
|
||||
final bool showInventory = featureState?.inventoryManagement ?? false;
|
||||
final bool showSales = featureState?.salesManagement ?? false;
|
||||
final bool showPurchase = featureState?.purchaseManagement ?? false;
|
||||
final nature = (profile?.natureOfBusiness ?? profile?.industry ?? 'JEWELLERY').toUpperCase();
|
||||
final bool isJewellery = nature == 'JEWELLERY';
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
@@ -200,7 +202,7 @@ class BusinessHubScreen extends ConsumerWidget {
|
||||
),
|
||||
isDesktop: isDesktop,
|
||||
),
|
||||
if (showInventory)
|
||||
if (showInventory && isJewellery)
|
||||
_buildActionCard(
|
||||
context,
|
||||
'Daily Rates',
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../providers/product_categories_provider.dart';
|
||||
import '../../../core/theme/nature_colors.dart';
|
||||
import '../../business/providers/business_mode_provider.dart';
|
||||
import '../../business/providers/business_provider.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'dart:ui';
|
||||
@@ -99,6 +100,18 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
|
||||
final children = allCategories.where((c) => c.parentCategoryId == category.id).toList();
|
||||
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
|
||||
return Container(
|
||||
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),
|
||||
),
|
||||
title: Text(category.name, style: const TextStyle(fontWeight: FontWeight.w600)),
|
||||
subtitle: category.commodityCode != null
|
||||
? Text('${category.commodityCode} • HSN: ${category.defaultHsn ?? "N/A"}',
|
||||
subtitle: subtitleText != null
|
||||
? Text(subtitleText,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600))
|
||||
: null,
|
||||
trailing: Row(
|
||||
@@ -186,35 +199,77 @@ class _CategoryManagementScreenState extends ConsumerState<CategoryManagementScr
|
||||
|
||||
Widget _buildCategoryPopupMenu(ProductCategory category, bool isLeaf) {
|
||||
return PopupMenuButton<String>(
|
||||
icon: const Icon(LucideIcons.moreVertical, color: Colors.grey),
|
||||
onSelected: (value) {
|
||||
if (value == 'edit') {
|
||||
icon: const Icon(LucideIcons.ellipsisVertical, size: 18, color: Colors.grey),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
onSelected: (val) {
|
||||
if (val == 'edit') {
|
||||
_showAddCategorySheet(context, category, ref, isEdit: true);
|
||||
} else if (value == 'add_sub') {
|
||||
} else if (val == 'addSub') {
|
||||
_showAddCategorySheet(context, category, ref, isEdit: false);
|
||||
} else if (value == 'delete') {
|
||||
if (category.id != null) {
|
||||
ref.read(productCategoriesProvider.notifier).deleteCategory(category.id!);
|
||||
}
|
||||
} else if (val == 'delete') {
|
||||
_confirmDelete(category);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
const PopupMenuItem(
|
||||
value: 'edit',
|
||||
child: Row(children: [Icon(LucideIcons.edit, size: 18), SizedBox(width: 8), Text('Edit')]),
|
||||
),
|
||||
if (!isLeaf)
|
||||
const PopupMenuItem(
|
||||
value: 'add_sub',
|
||||
child: Row(children: [Icon(LucideIcons.plus, size: 18), SizedBox(width: 8), Text('Add Subcategory')]),
|
||||
value: 'addSub',
|
||||
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(
|
||||
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}) {
|
||||
@@ -251,7 +306,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
bool _huidRequired = false;
|
||||
String _commodityCode = 'XAU';
|
||||
String _makingChargeType = 'PER_GRAM';
|
||||
String _baseUnit = 'g';
|
||||
String _baseUnit = 'pcs';
|
||||
|
||||
final List<Map<String, String>> _commodities = [
|
||||
{'code': 'XAU', 'label': 'XAU - Gold'},
|
||||
@@ -264,6 +319,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
@override
|
||||
void 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) {
|
||||
final c = widget.categoryToEdit!;
|
||||
_selectedParentId = c.parentCategoryId;
|
||||
@@ -275,14 +337,13 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
_makingChargeController.text = c.defaultMakingCharge?.toString() ?? '';
|
||||
_purityFactorController.text = formatPurity(c.purityFactor);
|
||||
_huidRequired = c.huidRequired;
|
||||
_commodityCode = c.commodityCode ?? 'XAU';
|
||||
_commodityCode = c.commodityCode ?? (isJewellery ? 'XAU' : 'NONE');
|
||||
|
||||
// Handle legacy or varying enum string values
|
||||
_makingChargeType = c.makingChargeType ?? 'PER_GRAM';
|
||||
if (_makingChargeType == 'PER_GM') _makingChargeType = 'PER_GRAM';
|
||||
|
||||
|
||||
_baseUnit = c.baseUnit ?? 'g';
|
||||
_baseUnit = c.baseUnit ?? (isJewellery ? 'g' : 'pcs');
|
||||
if (_baseUnit == 'gm') _baseUnit = 'g';
|
||||
}
|
||||
} else if (widget.parent != null) {
|
||||
@@ -302,18 +363,22 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
|
||||
void _submit() {
|
||||
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(
|
||||
id: widget.categoryToEdit?.id,
|
||||
name: _nameController.text.trim(),
|
||||
parentCategoryId: _selectedParentId,
|
||||
hasChild: !_isLeaf,
|
||||
commodityCode: _isLeaf ? _commodityCode : null,
|
||||
purityFactor: _isLeaf ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0,
|
||||
commodityCode: _isLeaf ? (isJewellery ? _commodityCode : 'NONE') : null,
|
||||
purityFactor: _isLeaf ? (isJewellery ? normalizePurity(double.tryParse(_purityFactorController.text)) : 1.0) : 1.0,
|
||||
defaultHsn: _isLeaf ? _hsnController.text.trim() : null,
|
||||
defaultGst: _isLeaf ? double.tryParse(_gstController.text) : null,
|
||||
huidRequired: _isLeaf ? _huidRequired : false,
|
||||
defaultMakingCharge: _isLeaf ? double.tryParse(_makingChargeController.text) : null,
|
||||
makingChargeType: _isLeaf ? _makingChargeType : null,
|
||||
huidRequired: _isLeaf ? (isJewellery ? _huidRequired : false) : false,
|
||||
defaultMakingCharge: _isLeaf && isJewellery ? double.tryParse(_makingChargeController.text) : null,
|
||||
makingChargeType: _isLeaf && isJewellery ? _makingChargeType : null,
|
||||
baseUnit: _isLeaf ? _baseUnit : 'pcs',
|
||||
);
|
||||
|
||||
@@ -330,6 +395,9 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
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(
|
||||
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 SizedBox(height: 16),
|
||||
|
||||
if (isBusinessMode) ...[
|
||||
if (isJewellery && isBusinessMode) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -516,6 +584,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
if (isJewellery) ...[
|
||||
SwitchListTile(
|
||||
title: const Text('HUID Required'),
|
||||
subtitle: const Text('Is Hallmark Unique Identification mandatory?'),
|
||||
@@ -530,7 +599,7 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: _makingChargeType,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Making Charge Type',
|
||||
),
|
||||
items: const [
|
||||
@@ -552,15 +621,29 @@ class _CategoryFormSheetState extends ConsumerState<CategoryFormSheet> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
DropdownButtonFormField<String>(
|
||||
value: _baseUnit,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Base Unit',
|
||||
),
|
||||
items: const [
|
||||
items: [
|
||||
if (isJewellery) ...const [
|
||||
DropdownMenuItem(value: 'g', child: Text('Grams (g)')),
|
||||
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!),
|
||||
),
|
||||
|
||||
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 795 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 161 KiB |
@@ -5,7 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<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="description" content="KIFI - Modern Financial Ledger & Jewellery Business Suite">
|
||||
<meta name="description" content="KIFI - Modern Financial Ledger & Business ERP">
|
||||
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -16,7 +16,7 @@
|
||||
<!-- Favicon -->
|
||||
<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">
|
||||
<style>
|
||||
body {
|
||||
@@ -85,9 +85,9 @@
|
||||
</head>
|
||||
<body>
|
||||
<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-sub">Financial Ledger & Jewellery ERP</div>
|
||||
<div class="splash-sub">Financial Ledger & Business ERP</div>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<script src="flutter_bootstrap.js" async></script>
|
||||
|
||||