Files
kulakpos_app/lib/Screens/User Roles/add_user_role_screen.dart

720 lines
28 KiB
Dart
Raw Permalink Normal View History

2026-02-07 15:57:09 +07:00
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mobile_pos/constant.dart';
import 'package:mobile_pos/generated/l10n.dart' as l;
import '../../Provider/profile_provider.dart';
import '../branch/model/branch_list_model.dart';
import '../branch/provider/branch_list_provider.dart';
import 'Model/user_role_model_new.dart';
import '../../service/check_user_role_permission_provider.dart';
import 'Provider/user_role_provider.dart';
import 'Repo/user_role_repo.dart';
class AddUserRoleScreen extends ConsumerStatefulWidget {
const AddUserRoleScreen({
super.key,
this.userRole,
});
final UserRoleListModelNew? userRole;
@override
_AddUserRoleScreenState createState() => _AddUserRoleScreenState();
}
class _AddUserRoleScreenState extends ConsumerState<AddUserRoleScreen> {
GlobalKey<FormState> globalKey = GlobalKey<FormState>();
bool _selectAll = false;
bool validateAndSave() {
final form = globalKey.currentState;
if (form!.validate()) {
form.save();
return true;
}
return false;
}
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController confirmPasswordController = TextEditingController();
TextEditingController titleController = TextEditingController();
Map<String, Permission> selectedPermissions = {};
@override
void initState() {
super.initState();
if (widget.userRole != null) {
emailController.text = widget.userRole!.email ?? '';
titleController.text = widget.userRole!.name ?? '';
// selectedBranch = widget.userRole.branchId ?? '';
selectedPermissions = widget.userRole!.visibility.map((key, value) {
return MapEntry(
key,
Permission(
read: value['read'],
create: value['create'],
update: value['update'],
delete: value['delete'],
price: value['price'],
));
});
}
}
bool _obscureText = true;
void _togglePasswordVisibility() {
setState(() {
_obscureText = !_obscureText;
});
}
BranchData? selectedBranch;
bool _branchInitialized = false;
@override
Widget build(BuildContext context) {
final _lang = l.S.of(context);
final modules = _basePermissions.modules;
final branchList = ref.watch(branchListProvider);
final businessInfo = ref.watch(businessInfoProvider);
final keys = modules.keys.toList(growable: false);
final theme = Theme.of(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(widget.userRole != null ? _lang.updateRole : _lang.addRole),
centerTitle: true,
iconTheme: const IconThemeData(color: Colors.black),
elevation: 0.0,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(
color: Color(0xFFE8E9F2),
height: 1,
),
),
),
body: businessInfo.when(data: (infoSnap) {
return SingleChildScrollView(
child: Form(
key: globalKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
children: [
if ((infoSnap.data?.addons?.multiBranchAddon == true) &&
(infoSnap.data?.enrolledPlan?.allowMultibranch == 1) &&
(infoSnap.data?.user?.activeBranch == null)) ...{
branchList.when(
data: (snapshot) {
if (widget.userRole != null && !_branchInitialized) {
final branchId = widget.userRole!.branchId;
try {
selectedBranch = snapshot.data!.firstWhere((branch) => branch.id == branchId);
} catch (e) {
selectedBranch = null;
}
_branchInitialized = true;
}
return DropdownButtonFormField<BranchData?>(
value: selectedBranch,
decoration: InputDecoration(
labelText: _lang.branch,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
items: [
DropdownMenuItem<BranchData?>(
value: null,
child: Text(
_lang.branchList,
style: theme.textTheme.bodyMedium?.copyWith(
color: kTitleColor,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
...?snapshot.data?.map((branch) {
return DropdownMenuItem<BranchData?>(
value: branch,
child: Text(
branch.name ?? 'Unnamed',
style: theme.textTheme.bodyMedium?.copyWith(
color: kTitleColor,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
);
}).toList(),
],
onChanged: (value) {
setState(() {
selectedBranch = value;
print('---------------------------->${selectedBranch?.id ?? ''}');
});
},
);
},
error: (e, stack) => Center(child: Text(e.toString())),
loading: () => const Center(child: LinearProgressIndicator()),
),
SizedBox(height: 24),
},
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name cannot be empty';
}
return null;
},
controller: titleController,
decoration: InputDecoration(
labelText: _lang.name,
hintText: _lang.enterUserName,
border: OutlineInputBorder(),
),
),
SizedBox(height: 24),
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return _lang.emailCannotBeEmpty;
} else if (!value.contains('@')) {
return _lang.pleaseEnterAValidEmail;
}
return null;
},
controller: emailController,
decoration: InputDecoration(
labelText: _lang.email,
hintText: _lang.enterYourEmailAddress,
border: OutlineInputBorder(),
),
),
SizedBox(height: 24),
TextFormField(
obscureText: _obscureText,
controller: passwordController,
validator: (value) {
if (widget.userRole != null) {
return null;
}
if (value == null || value.isEmpty) {
return _lang.passwordCannotBeEmpty;
} else if (value.length < 4) {
return _lang.pleaseEnterABiggerPassword;
}
return null;
},
decoration: InputDecoration(
labelText: _lang.password,
hintText: _lang.enterYourPassword,
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(
_obscureText ? Icons.visibility_off : Icons.visibility,
color: Color(0xff7B7C84),
),
onPressed: _togglePasswordVisibility,
),
),
),
],
),
),
/// Select All Checkbox
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Checkbox(
activeColor: kMainColor,
side: BorderSide(color: Color(0xffA3A3A3)),
value: _selectAll,
onChanged: (bool? value) {
if (value != null) {
setState(() {
_selectAll = value;
_toggleAllPermissions(value);
});
}
},
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
RichText(
text: TextSpan(
text: _lang.selectAll,
style: theme.textTheme.bodyMedium?.copyWith(color: kGreyTextColor, fontSize: 16),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
_selectAll = !_selectAll;
_toggleAllPermissions(_selectAll);
});
},
),
),
],
),
),
/// Permissions Table
LayoutBuilder(
builder: (context, constraints) {
return FittedBox(
child: DataTable(
headingRowColor: WidgetStateProperty.all(Color(0xffF7F7F7)),
columnSpacing: 16,
border: const TableBorder(
verticalInside: BorderSide(color: Color(0xffE6E6E6)),
),
headingTextStyle: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.bold),
columns: [
DataColumn(label: Text(_lang.sNo)),
DataColumn(label: Text(_lang.feature)),
DataColumn(label: Text(_lang.read)),
DataColumn(label: Text(_lang.create)),
DataColumn(label: Text(_lang.update)),
DataColumn(label: Text(_lang.update)),
DataColumn(label: Text(_lang.viewPrice)),
],
rows: List<DataRow>.generate(keys.length, (index) {
final key = keys[index];
final perm = selectedPermissions[key] ?? modules[key];
return DataRow(cells: [
DataCell(Text('${index + 1}')),
DataCell(
Text(permissionDisplayTitles[key] ?? key,
style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600)),
),
_buildCheckboxCell(
value: perm?.read,
onChanged: (v) => _togglePermission(key, 'read', v),
),
_buildCheckboxCell(
value: perm?.create,
onChanged: (v) => _togglePermission(key, 'create', v),
),
_buildCheckboxCell(
value: perm?.update,
onChanged: (v) => _togglePermission(key, 'update', v),
),
_buildCheckboxCell(
value: perm?.delete,
onChanged: (v) => _togglePermission(key, 'delete', v),
),
_buildCheckboxCell(
value: perm?.price,
onChanged: (v) => _togglePermission(key, 'price', v),
),
]);
}),
),
);
},
)
],
),
),
);
}, error: (e, stack) {
return Text(e.toString());
}, loading: () {
return const Center(child: CircularProgressIndicator());
}),
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(10.0),
child: ElevatedButton(
// onPressed: _createUserRole,
onPressed: () async {
print('--------------------------->>>>${selectedBranch?.id}');
if (validateAndSave()) {
UserRoleRepo repo = UserRoleRepo();
if (selectedPermissions.isEmpty) {
selectedPermissions = _basePermissions.modules.map((key, perm) {
return MapEntry(
key,
Permission(
read: perm.read != null ? "0" : null,
create: perm.create != null ? "0" : null,
update: perm.update != null ? "0" : null,
delete: perm.delete != null ? "0" : null,
price: perm.price != null ? "0" : null,
));
});
}
final visibilityMap = selectedPermissions.map((key, perm) {
final Map<String, String> permissionMap = {};
if (perm.read != null) {
permissionMap["read"] = perm.read ?? "0";
}
if (perm.create != null) {
permissionMap["create"] = perm.create ?? "0";
}
if (perm.update != null) {
permissionMap["update"] = perm.update ?? "0";
}
if (perm.delete != null) {
permissionMap["delete"] = perm.delete ?? "0";
}
if (perm.price != null) {
permissionMap["price"] = perm.price ?? "0";
}
return MapEntry(key, permissionMap);
});
print('=========================$visibilityMap');
if (widget.userRole == null) {
// Create
await repo.addUser(
ref: ref,
context: context,
branchId: selectedBranch?.id.toString() ?? '',
name: titleController.text,
email: emailController.text,
password: passwordController.text,
visibility: visibilityMap,
);
} else {
// Update
await repo.updateUser(
ref: ref,
context: context,
userId: widget.userRole?.id.toString() ?? '',
branchId: selectedBranch?.id.toString() ?? '',
name: titleController.text,
email: emailController.text,
password: passwordController.text,
visibility: visibilityMap,
);
}
ref.refresh(userRoleProvider);
Navigator.pop(context);
}
},
child: Text(widget.userRole != null ? _lang.update : _lang.create),
),
),
);
}
void _togglePermission(String key, String action, bool? value) {
final basePerm = selectedPermissions[key] ?? _basePermissions.modules[key];
if (basePerm == null) return;
final updated = basePerm.copyWith(
read: action == 'read' ? (value == true ? "1" : "0") : basePerm.read,
create: action == 'create' ? (value == true ? "1" : "0") : basePerm.create,
update: action == 'update' ? (value == true ? "1" : "0") : basePerm.update,
delete: action == 'delete' ? (value == true ? "1" : "0") : basePerm.delete,
price: action == 'price' ? (value == true ? "1" : "0") : basePerm.price,
);
setState(() {
selectedPermissions[key] = updated;
});
}
void _toggleAllPermissions(bool isSelected) {
final updated = <String, Permission>{};
_basePermissions.modules.forEach((key, perm) {
updated[key] = Permission(
read: perm.read != null ? (isSelected ? "1" : "0") : null,
create: perm.create != null ? (isSelected ? "1" : "0") : null,
update: perm.update != null ? (isSelected ? "1" : "0") : null,
delete: perm.delete != null ? (isSelected ? "1" : "0") : null,
price: perm.price != null ? (isSelected ? "1" : "0") : null,
);
});
setState(() {
selectedPermissions = updated;
});
}
DataCell _buildCheckboxCell({
required String? value,
required ValueChanged<bool?> onChanged,
}) {
return DataCell(
value == null
? const SizedBox.shrink()
: Center(
child: Checkbox(
value: value == "1",
onChanged: onChanged,
),
),
);
}
final _basePermissions = PermissionModules.fromJson({
"dashboard": {"read": "0"},
"sales": {
"read": "0",
"create": "0",
"update": "0",
"delete": "0",
},
"inventory": {
"read": "0",
"create": "0",
},
"sale-returns": {"read": "0", "create": "0", "price": "0"},
"purchases": {"read": "0", "create": "0", "update": "0", "delete": "0", "price": "0"},
"purchase-returns": {"read": "0", "create": "0", "price": "0"},
"products": {"read": "0", "create": "0", "update": "0", "delete": "0", "price": "0"},
// "branches": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"products-expired": {"read": "0"},
"barcodes": {"read": "0", "create": "0"},
"bulk-uploads": {"read": "0", "create": "0"},
"categories": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"brands": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"units": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"product-models": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"stocks": {
"read": "0",
"price": "0",
},
"expired-products": {"read": "0"},
"parties": {
"read": "0",
"create": "0",
"update": "0",
"delete": "0",
},
"incomes": {
"read": "0",
"create": "0",
"update": "0",
"delete": "0",
},
"income-categories": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"expenses": {
"read": "0",
"create": "0",
"update": "0",
"delete": "0",
},
"expense-categories": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"vats": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"dues": {"read": "0"},
"subscriptions": {"read": "0"},
"loss-profits": {"read": "0"},
"payment-types": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"roles": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"department": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"designations": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"shifts": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"employees": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"leave-types": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"leaves": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"holidays": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"attendances": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"payrolls": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"attendance-reports": {"read": "0"},
"payroll-reports": {"read": "0"},
"leave-reports": {"read": "0"},
"warehouses": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"transfers": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"racks": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"shelfs": {"read": "0", "create": "0", "update": "0", "delete": "0"},
"manage-settings": {"read": "0", "update": "0"},
"download-apk": {"read": "0"},
"sale-reports": {"read": "0"},
"sale-return-reports": {"read": "0"},
"purchase-reports": {"read": "0"},
"purchase-return-reports": {"read": "0"},
"vat-reports": {"read": "0"},
"income-reports": {"read": "0"},
"expense-reports": {"read": "0"},
"loss-profits-details": {"read": "0"},
"stock-reports": {"read": "0"},
"due-reports": {"read": "0"},
"supplier-due-reports": {"read": "0"},
"loss-profit-reports": {"read": "0"},
"transaction-history-reports": {"read": "0"},
"subscription-reports": {"read": "0"},
"expired-product-reports": {"read": "0"},
"day-book-reports": {"read": "0"},
"bill-wise-profit": {"read": "0"},
"cashflow": {"read": "0"},
"balance-sheet": {"read": "0"},
"tax-report": {"read": "0"},
"customer-ledger": {"read": "0"},
"supplier-ledger": {"read": "0"},
"parity-wise-profit": {"read": "0"},
"top-5-customer": {"read": "0"},
"top-5-supplier": {"read": "0"},
"combo-report": {"read": "0"},
"top-5-product": {"read": "0"},
"product-wise-profit-loss": {"read": "0"},
"product-purchase-report": {"read": "0"},
"product-sales-report": {"read": "0"},
"product-purchase-history": {"read": "0"},
"product-sale-history": {"read": "0"},
});
final Map<String, String> permissionDisplayTitles = {
"dashboard": l.S.current.dashboard,
"sales": l.S.current.sales,
"inventory": l.S.current.inventory,
"sale-returns": l.S.current.saleReturn,
"purchases": l.S.current.purchase,
"purchase-returns": l.S.current.purchaseReturns,
"products": l.S.current.products,
// "branches": "Branches",
"products-expired": l.S.current.expiredProduct,
"barcodes": l.S.current.barcodes,
"bulk-uploads": l.S.current.bulkUploads,
"categories": l.S.current.categories,
"brands": l.S.current.brands,
"units": l.S.current.units,
"product-models": l.S.current.productModels,
"stocks": l.S.current.stocks,
"expired-products": l.S.current.expiredProduct,
"parties": l.S.current.parties,
"incomes": l.S.current.income,
"income-categories": l.S.current.incomes,
"expenses": l.S.current.expense,
"expense-categories": l.S.current.expenseCat,
"vats": l.S.current.vat,
"dues": l.S.current.dues,
"subscriptions": l.S.current.subscriptions,
"loss-profits": l.S.current.profitAndLoss,
"payment-types": l.S.current.paymentTypes,
"roles": l.S.current.roles,
"department": l.S.current.department,
"designations": l.S.current.designation,
"shifts": l.S.current.shift,
"employees": l.S.current.employee,
"leave-types": l.S.current.leaveType,
"leaves": l.S.current.leave,
"holidays": l.S.current.holiday,
"attendances": l.S.current.attendance,
"payrolls": l.S.current.payroll,
"attendance-reports": l.S.current.attendanceReport,
"payroll-reports": l.S.current.payrollReports,
"leave-reports": l.S.current.leaveReports,
"warehouses": l.S.current.warehouse,
"transfers": l.S.current.transfer,
"racks": l.S.current.racks,
"shelfs": l.S.current.shelves,
"manage-settings": l.S.current.manageSetting,
"download-apk": l.S.current.downloadApk,
"sale-reports": l.S.current.salesReport,
"sale-return-reports": l.S.current.salesReturnReport,
"purchase-reports": l.S.current.purchaseReport,
"purchase-return-reports": l.S.current.purchaseReturnReport,
"vat-reports": l.S.current.vatReports,
"income-reports": l.S.current.incomeReport,
"expense-reports": l.S.current.expenseReport,
"loss-profits-details": l.S.current.profitAndLossDetailsReport,
"stock-reports": l.S.current.stockReport,
"due-reports": l.S.current.dueReport,
"supplier-due-reports": l.S.current.supplierDue,
"loss-profit-reports": l.S.current.profitAndLoss,
"transaction-history-reports": l.S.current.transactionsHistoryReport,
"subscription-reports": l.S.current.subscriptionReports,
"expired-product-reports": l.S.current.expireProductReports,
"day-book-reports": l.S.current.dayBook,
"bill-wise-profit": l.S.current.billWiseProfit,
"cashflow": l.S.current.cashFlow,
"balance-sheet": l.S.current.balanceSheet,
"tax-report": l.S.current.taxReport,
"customer-ledger": l.S.current.customerLedger,
"supplier-ledger": l.S.current.supplierLedger,
"parity-wise-profit": l.S.current.partyWiseProfit,
"top-5-customer": l.S.current.top5Customer,
"top-5-supplier": l.S.current.top5Supplier,
"combo-report": l.S.current.comboReport,
"top-5-product": l.S.current.top5Product,
"product-wise-profit-loss": l.S.current.productWiseProfitAndLoss,
"product-purchase-report": l.S.current.productPurchaseReport,
"product-sales-report": l.S.current.productSalesReport,
"product-purchase-history": l.S.current.productPurchaseHistory,
"product-sale-history": l.S.current.productSaleHistory,
};
}
class Permission {
final String? read;
final String? create;
final String? update;
final String? delete;
final String? price;
const Permission({
this.read,
this.create,
this.update,
this.delete,
this.price,
});
Permission copyWith({
String? read,
String? create,
String? update,
String? delete,
String? price,
}) {
return Permission(
read: read ?? this.read,
create: create ?? this.create,
update: update ?? this.update,
delete: delete ?? this.delete,
price: price ?? this.price,
);
}
factory Permission.fromJson(Map<String, dynamic> json) {
return Permission(
read: json['read'] as String?,
create: json['create'] as String?,
update: json['update'] as String?,
delete: json['delete'] as String?,
price: json['price'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
if (read != null) 'read': read,
if (create != null) 'create': create,
if (update != null) 'update': update,
if (delete != null) 'delete': delete,
if (price != null) 'price': price,
};
}
}
class PermissionModules {
final Map<String, Permission> modules;
PermissionModules(this.modules);
factory PermissionModules.fromJson(Map<String, dynamic> json) {
return PermissionModules(
json.map((key, value) => MapEntry(key, Permission.fromJson(value))),
);
}
}