first commit

This commit is contained in:
2026-02-07 15:57:09 +07:00
commit 157096f164
1153 changed files with 415766 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
import 'package:mobile_pos/model/sale_transaction_model.dart';
class Party {
Party({
this.id,
this.name,
this.businessId,
this.email,
this.branchId,
this.type,
this.phone,
this.due,
this.openingBalanceType,
this.openingBalance,
this.wallet,
this.loyaltyPoints,
this.creditLimit,
this.address,
this.image,
this.status,
this.meta,
this.sales,
this.shippingAddress,
this.billingAddress,
this.createdAt,
this.updatedAt,
});
Party.fromJson(dynamic json) {
id = json['id'];
name = json['name'];
businessId = json['business_id'];
email = json['email'];
type = json['type'];
phone = json['phone'];
branchId = json['branch_id'];
due = json['due'];
saleCount = json['sales_count'];
purchaseCount = json['purchases_count'];
totalSaleAmount = json['total_sale_amount'];
totalSalePaid = json['total_sale_paid'];
totalPurchaseAmount = json['total_purchase_amount'];
totalPurchasePaid = json['total_purchase_paid'];
totalSaleProfit = json['total_sale_profit'];
totalSaleLoss = json['total_sale_loss'];
openingBalanceType = json['opening_balance_type'];
openingBalance = json['opening_balance'];
wallet = json['wallet'];
loyaltyPoints = json['loyalty_points'];
creditLimit = json['credit_limit'];
address = json['address'];
image = json['image'];
status = json['status'];
meta = json['meta'];
shippingAddress = json['shipping_address'] != null ? ShippingAddress.fromJson(json['shipping_address']) : null;
if (json['sales'] != null) {
sales = [];
json['sales'].forEach((v) {
sales!.add(SalesTransactionModel.fromJson(v));
});
}
billingAddress = json['billing_address'] != null ? BillingAddress.fromJson(json['billing_address']) : null;
createdAt = json['created_at'];
updatedAt = json['updated_at'];
}
num? id;
String? name;
num? businessId;
String? email;
String? type;
String? phone;
num? branchId;
num? due;
num? saleCount;
num? purchaseCount;
num? totalSaleAmount;
num? totalSalePaid;
num? totalPurchaseAmount;
num? totalPurchasePaid;
// num? totalSaleLossProfit;
num? totalSaleProfit;
num? totalSaleLoss;
String? openingBalanceType;
num? openingBalance;
num? wallet;
num? loyaltyPoints;
num? creditLimit;
String? address;
String? image;
num? status;
dynamic meta;
ShippingAddress? shippingAddress;
BillingAddress? billingAddress;
List<SalesTransactionModel>? sales;
String? createdAt;
String? updatedAt;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = id;
map['branch_id'] = branchId;
map['name'] = name;
map['business_id'] = businessId;
map['email'] = email;
map['type'] = type;
map['phone'] = phone;
map['due'] = due;
map['sales_count'] = saleCount;
map['purchases_count'] = purchaseCount;
map['total_sale_amount'] = totalSaleAmount;
map['total_sale_paid'] = totalSalePaid;
map['total_purchase_amount'] = totalPurchaseAmount;
map['total_purchase_paid'] = totalPurchasePaid;
map['total_sale_profit'] = totalSaleProfit;
map['total_sale_loss'] = totalSaleLoss;
map['opening_balance_type'] = openingBalanceType;
map['opening_balance'] = openingBalance;
map['wallet'] = wallet;
map['loyalty_points'] = loyaltyPoints;
map['credit_limit'] = creditLimit;
map['address'] = address;
map['image'] = image;
map['status'] = status;
map['meta'] = meta;
map['sales'] = sales;
if (shippingAddress != null) {
map['shipping_address'] = shippingAddress?.toJson();
}
if (billingAddress != null) {
map['billing_address'] = billingAddress?.toJson();
}
map['created_at'] = createdAt;
map['updated_at'] = updatedAt;
return map;
}
}
class BillingAddress {
BillingAddress({
this.address,
this.city,
this.state,
this.zipCode,
this.country,
});
BillingAddress.fromJson(dynamic json) {
address = json['address'];
city = json['city'];
state = json['state'];
zipCode = json['zip_code'];
country = json['country'];
}
String? address;
String? city;
String? state;
String? zipCode;
String? country;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['address'] = address;
map['city'] = city;
map['state'] = state;
map['zip_code'] = zipCode;
map['country'] = country;
return map;
}
}
class ShippingAddress {
ShippingAddress({
this.address,
this.city,
this.state,
this.zipCode,
this.country,
});
ShippingAddress.fromJson(dynamic json) {
address = json['address'];
city = json['city'];
state = json['state'];
zipCode = json['zip_code'];
country = json['country'];
}
String? address;
String? city;
String? state;
String? zipCode;
String? country;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['address'] = address;
map['city'] = city;
map['state'] = state;
map['zip_code'] = zipCode;
map['country'] = country;
return map;
}
}
extension PartyListExt on List<Party> {
List<Party> getTopFiveCustomers() {
final _customerTypes = {'customer', 'dealer', 'wholesaler', 'retailer'};
final _customers = where((p) => _customerTypes.contains(p.type?.trim().toLowerCase())).toList();
if (_customers.isEmpty) return const <Party>[];
final _hasSaleAmount = _customers.any((p) => (p.totalSaleAmount ?? 0) > 0);
final _filteredList = _customers.where((p) {
if (_hasSaleAmount) {
return (p.totalSaleAmount ?? 0) > 0;
}
return (p.saleCount ?? 0) > 0;
}).toList();
if (_filteredList.isEmpty) return const <Party>[];
_filteredList.sort((a, b) {
if (_hasSaleAmount) {
return (b.totalSaleAmount ?? 0).compareTo(a.totalSaleAmount ?? 0);
}
return (b.saleCount ?? 0).compareTo(a.saleCount ?? 0);
});
return _filteredList.length > 5 ? _filteredList.sublist(0, 5) : _filteredList;
}
List<Party> getTopFiveSuppliers() {
final _suppliers = where((p) => p.type?.trim().toLowerCase() == 'supplier').toList();
if (_suppliers.isEmpty) return const <Party>[];
final _hasPurchaseAmount = _suppliers.any((p) => (p.totalPurchaseAmount ?? 0) > 0);
final _filteredList = _suppliers.where((p) {
if (_hasPurchaseAmount) {
return (p.totalPurchaseAmount ?? 0) > 0;
}
return (p.purchaseCount ?? 0) > 0;
}).toList();
if (_filteredList.isEmpty) return const <Party>[];
_filteredList.sort((a, b) {
if (_hasPurchaseAmount) {
return (b.totalPurchaseAmount ?? 0).compareTo(a.totalPurchaseAmount ?? 0);
}
return (b.purchaseCount ?? 0).compareTo(a.purchaseCount ?? 0);
});
return _filteredList.length > 5 ? _filteredList.sublist(0, 5) : _filteredList;
}
}

View File

@@ -0,0 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Screens/Customers/Model/parties_model.dart';
import '../Repo/parties_repo.dart';
PartyRepository partiesRepo = PartyRepository();
final partiesProvider = FutureProvider.autoDispose<List<Party>>((ref) => partiesRepo.fetchAllParties());

View File

