member screen updates
This commit is contained in:
parent
8d4fbfed00
commit
e187618437
|
|
@ -28,15 +28,7 @@ const addMemberToGroup = async (req, res) => {
|
|||
});
|
||||
}
|
||||
|
||||
// Check if group is still forming
|
||||
if (chitGroup.status !== 'forming') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Cannot add members after chit group has started'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if group is full
|
||||
// Check if group is full (allow adding members to any status as long as not full)
|
||||
const currentMemberCount = await GroupMember.count({
|
||||
where: { group_id: groupId, status: 'active' }
|
||||
});
|
||||
|
|
@ -44,7 +36,7 @@ const addMemberToGroup = async (req, res) => {
|
|||
if (currentMemberCount >= chitGroup.max_members) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Chit group is full'
|
||||
message: `Chit group is full (${currentMemberCount}/${chitGroup.max_members} members)`
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import '../../features/notifications/notification_center_page.dart';
|
|||
import 'chit_groups_page.dart';
|
||||
import 'create_group_page.dart';
|
||||
import 'import_existing_group_dialog.dart';
|
||||
import 'members_page.dart';
|
||||
import '../../test_animated_draw.dart';
|
||||
import '../../features/recordings/recordings_page.dart';
|
||||
|
||||
|
|
@ -574,10 +575,7 @@ class ManagerDashboard extends StatelessWidget {
|
|||
}
|
||||
|
||||
void _navigateToMembers() {
|
||||
SnackbarUtil.showInfo(
|
||||
'Members page will be implemented next',
|
||||
title: 'Coming Soon',
|
||||
);
|
||||
Get.to(() => const MembersPage());
|
||||
}
|
||||
|
||||
void _navigateToPayments() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,709 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import '../../core/services/chit_group_service.dart';
|
||||
import '../../core/models/group_member.dart';
|
||||
import '../../core/models/chit_group.dart';
|
||||
import 'add_user_dialog.dart';
|
||||
|
||||
class MembersPage extends StatefulWidget {
|
||||
const MembersPage({super.key});
|
||||
|
||||
@override
|
||||
State<MembersPage> createState() => _MembersPageState();
|
||||
}
|
||||
|
||||
class _MembersPageState extends State<MembersPage> {
|
||||
final _chitGroupService = Get.find<ChitGroupService>();
|
||||
final _searchController = TextEditingController();
|
||||
String _searchQuery = '';
|
||||
String _selectedFilter = 'all'; // all, active, inactive
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadAllMembers();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadAllMembers() async {
|
||||
// Load all groups to get all members
|
||||
await _chitGroupService.loadManagerChitGroups();
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _getAllMembers() {
|
||||
final allMembers = <Map<String, dynamic>>[];
|
||||
final groups = _chitGroupService.chitGroups;
|
||||
|
||||
for (var group in groups) {
|
||||
if (group.members != null) {
|
||||
for (var member in group.members!) {
|
||||
// Check if member already in list (user can be in multiple groups)
|
||||
final existingIndex = allMembers.indexWhere(
|
||||
(m) => m['userId'] == member.userId,
|
||||
);
|
||||
|
||||
if (existingIndex == -1) {
|
||||
allMembers.add({
|
||||
'userId': member.userId,
|
||||
'member': member,
|
||||
'groups': [group],
|
||||
});
|
||||
} else {
|
||||
// Add group to existing member's group list
|
||||
(allMembers[existingIndex]['groups'] as List).add(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allMembers;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _getFilteredMembers() {
|
||||
var members = _getAllMembers();
|
||||
|
||||
// Apply status filter
|
||||
if (_selectedFilter == 'active') {
|
||||
members = members.where((m) {
|
||||
final member = m['member'] as GroupMember;
|
||||
return member.status.toLowerCase() == 'active';
|
||||
}).toList();
|
||||
} else if (_selectedFilter == 'inactive') {
|
||||
members = members.where((m) {
|
||||
final member = m['member'] as GroupMember;
|
||||
return member.status.toLowerCase() != 'active';
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (_searchQuery.isNotEmpty) {
|
||||
members = members.where((m) {
|
||||
final member = m['member'] as GroupMember;
|
||||
final name = member.user?.fullName?.toLowerCase() ?? '';
|
||||
final mobile = member.user?.mobileNumber ?? '';
|
||||
final query = _searchQuery.toLowerCase();
|
||||
return name.contains(query) || mobile.contains(query);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade50,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'All Members',
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.green.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.person_add, size: 24.w),
|
||||
onPressed: _showAddUserDialog,
|
||||
tooltip: 'Add New Member',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Obx(() {
|
||||
if (_chitGroupService.isLoading.value) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.green.shade600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final filteredMembers = _getFilteredMembers();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Search and Filter Bar
|
||||
Container(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
color: Colors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
// Search Bar
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search by name or mobile...',
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: _searchQuery.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_searchController.clear();
|
||||
_searchQuery = '';
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey.shade50,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 16.w,
|
||||
vertical: 12.h,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
|
||||
// Filter Chips
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Filter:',
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
_buildFilterChip('All', 'all'),
|
||||
SizedBox(width: 8.w),
|
||||
_buildFilterChip('Active', 'active'),
|
||||
SizedBox(width: 8.w),
|
||||
_buildFilterChip('Inactive', 'inactive'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Member Count
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
|
||||
color: Colors.blue.shade50,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.people, color: Colors.blue.shade700, size: 20.w),
|
||||
SizedBox(width: 8.w),
|
||||
Text(
|
||||
'${filteredMembers.length} member${filteredMembers.length != 1 ? 's' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Members List
|
||||
Expanded(
|
||||
child: filteredMembers.isEmpty
|
||||
? _buildEmptyState()
|
||||
: RefreshIndicator(
|
||||
onRefresh: _loadAllMembers,
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
itemCount: filteredMembers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final memberData = filteredMembers[index];
|
||||
final member = memberData['member'] as GroupMember;
|
||||
final groups = memberData['groups'] as List<ChitGroup>;
|
||||
|
||||
return _buildMemberCard(member, groups);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChip(String label, String value) {
|
||||
final isSelected = _selectedFilter == value;
|
||||
|
||||
return FilterChip(
|
||||
label: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
selected: isSelected,
|
||||
onSelected: (selected) {
|
||||
setState(() {
|
||||
_selectedFilter = value;
|
||||
});
|
||||
},
|
||||
selectedColor: Colors.green.shade100,
|
||||
checkmarkColor: Colors.green.shade700,
|
||||
backgroundColor: Colors.grey.shade100,
|
||||
side: BorderSide(
|
||||
color: isSelected ? Colors.green.shade600 : Colors.grey.shade300,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMemberCard(GroupMember member, List<ChitGroup> groups) {
|
||||
final user = member.user;
|
||||
final isActive = member.status.toLowerCase() == 'active';
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.only(bottom: 12.h),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _showMemberDetails(member, groups),
|
||||
borderRadius: BorderRadius.circular(16.r),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
child: Row(
|
||||
children: [
|
||||
// Avatar
|
||||
CircleAvatar(
|
||||
radius: 28.r,
|
||||
backgroundColor: isActive ? Colors.green.shade600 : Colors.grey.shade400,
|
||||
child: Text(
|
||||
user?.fullName?.substring(0, 1).toUpperCase() ?? 'M',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
|
||||
// Member Info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
user?.fullName ?? 'Unknown',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
user?.mobileNumber ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.h),
|
||||
Wrap(
|
||||
spacing: 8.w,
|
||||
children: [
|
||||
// Status Badge
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? Colors.green.shade100 : Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
border: Border.all(
|
||||
color: isActive ? Colors.green.shade600 : Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
isActive ? 'Active' : 'Inactive',
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isActive ? Colors.green : Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Groups Count
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
border: Border.all(color: Colors.blue.shade200),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.group, size: 14.w, color: Colors.blue.shade700),
|
||||
SizedBox(width: 4.w),
|
||||
Text(
|
||||
'${groups.length} group${groups.length != 1 ? 's' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Arrow
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16.w,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(32.w),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.people_outline,
|
||||
size: 80.w,
|
||||
color: Colors.grey.shade300,
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
Text(
|
||||
_searchQuery.isNotEmpty
|
||||
? 'No members found'
|
||||
: 'No members yet',
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
Text(
|
||||
_searchQuery.isNotEmpty
|
||||
? 'Try a different search term'
|
||||
: 'Add members to your chit groups to see them here',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
if (_searchQuery.isEmpty) ...[
|
||||
SizedBox(height: 32.h),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _showAddUserDialog,
|
||||
icon: Icon(Icons.person_add, size: 20.w),
|
||||
label: Text(
|
||||
'Add Member',
|
||||
style: TextStyle(fontSize: 16.sp),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.green.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showMemberDetails(GroupMember member, List<ChitGroup> groups) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(24.r),
|
||||
topRight: Radius.circular(24.r),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Handle
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 12.h),
|
||||
width: 40.w,
|
||||
height: 4.h,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(2.r),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
|
||||
// Header
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 32.r,
|
||||
backgroundColor: Colors.green.shade600,
|
||||
child: Text(
|
||||
member.user?.fullName?.substring(0, 1).toUpperCase() ?? 'M',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
member.user?.fullName ?? 'Unknown',
|
||||
style: TextStyle(
|
||||
fontSize: 20.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
member.user?.mobileNumber ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// Content
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24.w),
|
||||
children: [
|
||||
// Stats
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Total Paid',
|
||||
'₹${_formatCurrency(member.totalPaid)}',
|
||||
Icons.payment,
|
||||
Colors.green,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Total Won',
|
||||
'₹${_formatCurrency(member.totalWon)}',
|
||||
Icons.emoji_events,
|
||||
Colors.amber,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// Groups
|
||||
Text(
|
||||
'Member of Groups (${groups.length})',
|
||||
style: TextStyle(
|
||||
fontSize: 16.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
...groups.map((group) => _buildGroupChip(group)),
|
||||
|
||||
SizedBox(height: 24.h),
|
||||
|
||||
// Additional Info
|
||||
_buildInfoRow('Joined', _formatDate(member.joinedDate)),
|
||||
SizedBox(height: 12.h),
|
||||
_buildInfoRow('Status', member.status),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(String label, String value, IconData icon, Color color) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16.w),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24.w),
|
||||
SizedBox(height: 8.h),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildGroupChip(ChitGroup group) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(bottom: 8.h),
|
||||
padding: EdgeInsets.all(12.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(12.r),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.account_balance_wallet,
|
||||
color: Colors.green.shade600,
|
||||
size: 20.w,
|
||||
),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
group.name,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${group.status} • ₹${_formatCurrency(group.monthlyInstallment)}/month',
|
||||
style: TextStyle(
|
||||
fontSize: 12.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 100.w,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddUserDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const AddUserDialog(),
|
||||
).then((result) {
|
||||
if (result == true) {
|
||||
// Reload members
|
||||
_loadAllMembers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
String _formatCurrency(double amount) {
|
||||
final amountStr = amount.toStringAsFixed(0);
|
||||
return amountStr.replaceAllMapped(
|
||||
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]},',
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.day}/${date.month}/${date.year}';
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue