first commit
This commit is contained in:
587
lib/Screens/Customers/party_list_screen.dart
Normal file
587
lib/Screens/Customers/party_list_screen.dart
Normal file
@@ -0,0 +1,587 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:iconly/iconly.dart';
|
||||
import 'package:mobile_pos/Const/api_config.dart';
|
||||
import 'package:mobile_pos/Screens/Sales/provider/sales_cart_provider.dart';
|
||||
import 'package:mobile_pos/Screens/Customers/Provider/customer_provider.dart';
|
||||
import 'package:mobile_pos/Screens/Customers/add_customer.dart';
|
||||
import 'package:mobile_pos/Screens/Customers/customer_details.dart';
|
||||
import 'package:mobile_pos/Screens/Sales/add_sales.dart';
|
||||
import 'package:mobile_pos/constant.dart';
|
||||
import 'package:mobile_pos/core/theme/_app_colors.dart';
|
||||
import 'package:mobile_pos/generated/l10n.dart' as lang;
|
||||
import 'package:mobile_pos/widgets/empty_widget/_empty_widget.dart';
|
||||
import 'package:nb_utils/nb_utils.dart';
|
||||
|
||||
import '../../GlobalComponents/glonal_popup.dart';
|
||||
import '../../Provider/profile_provider.dart';
|
||||
import '../../currency.dart';
|
||||
import '../../service/check_actions_when_no_branch.dart';
|
||||
import '../../service/check_user_role_permission_provider.dart';
|
||||
import 'Repo/parties_repo.dart';
|
||||
|
||||
// 1. Combine the screens into a single class with a parameter for mode
|
||||
class PartyListScreen extends StatefulWidget {
|
||||
// Use a boolean to determine the screen's purpose
|
||||
final bool isSelectionMode;
|
||||
|
||||
const PartyListScreen({super.key, this.isSelectionMode = false});
|
||||
|
||||
@override
|
||||
State<PartyListScreen> createState() => _PartyListScreenState();
|
||||
}
|
||||
|
||||
class _PartyListScreenState extends State<PartyListScreen> {
|
||||
late Color color;
|
||||
bool _isRefreshing = false;
|
||||
bool _isSearching = false;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
Future<void> refreshData(WidgetRef ref) async {
|
||||
if (_isRefreshing) return;
|
||||
_isRefreshing = true;
|
||||
|
||||
ref.refresh(partiesProvider);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
_isRefreshing = false;
|
||||
}
|
||||
|
||||
String? partyType;
|
||||
|
||||
// Define party types based on the mode
|
||||
List<String> get availablePartyTypes {
|
||||
if (widget.isSelectionMode) {
|
||||
// For Sales/Selection mode, exclude 'Supplier'
|
||||
return [
|
||||
PartyType.customer,
|
||||
PartyType.dealer,
|
||||
PartyType.wholesaler,
|
||||
];
|
||||
} else {
|
||||
// For General List/Management mode, include all
|
||||
return [
|
||||
PartyType.customer,
|
||||
PartyType.supplier,
|
||||
PartyType.dealer,
|
||||
PartyType.wholesaler,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showDeleteConfirmationAlert({
|
||||
required BuildContext context,
|
||||
required String id,
|
||||
required WidgetRef ref,
|
||||
}) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context1) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
lang.S.of(context).confirmPassword,
|
||||
//'Confirm Delete'
|
||||
),
|
||||
content: Text(
|
||||
lang.S.of(context).areYouSureYouWant,
|
||||
//'Are you sure you want to delete this party?'
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
lang.S.of(context).cancel,
|
||||
//'Cancel'
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
final party = PartyRepository();
|
||||
await party.deleteParty(id: id, context: context, ref: ref);
|
||||
},
|
||||
child: Text(lang.S.of(context).delete,
|
||||
// 'Delete',
|
||||
style: const TextStyle(color: Colors.red)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _theme = Theme.of(context);
|
||||
return Consumer(
|
||||
builder: (context, ref, __) {
|
||||
final providerData = ref.watch(partiesProvider);
|
||||
final businessInfo = ref.watch(businessInfoProvider);
|
||||
final permissionService = PermissionService(ref);
|
||||
|
||||
// Determine App Bar Title based on mode
|
||||
final appBarTitle = widget.isSelectionMode
|
||||
? lang.S.of(context).chooseCustomer // Sales title
|
||||
: lang.S.of(context).partyList; // Management title
|
||||
|
||||
return businessInfo.when(data: (details) {
|
||||
return GlobalPopup(
|
||||
child: Scaffold(
|
||||
backgroundColor: kWhite,
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.white,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
elevation: 0.0,
|
||||
actionsPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
title: Text(
|
||||
appBarTitle,
|
||||
style: _theme.textTheme.titleMedium?.copyWith(color: Colors.black),
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator.adaptive(
|
||||
onRefresh: () => refreshData(ref),
|
||||
child: providerData.when(data: (partyList) {
|
||||
// Permission check only required for the management view
|
||||
if (!widget.isSelectionMode && !permissionService.hasPermission(Permit.partiesRead.value)) {
|
||||
return const Center(child: PermitDenyWidget());
|
||||
}
|
||||
final filteredParties = partyList.where((c) {
|
||||
final normalizedType = (c.type ?? '').toLowerCase();
|
||||
|
||||
// Filter out suppliers ONLY if in selection mode
|
||||
if (widget.isSelectionMode && normalizedType == 'supplier') {
|
||||
return false;
|
||||
}
|
||||
|
||||
final nameMatches = !_isSearching || _searchController.text.isEmpty
|
||||
? true
|
||||
: (c.name ?? '').toLowerCase().contains(_searchController.text.toLowerCase());
|
||||
|
||||
final effectiveType = normalizedType == 'retailer' ? 'customer' : normalizedType;
|
||||
|
||||
final typeMatches = partyType == null || partyType!.isEmpty ? true : effectiveType == partyType;
|
||||
|
||||
return nameMatches && typeMatches;
|
||||
}).toList();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4),
|
||||
child: TextFormField(
|
||||
controller: _searchController,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: lang.S.of(context).search,
|
||||
border: InputBorder.none,
|
||||
hintStyle: TextStyle(color: Colors.grey[600]),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xffF7F7F7),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(8),
|
||||
bottomRight: Radius.circular(8),
|
||||
)),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
hint: Text(lang.S.of(context).selectType),
|
||||
icon: partyType != null
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
color: kMainColor,
|
||||
size: 18,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
partyType = null;
|
||||
});
|
||||
},
|
||||
)
|
||||
: const Icon(Icons.keyboard_arrow_down, color: kPeraColor),
|
||||
value: partyType,
|
||||
onChanged: (String? value) {
|
||||
setState(() {
|
||||
partyType = value;
|
||||
});
|
||||
},
|
||||
// Use the list defined by the mode
|
||||
items: availablePartyTypes.map((entry) {
|
||||
final valueToStore = entry.toLowerCase();
|
||||
return DropdownMenuItem<String>(
|
||||
value: valueToStore,
|
||||
child: Text(
|
||||
getPartyTypeLabel(context, valueToStore),
|
||||
style: _theme.textTheme.bodyLarge?.copyWith(color: kTitleColor),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(color: Colors.black),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isSearching = value.isNotEmpty;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 3. Show Walk-In Customer ONLY in selection mode
|
||||
if (widget.isSelectionMode)
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
onTap: () {
|
||||
AddSalesScreen(customerModel: null).launch(context);
|
||||
ref.refresh(cartNotifier);
|
||||
},
|
||||
leading: SizedBox(
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.white,
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'images/no_shop_image.png',
|
||||
fit: BoxFit.cover,
|
||||
width: 120.0,
|
||||
height: 120.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
lang.S.of(context).walkInCustomer,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: kTitleColor,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
lang.S.of(context).guest,
|
||||
style: _theme.textTheme.bodyLarge,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios_rounded,
|
||||
size: 18,
|
||||
color: Color(0xff4B5563),
|
||||
),
|
||||
),
|
||||
filteredParties.isNotEmpty
|
||||
? Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: filteredParties.length,
|
||||
shrinkWrap: true,
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(), // Use AlwaysScrollableScrollPhysics for the main list
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
itemBuilder: (_, index) {
|
||||
final item = filteredParties[index];
|
||||
final normalizedType = (item.type ?? '').toLowerCase();
|
||||
|
||||
// Color logic (unchanged)
|
||||
color = Colors.white;
|
||||
if (normalizedType == 'retailer' || normalizedType == 'customer') {
|
||||
color = const Color(0xFF56da87);
|
||||
}
|
||||
if (normalizedType == 'wholesaler') color = const Color(0xFF25a9e0);
|
||||
if (normalizedType == 'dealer') color = const Color(0xFFff5f00);
|
||||
if (normalizedType == 'supplier') color = const Color(0xFFA569BD);
|
||||
|
||||
// final effectiveDisplayType = normalizedType == 'retailer'
|
||||
// ? 'Customer'
|
||||
// : normalizedType == 'wholesaler'
|
||||
// ? lang.S.of(context).wholesaler
|
||||
// : normalizedType == 'dealer'
|
||||
// ? lang.S.of(context).dealer
|
||||
// : normalizedType == 'supplier'
|
||||
// ? lang.S.of(context).supplier
|
||||
// : item.type ?? '';
|
||||
|
||||
String effectiveDisplayType;
|
||||
|
||||
if (normalizedType == 'retailer') {
|
||||
effectiveDisplayType = lang.S.of(context).customer;
|
||||
} else if (normalizedType == 'wholesaler') {
|
||||
effectiveDisplayType = lang.S.of(context).wholesaler;
|
||||
} else if (normalizedType == 'dealer') {
|
||||
effectiveDisplayType = lang.S.of(context).dealer;
|
||||
} else if (normalizedType == 'supplier') {
|
||||
effectiveDisplayType = lang.S.of(context).supplier;
|
||||
} else {
|
||||
effectiveDisplayType = item.type ?? '';
|
||||
}
|
||||
|
||||
// Due/Advance/No Due Logic (from previous step)
|
||||
String statusText;
|
||||
Color statusColor;
|
||||
num? statusAmount;
|
||||
|
||||
if (item.due != null && item.due! > 0) {
|
||||
statusText = lang.S.of(context).due;
|
||||
statusColor = const Color(0xFFff5f00);
|
||||
statusAmount = item.due;
|
||||
} else if (item.openingBalanceType?.toLowerCase() == 'advance' &&
|
||||
item.wallet != null &&
|
||||
item.wallet! > 0) {
|
||||
statusText = lang.S.of(context).advance;
|
||||
statusColor = DAppColors.kSecondary;
|
||||
statusAmount = item.wallet;
|
||||
} else {
|
||||
statusText = lang.S.of(context).noDue;
|
||||
statusColor = DAppColors.kSecondary;
|
||||
statusAmount = null;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
visualDensity: const VisualDensity(vertical: -2),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
onTap: () {
|
||||
// 4. OnTap action based on mode
|
||||
if (widget.isSelectionMode) {
|
||||
// Selection Mode: Go to AddSalesScreen
|
||||
AddSalesScreen(customerModel: item).launch(context);
|
||||
ref.refresh(cartNotifier);
|
||||
} else {
|
||||
// Management Mode: Go to CustomerDetails
|
||||
CustomerDetails(party: item).launch(context);
|
||||
}
|
||||
},
|
||||
leading: item.image != null
|
||||
? Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: DAppColors.kBorder, width: 0.3),
|
||||
image: DecorationImage(
|
||||
image: NetworkImage('${APIConfig.domain}${item.image ?? ''}'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: CircleAvatarWidget(name: item.name),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.name ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: kTitleColor,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
statusAmount != null ? '$currency${statusAmount.toStringAsFixed(2)}' : '',
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(fontSize: 16.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
effectiveDisplayType,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: color,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
statusText,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: statusColor,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
offset: const Offset(0, 30),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (BuildContext bc) => [
|
||||
PopupMenuItem(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CustomerDetails(party: item),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.remove_red_eye,
|
||||
color: kGreyTextColor,
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 8.0),
|
||||
Text(
|
||||
lang.S.of(context).view,
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
bool result = await checkActionWhenNoBranch(ref: ref, context: context);
|
||||
if (!permissionService.hasPermission(Permit.partiesUpdate.value)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: Text(lang.S.of(context).updatePartyWarn),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (result) {
|
||||
AddParty(customerModel: item).launch(context);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
IconlyBold.edit,
|
||||
color: kGreyTextColor,
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 8.0),
|
||||
Text(
|
||||
lang.S.of(context).edit,
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () async {
|
||||
bool result = await checkActionWhenNoBranch(ref: ref, context: context);
|
||||
if (!permissionService.hasPermission(Permit.partiesDelete.value)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Colors.red,
|
||||
content: Text(lang.S.of(context).deletePartyWarn),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (result) {
|
||||
await showDeleteConfirmationAlert(
|
||||
context: context, id: item.id.toString(), ref: ref);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
IconlyBold.delete,
|
||||
color: kGreyTextColor,
|
||||
size: 20,
|
||||
),
|
||||
SizedBox(width: 8.0),
|
||||
Text(
|
||||
lang.S.of(context).delete,
|
||||
style: TextStyle(color: kGreyTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
onSelected: (value) {
|
||||
Navigator.pushNamed(context, '$value');
|
||||
},
|
||||
child: const Icon(
|
||||
FeatherIcons.moreVertical,
|
||||
color: kGreyTextColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: EmptyWidget(
|
||||
message: TextSpan(text: lang.S.of(context).noParty),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}, error: (e, stack) {
|
||||
return Text(e.toString());
|
||||
}, loading: () {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}),
|
||||
),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
child: ElevatedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
maximumSize: const Size(double.infinity, 48),
|
||||
minimumSize: const Size(double.infinity, 48),
|
||||
disabledBackgroundColor: _theme.colorScheme.primary.withAlpha(15),
|
||||
disabledForegroundColor: const Color(0xff567DF4).withOpacity(0.05),
|
||||
),
|
||||
onPressed: () async {
|
||||
bool result = await checkActionWhenNoBranch(ref: ref, context: context);
|
||||
// Check logic based on business info (kept original logic)
|
||||
if (result) {
|
||||
if (details.data?.subscriptionDate != null && details.data?.enrolledPlan != null) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const AddParty()));
|
||||
} else if (!widget.isSelectionMode) {
|
||||
// Allow navigation if not in selection mode and subscription check fails (or fix subscription check)
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => const AddParty()));
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.add, color: Colors.white),
|
||||
iconAlignment: IconAlignment.start,
|
||||
label: Text(
|
||||
lang.S.of(context).addCustomer,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _theme.textTheme.bodyMedium?.copyWith(
|
||||
color: _theme.colorScheme.primaryContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, error: (e, stack) {
|
||||
return Text(e.toString());
|
||||
}, loading: () {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user