@@ -0,0 +1,268 @@
//ignore_for_file: avoid_print,unused_local_variable
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:mobile_pos/Const/api_config.dart';
import '../../../Repository/constant_functions.dart';
import '../../../http_client/custome_http_client.dart';
import '../../../http_client/customer_http_client_get.dart';
import '../Model/parties_model.dart';
import '../Provider/customer_provider.dart';
import '../add_customer.dart';
class PartyRepository {
Future<List<Party>> fetchAllParties() async {
final uri = Uri.parse('${APIConfig.url}/parties');
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final response = await clientGet.get(url: uri);
if (response.statusCode == 200) {
final parsedData = jsonDecode(response.body) as Map<String, dynamic>;
final partyList = parsedData['data'] as List<dynamic>;
return partyList.map((category) => Party.fromJson(category)).toList();
// Parse into Party objects
} else {
throw Exception('Failed to fetch parties');
}
}
Future<void> addParty({
required WidgetRef ref,
required BuildContext context,
required Customer customer,
}) async {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
final uri = Uri.parse('${APIConfig.url}/parties');
var request = http.MultipartRequest('POST', uri)
..headers['Accept'] = 'application/json'
..headers['Authorization'] = await getAuthToken();
void addField(String key, String? value) {
if (value != null && value.isNotEmpty) {
request.fields[key] = value;
}
}
addField('name', customer.name);
addField('phone', customer.phone);
addField('type', customer.customerType);
addField('email', customer.email);
addField('address', customer.address);
addField('opening_balance_type', customer.openingBalanceType);
addField('opening_balance', customer.openingBalance?.toString());
addField('credit_limit', customer.creditLimit?.toString());
// Send billing and shipping address fields directly
addField('billing_address[address]', customer.billingAddress);
addField('billing_address[city]', customer.billingCity);
addField('billing_address[state]', customer.billingState);
addField('billing_address[zip_code]', customer.billingZipcode);
addField('billing_address[country]', customer.billingCountry);
addField('shipping_address[address]', customer.shippingAddress);
addField('shipping_address[city]', customer.shippingCity);
addField('shipping_address[state]', customer.shippingState);
addField('shipping_address[zip_code]', customer.shippingZipcode);
addField('shipping_address[country]', customer.shippingCountry);
print('Party Data: ${request.fields}');
final response = await customHttpClient.uploadFile(
url: uri,
fileFieldName: 'image',
file: customer.image,
fields: request.fields,
);
final responseData = await response.stream.bytesToString();
print('${responseData}');
final parsedData = jsonDecode(responseData);
print('Party Added Response: $parsedData');
request.fields.forEach((key, value) {
print('$key: $value');
});
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Added successfully!')));
ref.refresh(partiesProvider); // Refresh party list
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Party creation failed: ${parsedData['message']}')),
);
}
}
Future<void> updateParty({
required WidgetRef ref,
required BuildContext context,
required Customer customer,
}) async {
CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
final uri = Uri.parse('${APIConfig.url}/parties/${customer.id}');
var request = http.MultipartRequest('POST', uri)
..headers['Accept'] = 'application/json'
..headers['Authorization'] = await getAuthToken();
void addField(String key, String? value) {
if (value != null && value.isNotEmpty) {
request.fields[key] = value;
}
}
request.fields['_method'] = 'put';
addField('name', customer.name);
addField('phone', customer.phone);
addField('type', customer.customerType);
addField('email', customer.email);
addField('address', customer.address);
addField('opening_balance_type', customer.openingBalanceType);
addField('opening_balance', customer.openingBalance?.toString());
addField('credit_limit', customer.creditLimit?.toString());
// Send billing and shipping address fields directly
addField('billing_address[address]', customer.billingAddress);
addField('billing_address[city]', customer.billingCity);
addField('billing_address[state]', customer.billingState);
addField('billing_address[zip_code]', customer.billingZipcode);
addField('billing_address[country]', customer.billingCountry);
addField('shipping_address[address]', customer.shippingAddress);
addField('shipping_address[city]', customer.shippingCity);
addField('shipping_address[state]', customer.shippingState);
addField('shipping_address[zip_code]', customer.shippingZipcode);
addField('shipping_address[country]', customer.shippingCountry);
if (customer.image != null) {
request.files.add(await http.MultipartFile.fromPath('image', customer.image!.path));
}
final response = await customHttpClient.uploadFile(
url: uri,
fileFieldName: 'image',
file: customer.image,
fields: request.fields,
);
final responseData = await response.stream.bytesToString();
final parsedData = jsonDecode(responseData);
print('--- Sending Party Data ---');
request.fields.forEach((key, value) {
print('$key: $value');
});
if (customer.image != null) {
print('Image path: ${customer.image!.path}');
} else {
print('No image selected');
}
print('---------------------------');
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Added successfully!')));
ref.refresh(partiesProvider); // Refresh party list
Navigator.pop(context);
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Party creation failed: ${parsedData['message']}')),
);
}
}
// Future<void> updateParty({
// required String id,
// required WidgetRef ref,
// required BuildContext context,
// required String name,
// required String phone,
// required String type,
// File? image,
// String? email,
// String? address,
// String? due,
// }) async {
// final uri = Uri.parse('${APIConfig.url}/parties/$id');
// CustomHttpClient customHttpClient = CustomHttpClient(client: http.Client(), context: context, ref: ref);
//
// var request = http.MultipartRequest('POST', uri)
// ..headers['Accept'] = 'application/json'
// ..headers['Authorization'] = await getAuthToken();
//
// request.fields['_method'] = 'put';
// request.fields['name'] = name;
// request.fields['phone'] = phone;
// request.fields['type'] = type;
// if (email != null) request.fields['email'] = email;
// if (address != null) request.fields['address'] = address;
// if (due != null) request.fields['due'] = due; // Convert due to string
// if (image != null) {
// request.files.add(http.MultipartFile.fromBytes('image', image.readAsBytesSync(), filename: image.path));
// }
//
// // final response = await request.send();
// final response = await customHttpClient.uploadFile(url: uri, fields: request.fields, file: image, fileFieldName: 'image');
// final responseData = await response.stream.bytesToString();
//
// final parsedData = jsonDecode(responseData);
//
// if (response.statusCode == 200) {
// ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Updated Successfully!')));
// var data1 = ref.refresh(partiesProvider);
//
// Navigator.pop(context);
// Navigator.pop(context);
// } else {
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Party Update failed: ${parsedData['message']}')));
// }
// }
Future<void> deleteParty({
required String id,
required BuildContext context,
required WidgetRef ref,
}) async {
final String apiUrl = '${APIConfig.url}/parties/$id';
try {
CustomHttpClient customHttpClient = CustomHttpClient(ref: ref, context: context, client: http.Client());
final response = await customHttpClient.delete(
url: Uri.parse(apiUrl),
);
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Party deleted successfully')));
var data1 = ref.refresh(partiesProvider);
Navigator.pop(context); // Assuming you want to close the screen after deletion
// Navigator.pop(context); // Assuming you want to close the screen after deletion
} else {
final parsedData = jsonDecode(response.body);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to delete party: ${parsedData['message']}')));
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: $e')));
}
}
Future<void> sendCustomerUdeSms({required num id, required BuildContext context}) async {
CustomHttpClientGet clientGet = CustomHttpClientGet(client: http.Client());
final uri = Uri.parse('${APIConfig.url}/parties/$id');
final response = await clientGet.get(url: uri);
EasyLoading.dismiss();
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(jsonDecode(response.body)['message'])));
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: ${jsonDecode((response.body))['message']}')));
}
}
}

View File

@@ -0,0 +1,981 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl_phone_field/intl_phone_field.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 '../../GlobalComponents/glonal_popup.dart';
import '../../Provider/profile_provider.dart';
import '../../model/country_model.dart';
import '../../service/check_user_role_permission_provider.dart';
import 'Provider/customer_provider.dart';
import 'Repo/parties_repo.dart';
import 'package:mobile_pos/Screens/Customers/Model/parties_model.dart';
class AddParty extends StatefulWidget {
const AddParty({super.key, this.customerModel});
final Party? customerModel;
@override
// ignore: library_private_types_in_public_api
_AddPartyState createState() => _AddPartyState();
}
class _AddPartyState extends State<AddParty> {
String groupValue = 'Retailer';
String advanced = 'advance';
String due = 'due';
String openingBalanceType = 'due';
bool expanded = false;
final ImagePicker _picker = ImagePicker();
bool showProgress = false;
XFile? pickedImage;
TextEditingController phoneController = TextEditingController();
TextEditingController nameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController addressController = TextEditingController();
final creditLimitController = TextEditingController();
final billingAddressController = TextEditingController();
final billingCityController = TextEditingController();
final billingStateController = TextEditingController();
final shippingAddressController = TextEditingController();
final shippingCityController = TextEditingController();
final shippingStateController = TextEditingController();
final billingZipCodeCountryController = TextEditingController();
final shippingZipCodeCountryController = TextEditingController();
final openingBalanceController = TextEditingController();
final GlobalKey<FormState> _formKay = GlobalKey();
FocusNode focusNode = FocusNode();
List<Country> _countries = [];
Country? _selectedBillingCountry;
Country? _selectedShippingCountry;
@override
void initState() {
super.initState();
_loadCountries();
}
void _initializeFields() {
final party = widget.customerModel;
if (party != null) {
nameController.text = party.name ?? '';
emailController.text = party.email ?? '';
addressController.text = party.address ?? '';
// dueController.text = party.due?.toString() ?? '';
creditLimitController.text = party.creditLimit?.toString() ?? '';
openingBalanceController.text = party.openingBalance?.toString() ?? '';
openingBalanceType = party.openingBalanceType ?? 'due';
groupValue = party.type ?? 'Retailer';
phoneController.text = party.phone ?? '';
// Initialize billing address fields
billingAddressController.text = party.billingAddress?.address ?? '';
billingCityController.text = party.billingAddress?.city ?? '';
billingStateController.text = party.billingAddress?.state ?? '';
billingZipCodeCountryController.text = party.billingAddress?.zipCode ?? '';
if (party.billingAddress?.country != null) {
_selectedBillingCountry = _countries.firstWhere(
(c) => c.name == party.billingAddress!.country,
);
}
shippingAddressController.text = party.shippingAddress?.address ?? '';
shippingCityController.text = party.shippingAddress?.city ?? '';
shippingStateController.text = party.shippingAddress?.state ?? '';
shippingZipCodeCountryController.text = party.shippingAddress?.zipCode ?? '';
if (party.shippingAddress?.country != null) {
_selectedShippingCountry = _countries.firstWhere(
(c) => c.name == party.shippingAddress!.country,
);
}
}
}
Future<void> _loadCountries() async {
try {
final String response = await rootBundle.loadString('assets/countrylist.json');
final List<dynamic> data = json.decode(response);
setState(() {
_countries = data.map((json) => Country.fromJson(json)).toList();
});
// Now that countries are loaded, initialize fields
_initializeFields();
} catch (e) {
print('Error loading countries: $e');
}
}
@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);
bool isReadOnly = (widget.customerModel?.branchId != businessInfo.value?.data?.user?.activeBranchId) &&
widget.customerModel != null;
return GlobalPopup(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
surfaceTintColor: kWhite,
backgroundColor: Colors.white,
title: Text(
lang.S.of(context).addParty,
),
centerTitle: true,
iconTheme: const IconThemeData(color: Colors.black),
elevation: 0.0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(1),
child: Divider(
height: 1,
thickness: 1,
)),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Form(
key: _formKay,
child: Column(
children: [
TextFormField(
controller: phoneController,
keyboardType: TextInputType.phone,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
validator: (value) {
if (value == null || value.isEmpty) {
return lang.S.of(context).pleaseEnterAValidPhoneNumber;
}
return null;
},
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).phone,
hintText: lang.S.of(context).enterYourPhoneNumber,
border: const OutlineInputBorder(),
),
),
SizedBox(height: 20),
///_________Name_______________________
TextFormField(
controller: nameController,
validator: (value) {
if (value == null || value.isEmpty) {
// return 'Please enter a valid Name';
return lang.S.of(context).pleaseEnterAValidName;
}
// You can add more validation logic as needed
return null;
},
keyboardType: TextInputType.name,
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).name,
hintText: lang.S.of(context).enterYourName,
border: const OutlineInputBorder(),
),
),
SizedBox(height: 20),
///_________opening balance_______________________
///
TextFormField(
controller: openingBalanceController,
// 2. Use the variable here
readOnly: isReadOnly,
keyboardType: TextInputType.name,
decoration: InputDecoration(
labelText: lang.S.of(context).balance,
hintText: lang.S.of(context).enterOpeningBalance,
suffixIcon: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
color: Color(0xffF7F7F7),
borderRadius: BorderRadius.only(
topRight: Radius.circular(4),
bottomRight: Radius.circular(4),
)),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
icon: Icon(
Icons.keyboard_arrow_down,
// Optional: Change icon color if disabled
color: isReadOnly ? Colors.grey : kPeraColor,
),
// items: ['Advance', 'Due'].map((entry) {
// final valueToStore = entry.toLowerCase();
// return DropdownMenuItem<String>(
// value: valueToStore,
// child: Text(
// entry,
// style: theme.textTheme.bodyLarge?.copyWith(color: kTitleColor),
// ),
// );
// }).toList(),
items: [
DropdownMenuItem<String>(
value: advanced,
child: Text(
lang.S.of(context).advance,
),
),
DropdownMenuItem<String>(
value: due,
child: Text(
lang.S.of(context).due,
),
),
],
value: openingBalanceType,
// 3. LOGIC APPLIED HERE:
// If isReadOnly is true, set onChanged to null (disables it).
// If false, allow the function to run.
onChanged: isReadOnly
? null
: (String? value) {
setState(() {
openingBalanceType = value!;
});
},
),
),
),
),
),
),
// TextFormField(
// controller: openingBalanceController,
// keyboardType: TextInputType.name,
// decoration: InputDecoration(
// labelText: lang.S.of(context).balance,
// hintText: lang.S.of(context).enterOpeningBalance,
// suffixIcon: Padding(
// padding: const EdgeInsets.all(1.0),
// child: Container(
// padding: EdgeInsets.symmetric(horizontal: 10),
// decoration: BoxDecoration(
// color: Color(0xffF7F7F7),
// borderRadius: BorderRadius.only(
// topRight: Radius.circular(4),
// bottomRight: Radius.circular(4),
// )),
// child: DropdownButtonHideUnderline(
// child: DropdownButton<String>(
// icon: Icon(
// Icons.keyboard_arrow_down,
// color: kPeraColor,
// ),
// items: ['Advance', 'Due'].map((entry) {
// final valueToStore = entry.toLowerCase(); // 'advanced', 'due'
// return DropdownMenuItem<String>(
// value: valueToStore,
// child: Text(
// entry, // show capitalized
// style: theme.textTheme.bodyLarge?.copyWith(color: kTitleColor),
// ),
// );
// }).toList(),
// value: openingBalanceType,
// onChanged: (String? value) {
// setState(() {
// openingBalanceType = value!;
// });
// },
// ),
// ),
// ),
// ),
// ),
// ),
SizedBox(height: 20),
///_______Type___________________________
Row(
children: [
Expanded(
child: RadioListTile(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return kMainColor;
}
return kPeraColor;
},
),
contentPadding: EdgeInsets.zero,
groupValue: groupValue,
title: Text(
lang.S.of(context).customer,
maxLines: 1,
style: theme.textTheme.bodyMedium,
),
value: 'Retailer',
onChanged: (value) {
setState(() {
groupValue = value.toString();
});
},
),
),
Expanded(
child: RadioListTile(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return kMainColor;
}
return kPeraColor;
},
),
contentPadding: EdgeInsets.zero,
groupValue: groupValue,
title: Text(
lang.S.of(context).dealer,
maxLines: 1,
style: theme.textTheme.bodyMedium,
),
value: 'Dealer',
onChanged: (value) {
setState(() {
groupValue = value.toString();
});
},
),
),
],
),
Row(
children: [
Expanded(
child: RadioListTile(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return kMainColor;
}
return kPeraColor;
},
),
contentPadding: EdgeInsets.zero,
activeColor: kMainColor,
groupValue: groupValue,
title: Text(
lang.S.of(context).wholesaler,
maxLines: 1,
style: theme.textTheme.bodyMedium,
),
value: 'Wholesaler',
onChanged: (value) {
setState(() {
groupValue = value.toString();
});
},
),
),
Expanded(
child: RadioListTile(
contentPadding: EdgeInsets.zero,
activeColor: kMainColor,
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
fillColor: WidgetStateProperty.resolveWith(
(states) {
if (states.contains(WidgetState.selected)) {
return kMainColor;
}
return kPeraColor;
},
),
groupValue: groupValue,
title: Text(
lang.S.of(context).supplier,
maxLines: 1,
style: theme.textTheme.bodyMedium,
),
value: 'Supplier',
onChanged: (value) {
setState(() {
groupValue = value.toString();
});
},
),
),
],
),
Visibility(
visible: showProgress,
child: const CircularProgressIndicator(
color: kMainColor,
strokeWidth: 5.0,
),
),
ExpansionPanelList(
expandIconColor: Colors.transparent,
expandedHeaderPadding: EdgeInsets.zero,
expansionCallback: (int index, bool isExpanded) {
setState(() {
expanded == false ? expanded = true : expanded = false;
});
},
animationDuration: const Duration(milliseconds: 500),
elevation: 0,
dividerColor: Colors.white,
children: [
ExpansionPanel(
backgroundColor: kWhite,
headerBuilder: (BuildContext context, bool isExpanded) {
return TextButton.icon(
style: ButtonStyle(
alignment: Alignment.center,
backgroundColor: WidgetStateColor.transparent,
overlayColor: WidgetStateColor.transparent,
surfaceTintColor: WidgetStateColor.transparent,
padding: WidgetStatePropertyAll(
EdgeInsets.only(left: 70),
),
),
onPressed: () {
setState(() {
expanded == false ? expanded = true : expanded = false;
});
},
label: Text(
lang.S.of(context).moreInfo,
style: theme.textTheme.titleSmall?.copyWith(color: Colors.red),
),
icon: Icon(Icons.keyboard_arrow_down_outlined),
iconAlignment: IconAlignment.end,
);
},
body: Column(
children: [
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: kWhite,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
// ignore: sized_box_for_whitespace
child: Container(
height: 200.0,
width: MediaQuery.of(context).size.width - 80,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () async {
pickedImage = await _picker.pickImage(source: ImageSource.gallery);
setState(() {});
Future.delayed(const Duration(milliseconds: 100), () {
Navigator.pop(context);
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.photo_library_rounded,
size: 60.0,
color: kMainColor,
),
Text(
lang.S.of(context).gallery,
//'Gallery',
style: theme.textTheme.titleMedium?.copyWith(
color: kMainColor,
),
),
],
),
),
const SizedBox(
width: 40.0,
),
GestureDetector(
onTap: () async {
pickedImage = await _picker.pickImage(source: ImageSource.camera);
setState(() {});
Future.delayed(const Duration(milliseconds: 100), () {
Navigator.pop(context);
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.camera,
size: 60.0,
color: kGreyTextColor,
),
Text(
lang.S.of(context).camera,
//'Camera',
style: theme.textTheme.titleMedium?.copyWith(
color: kGreyTextColor,
),
),
],
),
),
],
),
),
),
);
});
},
child: Stack(
children: [
Container(
height: 120,
width: 120,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: pickedImage == null
? const DecorationImage(
image: AssetImage('images/no_shop_image.png'),
fit: BoxFit.cover,
)
: DecorationImage(
image: FileImage(File(pickedImage!.path)),
fit: BoxFit.cover,
),
),
),
Positioned(
bottom: 0,
right: 0,
child: Container(
height: 35,
width: 35,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2),
borderRadius: const BorderRadius.all(Radius.circular(120)),
color: kMainColor,
),
child: const Icon(
Icons.camera_alt_outlined,
size: 20,
color: Colors.white,
),
),
)
],
),
),
const SizedBox(height: 20),
///__________email__________________________
TextFormField(
controller: emailController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).email,
//hintText: 'Enter your email address',
hintText: lang.S.of(context).hintEmail),
),
SizedBox(height: 20),
TextFormField(
controller: addressController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).address,
//hintText: 'Enter your address'
hintText: lang.S.of(context).hintEmail),
),
// SizedBox(height: 20),
// TextFormField(
// controller: dueController,
// inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}'))],
// keyboardType: TextInputType.number,
// decoration: InputDecoration(
// border: const OutlineInputBorder(),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// labelText: lang.S.of(context).previousDue,
// hintText: lang.S.of(context).amount,
// ),
// ),
SizedBox(height: 20),
TextFormField(
controller: creditLimitController,
decoration: InputDecoration(
border: const OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: lang.S.of(context).creditLimit,
hintText: 'Ex: 800'),
),
SizedBox(height: 4),
Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: ExpansionTile(
collapsedIconColor: kGreyTextColor,
visualDensity: VisualDensity(vertical: -2, horizontal: -4),
tilePadding: EdgeInsets.zero,
trailing: SizedBox.shrink(),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
FeatherIcons.plus,
size: 20,
),
SizedBox(width: 8),
Text(
lang.S.of(context).billingAddress,
style: theme.textTheme.titleMedium,
)
],
),
children: [
SizedBox(height: 10),
//___________Billing Address________________
TextFormField(
controller: billingAddressController,
decoration: InputDecoration(
labelText: lang.S.of(context).address,
hintText: lang.S.of(context).enterAddress,
),
),
SizedBox(height: 20),
//--------------billing city------------------------
Row(
children: [
Expanded(
child: TextFormField(
controller: billingCityController,
decoration: InputDecoration(
labelText: lang.S.of(context).city,
hintText: lang.S.of(context).cityName,
),
),
),
SizedBox(width: 16),
Expanded(
child: TextFormField(
controller: billingStateController,
decoration: InputDecoration(
labelText: lang.S.of(context).state,
hintText: lang.S.of(context).stateName,
),
),
),
],
),
//--------------billing state------------------------
SizedBox(height: 20),
Row(
children: [
//--------------billing zip code------------------------
Expanded(
child: TextFormField(
controller: billingZipCodeCountryController,
decoration: InputDecoration(
labelText: lang.S.of(context).zip,
hintText: lang.S.of(context).zipCode,
),
),
),
SizedBox(width: 20),
//--------------billing country------------------------
Flexible(
child: DropdownButtonFormField<Country>(
value: _selectedBillingCountry,
hint: Text(lang.S.of(context).chooseCountry),
onChanged: (Country? newValue) {
setState(() {
_selectedBillingCountry = newValue;
});
if (newValue != null) {
print('Selected: ${newValue.name} (${newValue.code})');
}
},
items: _countries.map<DropdownMenuItem<Country>>((Country country) {
return DropdownMenuItem<Country>(
value: country,
child: Row(
children: [
Text(country.emoji),
const SizedBox(width: 8),
Flexible(
child: Text(
country.name,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}).toList(),
isExpanded: true,
dropdownColor: Colors.white,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).country,
),
),
),
],
),
],
),
),
Theme(
data: Theme.of(context).copyWith(
dividerColor: Colors.transparent,
),
child: ExpansionTile(
collapsedIconColor: kGreyTextColor,
tilePadding: EdgeInsets.zero,
visualDensity: VisualDensity(horizontal: -4, vertical: -2),
trailing: SizedBox.shrink(),
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(FeatherIcons.plus, size: 20),
SizedBox(width: 8),
Text(
lang.S.of(context).shippingAddress,
style: theme.textTheme.titleMedium,
)
],
),
children: [
SizedBox(height: 10),
//___________Billing Address________________
TextFormField(
controller: shippingAddressController,
decoration: InputDecoration(
labelText: lang.S.of(context).address,
hintText: lang.S.of(context).enterAddress,
),
),
SizedBox(height: 20),
//--------------billing city------------------------
Row(
children: [
Expanded(
child: TextFormField(
controller: shippingCityController,
decoration: InputDecoration(
labelText: lang.S.of(context).city,
hintText: lang.S.of(context).cityName,
),
),
),
SizedBox(width: 16),
Expanded(
child: TextFormField(
controller: shippingStateController,
decoration: InputDecoration(
labelText: lang.S.of(context).state,
hintText: lang.S.of(context).stateName,
),
),
),
],
),
//--------------billing state------------------------
SizedBox(height: 20),
Row(
children: [
//--------------billing zip code------------------------
Expanded(
child: TextFormField(
controller: shippingZipCodeCountryController,
decoration: InputDecoration(
labelText: lang.S.of(context).zip,
hintText: lang.S.of(context).zipCode,
),
),
),
SizedBox(width: 20),
//--------------billing country------------------------
Flexible(
child: DropdownButtonFormField<Country>(
value: _selectedShippingCountry,
hint: Text(lang.S.of(context).chooseCountry),
onChanged: (Country? newValue) {
setState(() {
_selectedShippingCountry = newValue;
});
if (newValue != null) {
print('Selected: ${newValue.name} (${newValue.code})');
}
},
items: _countries.map<DropdownMenuItem<Country>>((Country country) {
return DropdownMenuItem<Country>(
value: country,
child: Row(
children: [
Text(country.emoji),
const SizedBox(width: 8),
Flexible(
child: Text(
country.name,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}).toList(),
isExpanded: true,
dropdownColor: Colors.white,
decoration: kInputDecoration.copyWith(
labelText: lang.S.of(context).country,
),
),
),
],
),
],
),
)
],
),
isExpanded: expanded,
),
],
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
if (!permissionService.hasPermission(Permit.partiesCreate.value)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: Colors.red,
content: Text(lang.S.of(context).partyCreateWarn),
),
);
return;
}
num parseOrZero(String? input) {
if (input == null || input.isEmpty) return 0;
return num.tryParse(input) ?? 0;
}
Customer customer = Customer(
id: widget.customerModel?.id.toString() ?? '',
name: nameController.text,
phone: phoneController.text ?? '',
customerType: groupValue,
image: pickedImage != null ? File(pickedImage!.path) : null,
email: emailController.text,
address: addressController.text,
openingBalanceType: openingBalanceType.toString(),
openingBalance: parseOrZero(openingBalanceController.text),
creditLimit: parseOrZero(creditLimitController.text),
billingAddress: billingAddressController.text,
billingCity: billingCityController.text,
billingState: billingStateController.text,
billingZipcode: billingZipCodeCountryController.text,
billingCountry: _selectedBillingCountry?.name.toString() ?? '',
shippingAddress: shippingAddressController.text,
shippingCity: shippingCityController.text,
shippingState: shippingStateController.text,
shippingZipcode: shippingZipCodeCountryController.text,
shippingCountry: _selectedShippingCountry?.name.toString() ?? '',
);
final partyRepo = PartyRepository();
if (widget.customerModel == null) {
// Add new
await partyRepo.addParty(
ref: ref,
context: context,
customer: customer,
);
} else {
await partyRepo.updateParty(
ref: ref,
context: context,
customer: customer,
);
}
},
child: Text(lang.S.of(context).save),
)
],
),
),
),
),
);
});
}
}
class Customer {
String? id;
String name;
String? phone;
String? customerType;
File? image;
String? email;
String? address;
String? openingBalanceType;
num? openingBalance;
num? creditLimit;
String? billingAddress;
String? billingCity;
String? billingState;
String? billingZipcode;
String? billingCountry;
String? shippingAddress;
String? shippingCity;
String? shippingState;
String? shippingZipcode;
String? shippingCountry;
Customer({
this.id,
required this.name,
this.phone,
this.customerType,
this.image,
this.email,
this.address,
this.openingBalanceType,
this.openingBalance,
this.creditLimit,
this.billingAddress,
this.billingCity,
this.billingState,
this.billingZipcode,
this.billingCountry,
this.shippingAddress,
this.shippingCity,
this.shippingState,
this.shippingZipcode,
this.shippingCountry,
});
}

