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