View File

@@ -0,0 +1,721 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/Const/api_config.dart';
import 'package:mobile_pos/GlobalComponents/url_lanuncer.dart';
import 'package:mobile_pos/Provider/transactions_provider.dart';
import 'package:mobile_pos/Screens/Customers/edit_customer.dart';
import 'package:mobile_pos/Screens/Customers/sms_sent_confirmation.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 'package:url_launcher/url_launcher.dart';
import '../../GlobalComponents/glonal_popup.dart';
import '../../GlobalComponents/sales_transaction_widget.dart';
import '../../PDF Invoice/purchase_invoice_pdf.dart';
import '../../Provider/profile_provider.dart';
import '../../currency.dart';
import '../../http_client/custome_http_client.dart';
import '../../service/check_actions_when_no_branch.dart';
import '../../thermal priting invoices/model/print_transaction_model.dart';
import '../../thermal priting invoices/provider/print_thermal_invoice_provider.dart';
import '../../service/check_user_role_permission_provider.dart';
import '../invoice_details/purchase_invoice_details.dart';
import 'Model/parties_model.dart';
import 'Repo/parties_repo.dart';
import 'add_customer.dart';
// ignore: must_be_immutable
class CustomerDetails extends ConsumerStatefulWidget {
CustomerDetails({super.key, required this.party});
Party party;
@override
ConsumerState<CustomerDetails> createState() => _CustomerDetailsState();
}
class _CustomerDetailsState extends ConsumerState<CustomerDetails> {
@override
void initState() {
super.initState();
}
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)),
),
],
);
},
);
}
int selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Consumer(builder: (context, cRef, __) {
final providerData = cRef.watch(salesTransactionProvider);
final purchaseList = cRef.watch(purchaseTransactionProvider);
final printerData = cRef.watch(thermalPrinterProvider);
final businessInfo = cRef.watch(businessInfoProvider);
final permissionService = PermissionService(cRef);
final _theme = Theme.of(context);
return GlobalPopup(
child: Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
surfaceTintColor: kWhite,
backgroundColor: Colors.white,
title: Text(
widget.party.type != 'Supplier' ? lang.S.of(context).CustomerDetails : lang.S.of(context).supplierDetails,
),
actions: [
businessInfo.when(data: (details) {
return Row(
children: [
IconButton(
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero,
onPressed: () 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: widget.party).launch(context);
}
},
icon: const Icon(
FeatherIcons.edit2,
color: Colors.grey,
size: 20,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
padding: EdgeInsets.zero,
onPressed: () 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: widget.party.id.toString(), ref: cRef);
}
},
icon: const Icon(
FeatherIcons.trash2,
color: Colors.grey,
size: 20,
),
),
),
],
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
})
],
centerTitle: true,
iconTheme: const IconThemeData(color: Colors.black),
elevation: 0.0,
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (permissionService.hasPermission(Permit.partiesRead.value)) ...{
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 30),
widget.party.image == null
? Center(
child: Container(
height: 100,
width: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _theme.colorScheme.primary,
),
child: Center(
child: Text(
(widget.party.name != null && widget.party.name!.length >= 2)
? widget.party.name!.substring(0, 2)
: (widget.party.name != null ? widget.party.name! : ''),
style: _theme.textTheme.bodyMedium?.copyWith(
color: Colors.white,
fontSize: 21,
fontWeight: FontWeight.w700,
),
),
),
),
)
: Center(
child: Container(
height: 100,
width: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: widget.party.image == null
? const DecorationImage(
image: AssetImage('images/no_shop_image.png'),
fit: BoxFit.cover,
)
: DecorationImage(
image: NetworkImage('${APIConfig.domain}${widget.party.image!}'),
fit: BoxFit.cover,
),
),
),
),
SizedBox(height: 16),
Text(
// 'Personal Info:',
lang.S.of(context).personalInfo,
style: _theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
SizedBox(height: 4),
...{
lang.S.of(context).name: widget.party.name,
lang.S.of(context).type: widget.party.type,
lang.S.of(context).phoneNumber: widget.party.phone,
lang.S.of(context).email: widget.party.email ?? "n/a",
lang.S.of(context).dueBalance: "$currency${(widget.party.due ?? "0")}",
lang.S.of(context).walletBalance: "$currency${(widget.party.wallet ?? "0")}",
lang.S.of(context).address: widget.party.address ?? "n/a",
// "Party Credit Limit": widget.party.creditLimit ?? "0",
// "Party GST": widget.party.creditLimit ?? "0",
}.entries.map((entry) {
return keyValueWidget(title: entry.key, value: entry.value.toString(), context: context);
}),
SizedBox(height: 19),
Text(
// 'Billing Address:',
lang.S.of(context).billingAddress,
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
() {
final parts = [
widget.party.billingAddress?.address,
widget.party.billingAddress?.city,
widget.party.billingAddress?.state,
widget.party.billingAddress?.zipCode,
widget.party.billingAddress?.country,
].where((part) => part != null && part.isNotEmpty).toList();
return parts.isEmpty ? 'n/a' : parts.join(', ');
}(),
style: _theme.textTheme.bodyMedium?.copyWith(
color: kPeraColor,
),
),
SizedBox(height: 12),
Text(
// 'Shipping Address:',
lang.S.of(context).shippingAddress,
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8),
Text(
() {
final parts = [
widget.party.shippingAddress?.address,
widget.party.shippingAddress?.city,
widget.party.shippingAddress?.state,
widget.party.shippingAddress?.zipCode,
widget.party.shippingAddress?.country,
].where((part) => part != null && part.isNotEmpty).toList();
return parts.isEmpty ? 'n/a' : parts.join(', ');
}(),
style: _theme.textTheme.bodyMedium?.copyWith(
color: kPeraColor,
),
),
SizedBox(height: 12),
Divider(
height: 1,
thickness: 1,
color: DAppColors.kDividerColor,
),
SizedBox(height: 12),
Text(
lang.S.of(context).recentTransaction,
style: _theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
),
// const SizedBox(height: 8),
widget.party.type != 'Supplier'
? providerData.when(data: (transaction) {
final filteredTransactions =
transaction.where((t) => t.party?.id == widget.party.id).toList();
return filteredTransactions.isNotEmpty
? ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filteredTransactions.length,
itemBuilder: (context, index) {
final currentTransaction = filteredTransactions[index];
return salesTransactionWidget(
context: context,
ref: cRef,
businessInfo: businessInfo.value!,
sale: currentTransaction,
advancePermission: true,
showProductQTY: true,
);
},
)
: EmptyWidget(
message: TextSpan(text: lang.S.of(context).noTransaction),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
})
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: purchaseList.when(data: (pTransaction) {
final filteredTransactions =
pTransaction.where((t) => t.party?.id == widget.party.id).toList();
return filteredTransactions.isNotEmpty
? ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filteredTransactions.length,
itemBuilder: (context, index) {
final currentTransaction = filteredTransactions[index];
return GestureDetector(
onTap: () {
PurchaseInvoiceDetails(
transitionModel: currentTransaction,
businessInfo: businessInfo.value!,
).launch(context);
},
child: Column(
children: [
SizedBox(
width: context.width(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${lang.S.of(context).totalProduct} : ${currentTransaction.details!.length.toString()}",
style: const TextStyle(fontSize: 16),
),
Text('#${currentTransaction.invoiceNumber}'),
],
),
const SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: currentTransaction.dueAmount! <= 0
? const Color(0xff0dbf7d).withValues(alpha: 0.1)
: const Color(0xFFED1A3B).withValues(alpha: 0.1),
borderRadius: const BorderRadius.all(Radius.circular(2)),
),
child: Text(
currentTransaction.dueAmount! <= 0
? lang.S.of(context).paid
: lang.S.of(context).unPaid,
style: TextStyle(
color: currentTransaction.dueAmount! <= 0
? const Color(0xff0dbf7d)
: const Color(0xFFED1A3B),
),
),
),
Text(currentTransaction.purchaseDate!.substring(0, 10),
style: _theme.textTheme.bodyMedium
?.copyWith(color: DAppColors.kSecondary)),
],
),
const SizedBox(height: 10),
Text(
'${lang.S.of(context).total} : $currency${currentTransaction.totalAmount.toString()}',
style: _theme.textTheme.bodyMedium
?.copyWith(color: DAppColors.kSecondary),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${lang.S.of(context).due}: $currency${currentTransaction.dueAmount.toString()}',
style: const TextStyle(fontSize: 16),
),
businessInfo.when(data: (data) {
return Row(
children: [
IconButton(
onPressed: () async {
PrintPurchaseTransactionModel model =
PrintPurchaseTransactionModel(
purchaseTransitionModel: currentTransaction,
personalInformationModel: data,
);
await printerData.printPurchaseThermalInvoiceNow(
transaction: model,
productList: model.purchaseTransitionModel!.details,
invoiceSize: businessInfo.value?.data?.invoiceSize,
context: context,
);
},
icon: const Icon(
FeatherIcons.printer,
color: Colors.grey,
),
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
),
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
),
),
const SizedBox(width: 8),
businessInfo.when(data: (business) {
return Row(
children: [
IconButton(
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
)),
onPressed: () =>
PurchaseInvoicePDF.generatePurchaseDocument(
currentTransaction,
data,
context,
showPreview: true,
),
icon: const Icon(
Icons.picture_as_pdf,
color: Colors.grey,
),
),
IconButton(
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
visualDensity: const VisualDensity(
horizontal: -4,
vertical: -4,
)),
onPressed: () =>
PurchaseInvoicePDF.generatePurchaseDocument(
currentTransaction, data, context,
isShare: true),
icon: const Icon(
Icons.share_outlined,
color: Colors.grey,
),
),
],
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}),
],
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return Text(lang.S.of(context).loading);
}),
],
),
],
),
),
const Divider(
height: 15,
color: kBorderColor,
),
const SizedBox(height: 10),
],
),
);
},
)
: EmptyWidget(
message: TextSpan(text: lang.S.of(context).noTransaction),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
}),
),
} else
Center(child: PermitDenyWidget()),
],
),
),
// bottomNavigationBar: ButtonGlobal(
// iconWidget: null,
// buttontext: lang.S.of(context).viewAll,
// iconColor: Colors.white,
// buttonDecoration: kButtonDecoration.copyWith(color: kMainColor),
// onPressed: () {
// Navigator.push(context, MaterialPageRoute(builder: (context)=>const CustomerAllTransactionScreen()));
// },
// ),
),
);
});
}
}
class ContactOptionsRow extends StatefulWidget {
final Party party;
const ContactOptionsRow({super.key, required this.party});
@override
State<ContactOptionsRow> createState() => _ContactOptionsRowState();
}
class _ContactOptionsRowState extends State<ContactOptionsRow> {
int selectedIndex = -1;
void _onButtonTap(int index) async {
setState(() {
selectedIndex = index;
});
if (index == 0) {
// Call functionality
if (widget.party.phone == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(lang.S.of(context).phoneNotAvail)),
);
return;
}
final Uri url = Uri.parse('tel:${widget.party.phone}');
bool t = await launchUrl(url);
if (!t) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(lang.S.of(context).notLaunch)),
);
}
} else if (index == 1) {
// SMS functionality
if (widget.party.type != 'Supplier') {
showDialog(
context: context,
builder: (context1) {
return SmsConfirmationPopup(
customerName: widget.party.name ?? '',
phoneNumber: widget.party.phone ?? '',
onCancel: () {
Navigator.pop(context1);
},
onSendSms: () {
UrlLauncher.handleLaunchURL(context, 'sms:${widget.party.phone}', false);
// EasyLoading.show(status: 'SMS Sending..');
// PartyRepository repo = PartyRepository();
// await repo.sendCustomerUdeSms(id: widget.party.id!, context: context);
},
);
},
);
} else {
if (widget.party.phone == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(lang.S.of(context).phoneNotAvail)),
);
return;
}
UrlLauncher.handleLaunchURL(
context,
'sms:${widget.party.phone}',
false,
);
}
} else if (index == 2) {
// Email functionality
if (widget.party.email == null || !RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(widget.party.email!)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid email address.')),
);
return;
}
UrlLauncher.handleLaunchURL(context, 'mailto:${widget.party.email}', true);
}
}
Widget _buildContactButton(int index, IconData icon, String label) {
final _theme = Theme.of(context);
return Expanded(
child: GestureDetector(
onTap: () => _onButtonTap(index),
child: Container(
padding: const EdgeInsets.all(8),
height: 90,
decoration: BoxDecoration(
color: selectedIndex == index ? kMainColor : kMainColor.withValues(alpha: 0.10),
borderRadius: const BorderRadius.all(Radius.circular(10)),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
size: 20,
color: selectedIndex == index ? kWhite : Colors.black,
),
const SizedBox(height: 8),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
label,
maxLines: 1,
style: _theme.textTheme.bodyMedium?.copyWith(
fontSize: 14,
color: selectedIndex == index ? kWhite : Colors.black,
),
),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildContactButton(0, FeatherIcons.phone, 'Call'),
const SizedBox(width: 18),
_buildContactButton(1, FeatherIcons.messageSquare, 'Message'),
const SizedBox(width: 18),
_buildContactButton(2, FeatherIcons.mail, 'Email'),
],
);
}
}
Widget keyValueWidget({required String title, required String value, required BuildContext context}) {
final _theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
fit: FlexFit.tight,
flex: 3,
child: Text(
'$title ',
style: _theme.textTheme.bodyMedium?.copyWith(
color: DAppColors.kNeutral700,
),
),
),
SizedBox(width: 8),
Flexible(
fit: FlexFit.tight,
flex: 4,
child: Text(
': $value',
style: _theme.textTheme.bodyMedium?.copyWith(
color: kTitleColor,
// fontSize: 15,
),
),
),
],
),
);
}

View File

@@ -0,0 +1,775 @@
// // ignore: import_of_legacy_library_into_null_safe
// // ignore_for_file: unused_result
// import 'dart:io';
//
// import 'package:flutter/material.dart';
// import 'package:flutter_easyloading/flutter_easyloading.dart';
// import 'package:flutter_feather_icons/flutter_feather_icons.dart';
// import 'package:flutter_riverpod/flutter_riverpod.dart';
// import 'package:image_picker/image_picker.dart';
// import 'package:mobile_pos/Const/api_config.dart';
// import 'package:mobile_pos/Screens/Customers/Model/parties_model.dart';
// import 'package:mobile_pos/constant.dart';
// import 'package:mobile_pos/generated/l10n.dart' as lang;
// import 'package:nb_utils/nb_utils.dart';
//
// import '../../GlobalComponents/glonal_popup.dart';
// import '../../http_client/custome_http_client.dart';
// import '../User Roles/Provider/check_user_role_permission_provider.dart';
// import 'Provider/customer_provider.dart';
// import 'Repo/parties_repo.dart';
//
// // ignore: must_be_immutable
// class EditCustomer extends StatefulWidget {
// EditCustomer({super.key, required this.customerModel});
//
// Party customerModel;
//
// @override
// // ignore: library_private_types_in_public_api
// _EditCustomerState createState() => _EditCustomerState();
// }
//
// class _EditCustomerState extends State<EditCustomer> {
// String groupValue = '';
// bool expanded = false;
// final ImagePicker _picker = ImagePicker();
// bool showProgress = false;
// XFile? pickedImage;
//
// @override
// void initState() {
// phoneController.text = widget.customerModel.phone ?? '';
// nameController.text = widget.customerModel.name ?? '';
// emailController.text = widget.customerModel.email ?? '';
// dueController.text = (widget.customerModel.due ?? 0).toString();
// addressController.text = widget.customerModel.address ?? '';
// groupValue = widget.customerModel.type ?? '';
// super.initState();
// }
//
// final GlobalKey<FormState> _formKay = GlobalKey();
//
// TextEditingController phoneController = TextEditingController();
// TextEditingController nameController = TextEditingController();
// TextEditingController emailController = TextEditingController();
// TextEditingController dueController = TextEditingController();
// TextEditingController addressController = TextEditingController();
//
// final partyCreditLimitController = TextEditingController();
// final partyGstController = TextEditingController();
// final billingAddressController = TextEditingController();
// final billingCityController = TextEditingController();
// final billingStateController = TextEditingController();
// final billingCountryController = TextEditingController();
// final shippingAddressController = TextEditingController();
// final shippingCityController = TextEditingController();
// final shippingStateController = TextEditingController();
// final shippingCountryController = TextEditingController();
// final billingZipCodeCountryController = TextEditingController();
// final shippingZipCodeCountryController = TextEditingController();
// final openingBalanceController = TextEditingController();
//
// FocusNode focusNode = FocusNode();
// String? selectedBillingCountry;
// String? selectedDShippingCountry;
// String? selectedBalanceType;
//
// @override
// Widget build(BuildContext context) {
// final theme = Theme.of(context);
//
// return Consumer(builder: (context, cRef, __) {
// final permissionService = PermissionService(cRef);
// return GlobalPopup(
// child: Scaffold(
// backgroundColor: Colors.white,
// appBar: AppBar(
// backgroundColor: Colors.white,
// title: Text(
// lang.S.of(context).updateContact,
// ),
// centerTitle: true,
// iconTheme: const IconThemeData(color: Colors.black),
// elevation: 0.0,
// ),
// body: Consumer(builder: (context, ref, __) {
// // ignore: unused_local_variable
// final customerData = ref.watch(partiesProvider);
//
// return SingleChildScrollView(
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// children: [
// Form(
// key: _formKay,
// child: Column(
// children: [
// ///_________Phone_______________________
// TextFormField(
// controller: phoneController,
// validator: (value) {
// if (value == null || value.isEmpty) {
// // return 'Please enter a valid phone number';
// return lang.S.of(context).pleaseEnterAValidPhoneNumber;
// }
// return null;
// },
// decoration: InputDecoration(
// floatingLabelBehavior: FloatingLabelBehavior.always,
// labelText: lang.S.of(context).phone,
// hintText: lang.S.of(context).enterYourPhoneNumber,
// border: const OutlineInputBorder(),
// ),
// ),
// SizedBox(height: 20),
//
// ///_________Name_______________________
// TextFormField(
// controller: nameController,
// validator: (value) {
// if (value == null || value.isEmpty) {
// // return 'Please enter a valid Name';
// return lang.S.of(context).pleaseEnterAValidName;
// }
// // You can add more validation logic as needed
// return null;
// },
// decoration: InputDecoration(
// floatingLabelBehavior: FloatingLabelBehavior.always,
// labelText: lang.S.of(context).name,
// hintText: lang.S.of(context).enterYourName,
// border: const OutlineInputBorder(),
// ),
// ),
// ],
// ),
// ),
// SizedBox(height: 20),
//
// ///_________opening balance_______________________
// // TextFormField(
// // controller: openingBalanceController,
// // keyboardType: TextInputType.name,
// // decoration: InputDecoration(
// // labelText: lang.S.of(context).openingBalance,
// // hintText: lang.S.of(context).enterOpeningBalance,
// // suffixIcon: Padding(
// // padding: const EdgeInsets.all(1.0),
// // child: Container(
// // padding: EdgeInsets.symmetric(horizontal: 10),
// // decoration: BoxDecoration(
// // color: kBackgroundColor,
// // borderRadius: BorderRadius.only(
// // topRight: Radius.circular(4),
// // bottomRight: Radius.circular(4),
// // )),
// // child: DropdownButtonHideUnderline(
// // child: DropdownButton(
// // icon: Icon(
// // Icons.keyboard_arrow_down,
// // color: kPeraColor,
// // ),
// // items: ['Advanced', 'Due'].map((entry) {
// // return DropdownMenuItem(value: entry, child: Text(entry, style: theme.textTheme.bodyLarge?.copyWith(color: kTitleColor)));
// // }).toList(),
// // value: selectedBalanceType ?? 'Advanced',
// // onChanged: (String? value) {
// // setState(() {
// // selectedBalanceType = value;
// // });
// // }),
// // ),
// // ),
// // )),
// // ),
// // SizedBox(height: 20),
// Row(
// children: [
// Expanded(
// child: RadioListTile(
// visualDensity: VisualDensity(horizontal: -4, vertical: -4),
// fillColor: WidgetStateProperty.resolveWith(
// (states) {
// if (states.contains(WidgetState.selected)) {
// return kMainColor;
// }
// return kPeraColor;
// },
// ),
// contentPadding: EdgeInsets.zero,
// groupValue: groupValue,
// title: Text(
// lang.S.of(context).retailer,
// maxLines: 1,
// style: theme.textTheme.bodySmall,
// ),
// value: 'Retailer',
// onChanged: (value) {
// if (widget.customerModel.type != 'Supplier') {
// setState(() {
// groupValue = value.toString();
// });
// }
// },
// // Change the color to indicate it's not selectable
// activeColor: widget.customerModel.type == 'Supplier' ? Colors.grey : kMainColor,
// ),
// ),
// Expanded(
// child: RadioListTile(
// visualDensity: VisualDensity(horizontal: -4, vertical: -4),
// fillColor: WidgetStateProperty.resolveWith(
// (states) {
// if (states.contains(WidgetState.selected)) {
// return kMainColor;
// }
// return kPeraColor;
// },
// ),
// contentPadding: EdgeInsets.zero,
// groupValue: groupValue,
// title: Text(
// lang.S.of(context).dealer,
// maxLines: 1,
// style: theme.textTheme.bodySmall,
// ),
// value: 'Dealer',
// onChanged: (value) {
// if (widget.customerModel.type != 'Supplier') {
// setState(() {
// groupValue = value.toString();
// });
// }
// },
// activeColor: widget.customerModel.type == 'Supplier' ? Colors.grey : kMainColor,
// ),
// ),
// ],
// ),
// Row(
// children: [
// Expanded(
// child: RadioListTile(
// visualDensity: VisualDensity(horizontal: -4, vertical: -4),
// fillColor: WidgetStateProperty.resolveWith(
// (states) {
// if (states.contains(WidgetState.selected)) {
// return kMainColor;
// }
// return kPeraColor;
// },
// ),
// contentPadding: EdgeInsets.zero,
// activeColor: kMainColor,
// groupValue: groupValue,
// title: Text(
// lang.S.of(context).wholesaler,
// maxLines: 1,
// style: theme.textTheme.bodySmall,
// ),
// value: 'Wholesaler',
// onChanged: (value) {
// if (widget.customerModel.type != 'Supplier') {
// setState(() {
// groupValue = value.toString();
// });
// }
// },
// ),
// ),
// Expanded(
// child: RadioListTile(
// visualDensity: VisualDensity(horizontal: -4, vertical: -4),
// fillColor: WidgetStateProperty.resolveWith(
// (states) {
// if (states.contains(WidgetState.selected)) {
// return kMainColor;
// }
// return kPeraColor;
// },
// ),
// contentPadding: EdgeInsets.zero,
// activeColor: kMainColor,
// groupValue: groupValue,
// title: Text(
// lang.S.of(context).supplier,
// maxLines: 1,
// style: theme.textTheme.bodySmall,
// ),
// value: 'Supplier',
// onChanged: (value) {
// if (widget.customerModel.type != 'Retailer' && widget.customerModel.type != 'Dealer' && widget.customerModel.type != 'Wholesaler') {
// setState(() {
// groupValue = value.toString();
// });
// }
// },
// ),
// ),
// ],
// ),
// Visibility(
// visible: showProgress,
// child: const CircularProgressIndicator(
// color: kMainColor,
// strokeWidth: 5.0,
// ),
// ),
// ExpansionPanelList(
// expandIconColor: Colors.red,
// expansionCallback: (int index, bool isExpanded) {},
// animationDuration: const Duration(seconds: 1),
// elevation: 0,
// dividerColor: Colors.white,
// children: [
// ExpansionPanel(
// backgroundColor: kWhite,
// headerBuilder: (BuildContext context, bool isExpanded) {
// return Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// TextButton(
// child: Text(
// lang.S.of(context).moreInfo,
// style: theme.textTheme.titleLarge?.copyWith(
// color: kMainColor,
// ),
// ),
// onPressed: () {
// setState(() {
// expanded == false ? expanded = true : expanded = false;
// });
// },
// ),
// ],
// );
// },
// body: Column(
// children: [
// GestureDetector(
// onTap: () {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return Dialog(
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12.0),
// ),
// // ignore: sized_box_for_whitespace
// child: Container(
// height: 200.0,
// width: MediaQuery.of(context).size.width - 80,
// child: Center(
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// GestureDetector(
// onTap: () async {
// pickedImage = await _picker.pickImage(source: ImageSource.gallery);
// setState(() {});
// Navigator.pop(context);
// },
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(
// Icons.photo_library_rounded,
// size: 60.0,
// color: kMainColor,
// ),
// Text(lang.S.of(context).gallery, style: theme.textTheme.titleLarge?.copyWith(color: kMainColor)),
// ],
// ),
// ),
// const SizedBox(
// width: 40.0,
// ),
// GestureDetector(
// onTap: () async {
// pickedImage = await _picker.pickImage(source: ImageSource.camera);
// setState(() {});
// Navigator.pop(context);
// },
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(
// Icons.camera,
// size: 60.0,
// color: kGreyTextColor,
// ),
// Text(
// lang.S.of(context).camera,
// style: theme.textTheme.titleLarge?.copyWith(
// color: kGreyTextColor,
// ),
// ),
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// );
// });
// },
// child: Stack(
// children: [
// Container(
// height: 120,
// width: 120,
// decoration: BoxDecoration(
// border: Border.all(color: Colors.black54, width: 1),
// borderRadius: const BorderRadius.all(Radius.circular(120)),
// image: pickedImage == null
// ? widget.customerModel.image.isEmptyOrNull
// ? const DecorationImage(
// image: AssetImage('images/no_shop_image.png'),
// fit: BoxFit.cover,
// )
// : DecorationImage(
// image: NetworkImage('${APIConfig.domain}${widget.customerModel.image!}'),
// fit: BoxFit.cover,
// )
// : DecorationImage(
// image: FileImage(File(pickedImage!.path)),
// fit: BoxFit.cover,
// ),
// ),
// ),
// Positioned(
// bottom: 0,
// right: 0,
// child: Container(
// height: 35,
// width: 35,
// decoration: BoxDecoration(
// border: Border.all(color: Colors.white, width: 2),
// borderRadius: const BorderRadius.all(Radius.circular(120)),
// color: kMainColor,
// ),
// child: const Icon(
// Icons.camera_alt_outlined,
// size: 20,
// color: Colors.white,
// ),
// ),
// )
// ],
// ),
// ),
// const SizedBox(height: 20),
// TextFormField(
// controller: emailController,
// decoration: InputDecoration(
// labelText: lang.S.of(context).email,
// hintText: lang.S.of(context).hintEmail,
// ),
// ),
// SizedBox(height: 20),
// TextFormField(
// controller: addressController,
// decoration: InputDecoration(
// labelText: lang.S.of(context).address,
// hintText: lang.S.of(context).enterFullAddress,
// ),
// ),
// SizedBox(height: 20),
// TextFormField(
// readOnly: true,
// controller: dueController,
// decoration: InputDecoration(
// border: const OutlineInputBorder(),
// floatingLabelBehavior: FloatingLabelBehavior.always,
// labelText: lang.S.of(context).previousDue,
// ),
// ),
// // TextFormField(
// // readOnly: true,
// // controller: dueController,
// // decoration: InputDecoration(
// // border: const OutlineInputBorder(),
// // floatingLabelBehavior:
// // FloatingLabelBehavior.always,
// // labelText: lang.S.of(context).previousDue,
// // ),
// // ),
// // Row(
// // children: [
// // Expanded(
// // child: TextFormField(
// // controller: partyCreditLimitController,
// // decoration: InputDecoration(
// // border: const OutlineInputBorder(),
// // floatingLabelBehavior: FloatingLabelBehavior.always,
// // labelText: 'Party Credit Limit',
// // //hintText: 'Enter your address'
// // hintText: 'Ex: 800'),
// // ),
// // ),
// // SizedBox(width: 20),
// // Expanded(
// // child: TextFormField(
// // controller: partyGstController,
// // decoration: InputDecoration(
// // border: const OutlineInputBorder(),
// // floatingLabelBehavior: FloatingLabelBehavior.always,
// // labelText: 'Party Gst',
// // //hintText: 'Enter your address'
// // hintText: 'Ex: 800'),
// // ),
// // ),
// // ],
// // ),
// // SizedBox(height: 4),
// // Theme(
// // data: Theme.of(context).copyWith(
// // dividerColor: Colors.transparent,
// // ),
// // child: ExpansionTile(
// // visualDensity: VisualDensity(vertical: -2, horizontal: -4),
// // tilePadding: EdgeInsets.zero,
// // trailing: SizedBox.shrink(),
// // title: Row(
// // crossAxisAlignment: CrossAxisAlignment.center,
// // children: [
// // Icon(FeatherIcons.minus, size: 20, color: Colors.red),
// // SizedBox(width: 8),
// // Text(
// // 'Billing Address',
// // style: theme.textTheme.titleMedium?.copyWith(
// // color: kMainColor,
// // ),
// // )
// // ],
// // ),
// // children: [
// // SizedBox(height: 10),
// // //___________Billing Address________________
// // TextFormField(
// // controller: billingAddressController,
// // decoration: InputDecoration(
// // labelText: 'Address',
// // hintText: 'Enter Address',
// // ),
// // ),
// // SizedBox(height: 20),
// // //--------------billing city------------------------
// // TextFormField(
// // controller: billingCityController,
// // decoration: InputDecoration(
// // labelText: 'City',
// // hintText: 'Enter city',
// // ),
// // ),
// // SizedBox(height: 20),
// // //--------------billing state------------------------
// // TextFormField(
// // controller: billingStateController,
// // decoration: InputDecoration(
// // labelText: 'State',
// // hintText: 'Enter state',
// // ),
// // ),
// // SizedBox(height: 20),
// // Row(
// // children: [
// // //--------------billing zip code------------------------
// // Expanded(
// // child: TextFormField(
// // controller: billingZipCodeCountryController,
// // decoration: InputDecoration(
// // labelText: 'Zip Code',
// // hintText: 'Enter zip code',
// // ),
// // ),
// // ),
// // SizedBox(width: 20),
// // //--------------billing country------------------------
// // Expanded(
// // child: DropdownButtonFormField(
// // isExpanded: true,
// // hint: Text(
// // 'Select Country',
// // maxLines: 1,
// // style: theme.textTheme.bodyMedium?.copyWith(
// // color: kPeraColor,
// // ),
// // overflow: TextOverflow.ellipsis,
// // ),
// // icon: Icon(Icons.keyboard_arrow_down, color: kPeraColor),
// // items: ['Bangladesh', 'Pakisthan', 'Iran'].map((entry) {
// // return DropdownMenuItem(
// // value: entry,
// // child: Text(
// // entry,
// // style: theme.textTheme.bodyMedium?.copyWith(color: kPeraColor),
// // ),
// // );
// // }).toList(),
// // value: selectedDShippingCountry,
// // onChanged: (String? value) {
// // setState(() {
// // selectedBillingCountry = value;
// // });
// // }),
// // ),
// // ],
// // ),
// // ],
// // ),
// // ),
// // Theme(
// // data: Theme.of(context).copyWith(
// // dividerColor: Colors.transparent,
// // ),
// // child: ExpansionTile(
// // tilePadding: EdgeInsets.zero,
// // visualDensity: VisualDensity(horizontal: -4, vertical: -2),
// // trailing: SizedBox.shrink(),
// // title: Row(
// // crossAxisAlignment: CrossAxisAlignment.center,
// // children: [
// // Icon(FeatherIcons.plus, size: 20),
// // SizedBox(width: 8),
// // Text(
// // 'Shipping Address',
// // style: theme.textTheme.titleMedium,
// // )
// // ],
// // ),
// // children: [
// // SizedBox(height: 10),
// // //___________Billing Address________________
// // TextFormField(
// // controller: billingAddressController,
// // decoration: InputDecoration(
// // labelText: 'Address',
// // hintText: 'Enter Address',
// // ),
// // ),
// // SizedBox(height: 20),
// // //--------------billing city------------------------
// // TextFormField(
// // controller: billingCityController,
// // decoration: InputDecoration(
// // labelText: 'City',
// // hintText: 'Enter city',
// // ),
// // ),
// // SizedBox(height: 20),
// // //--------------billing state------------------------
// // TextFormField(
// // controller: billingStateController,
// // decoration: InputDecoration(
// // labelText: 'State',
// // hintText: 'Enter state',
// // ),
// // ),
// // SizedBox(height: 20),
// // Row(
// // children: [
// // //--------------billing zip code------------------------
// // Expanded(
// // child: TextFormField(
// // controller: billingZipCodeCountryController,
// // decoration: InputDecoration(
// // labelText: 'Zip Code',
// // hintText: 'Enter zip code',
// // ),
// // ),
// // ),
// // SizedBox(width: 20),
// // //--------------billing country------------------------
// // Expanded(
// // child: DropdownButtonFormField(
// // isExpanded: true,
// // hint: Text(
// // 'Select Country',
// // maxLines: 1,
// // style: theme.textTheme.bodyMedium?.copyWith(
// // color: kPeraColor,
// // ),
// // overflow: TextOverflow.ellipsis,
// // ),
// // icon: Icon(Icons.keyboard_arrow_down, color: kPeraColor),
// // items: ['Bangladesh', 'Pakisthan', 'Iran'].map((entry) {
// // return DropdownMenuItem(
// // value: entry,
// // child: Text(
// // entry,
// // style: theme.textTheme.bodyMedium?.copyWith(color: kPeraColor),
// // ),
// // );
// // }).toList(),
// // value: selectedDShippingCountry,
// // onChanged: (String? value) {
// // setState(() {
// // selectedBillingCountry = value;
// // });
// // }),
// // ),
// // ],
// // ),
// // ],
// // ),
// // )
// ],
// ),
// isExpanded: expanded,
// ),
// ],
// ),
// SizedBox(height: 20),
// ElevatedButton(
// onPressed: () async {
// if (!permissionService.hasPermission(Permit.partiesCreate.value)) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// backgroundColor: Colors.red,
// content: Text('You do not have permission to update Party.'),
// ),
// );
// return;
// }
// if (_formKay.currentState!.validate()) {
// try {
// EasyLoading.show(
// status: lang.S.of(context).updating,
// // 'Updating...'
// );
// final party = PartyRepository();
// await party.updateParty(
// id: widget.customerModel.id.toString(),
// // Assuming id is a property in customerModel
// ref: ref,
// context: context,
// name: nameController.text,
// phone: phoneController.text,
// type: groupValue,
// image: pickedImage != null ? File(pickedImage!.path) : null,
// email: emailController.text,
// address: addressController.text,
// due: dueController.text,
// );
// EasyLoading.dismiss();
// } catch (e) {
// EasyLoading.dismiss();
// ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
// }
// }
// },
// child: Text(lang.S.of(context).update)),
// ],
// ),
// ),
// );
// }),
// ),
// );
// });
// }
// }

View 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());
});
},
);
}
}

View File

@@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
class SmsConfirmationPopup extends StatefulWidget {
final String customerName;
final String phoneNumber;
final Function onSendSms;
final VoidCallback onCancel;
const SmsConfirmationPopup({
super.key,
required this.customerName,
required this.phoneNumber,
required this.onSendSms,
required this.onCancel,
});
@override
_SmsConfirmationPopupState createState() => _SmsConfirmationPopupState();
}
class _SmsConfirmationPopupState extends State<SmsConfirmationPopup> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
final scale = _animationController.value;
return Transform.scale(
scale: scale,
child: child,
);
},
child: Dialog(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
// 'Confirm SMS to ${widget.customerName}',
'${lang.S.of(context).confirmSMSTo} ${widget.customerName}',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8.0),
Text(
//'An SMS will be sent to the following number: ${widget.phoneNumber}',
'${lang.S.of(context).anSMSWillBeSentToTheFollowingNumber} ${widget.phoneNumber}',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: widget.onCancel,
child: Text(
lang.S.of(context).cancel,
//'Cancel'
),
),
),
SizedBox(width: 15),
Flexible(
child: ElevatedButton(
style: const ButtonStyle(backgroundColor: MaterialStatePropertyAll(kMainColor)),
onPressed: () {
widget.onSendSms();
Navigator.pop(context);
},
child: Text(
lang.S.of(context).sendSMS,
maxLines: 1,
overflow: TextOverflow.ellipsis,
// 'Send SMS',
style: const TextStyle(color: Colors.white),
),
),
),
],
),
],
),
),
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_animationController.forward();
}
}

View File

@@ -0,0 +1,175 @@
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as lang;
import 'package:nb_utils/nb_utils.dart';
import '../../currency.dart';
class CustomerAllTransactionScreen extends StatefulWidget {
const CustomerAllTransactionScreen({Key? key}) : super(key: key);
@override
State<CustomerAllTransactionScreen> createState() => _CustomerAllTransactionScreenState();
}
class _CustomerAllTransactionScreenState extends State<CustomerAllTransactionScreen> {
int currentIndex = 0;
bool isSearch = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kWhite,
appBar: AppBar(
elevation: 2.0,
surfaceTintColor: kWhite,
automaticallyImplyLeading: isSearch ? false : true,
backgroundColor: kWhite,
title: isSearch
? TextFormField(
decoration: kInputDecoration.copyWith(
contentPadding: const EdgeInsets.only(left: 12, right: 5),
//hintText: 'Search Here.....',
hintText: lang.S.of(context).searchH,
),
)
: Text(
lang.S.of(context).transactions,
// 'Transactions'
),
actions: [
GestureDetector(
onTap: () {
setState(() {
isSearch = true;
});
},
child: const Padding(
padding: EdgeInsets.all(15.0),
child: Icon(
FeatherIcons.search,
color: kGreyTextColor,
),
),
)
],
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 10,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
// SalesInvoiceDetails(
// businessInfo: personalData.value!,
// saleTransaction: transaction[index],
// ).launch(context);
},
child: Column(
children: [
Container(
// padding: const EdgeInsets.all(20),
width: context.width(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
lang.S.of(context).sale,
//"Sale",
style: const TextStyle(fontSize: 16),
),
const Text('#2145'),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
// padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: const Color(0xff0dbf7d).withOpacity(0.1), borderRadius: const BorderRadius.all(Radius.circular(10))),
child: Text(
lang.S.of(context).paid,
style: const TextStyle(color: Color(0xff0dbf7d)),
),
),
const Text(
'30/08/2021',
style: TextStyle(color: Colors.grey),
),
],
),
const SizedBox(height: 10),
Text(
'${lang.S.of(context).total} : $currency 20000',
style: const TextStyle(color: Colors.grey),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${lang.S.of(context).due}: $currency 3000',
style: const TextStyle(fontSize: 16),
),
Row(
children: [
IconButton(
onPressed: () {},
icon: const Icon(
FeatherIcons.printer,
color: Colors.grey,
)),
IconButton(
onPressed: () {},
icon: const Icon(
Icons.picture_as_pdf,
color: Colors.grey,
)),
// IconButton(
// onPressed: () {},
// icon: const Icon(
// FeatherIcons.share,
// color: Colors.grey,
// ),
// ),
// IconButton(
// onPressed: () {},
// icon: const Icon(
// FeatherIcons.moreVertical,
// color: Colors.grey,
// )),
],
)
],
)
],
),
),
Container(
height: 0.5,
width: context.width(),
color: Colors.grey,
)
],
),
);
},
)
],
),
),
),
);
}
}