diff --git a/backend/src/controllers/monthlyDrawController.js b/backend/src/controllers/monthlyDrawController.js index bb797a6..598ee14 100644 --- a/backend/src/controllers/monthlyDrawController.js +++ b/backend/src/controllers/monthlyDrawController.js @@ -146,6 +146,16 @@ const createMonthlyDraw = async (req, res) => { message: 'Selected winner is not a member of this group' }); } + + // Check if this member has already won + if (wonMemberIds.includes(winner_id)) { + return res.status(400).json({ + success: false, + message: `${selectedWinner.User.full_name} has already won in a previous draw. Each member can only win once.`, + alreadyWon: true, + winnerName: selectedWinner.User.full_name + }); + } } else { // Regular draw - provably fair random selection serverSeed = crypto.randomBytes(32).toString('hex'); diff --git a/luckychit/lib/interfaces/manager/add_past_draw_dialog.dart b/luckychit/lib/interfaces/manager/add_past_draw_dialog.dart index 5c48b1a..d4cca9a 100644 --- a/luckychit/lib/interfaces/manager/add_past_draw_dialog.dart +++ b/luckychit/lib/interfaces/manager/add_past_draw_dialog.dart @@ -84,10 +84,22 @@ class _AddPastDrawDialogState extends State { ); Get.back(result: true); } else { - SnackbarUtil.showError( - response['message'] ?? 'Failed to add draw', - title: 'Error', - ); + // Check if it's a duplicate winner error + final isAlreadyWon = response['alreadyWon'] ?? false; + final winnerName = response['winnerName'] ?? ''; + + if (isAlreadyWon && winnerName.isNotEmpty) { + SnackbarUtil.showError( + '$winnerName has already won in a previous draw.\nEach member can only win once.', + title: 'Duplicate Winner', + duration: const Duration(seconds: 4), + ); + } else { + SnackbarUtil.showError( + response['message'] ?? 'Failed to add draw', + title: 'Error', + ); + } } } catch (e) { SnackbarUtil.showError('Error: ${e.toString()}'); @@ -223,6 +235,10 @@ class _AddPastDrawDialogState extends State { ); } + // Get list of members who have already won + final monthlyDraws = Get.find().monthlyDraws; + final winnerIds = monthlyDraws.map((draw) => draw.winnerId).where((id) => id != null).toSet(); + return Container( constraints: BoxConstraints( maxHeight: 300.h, // Limit height for scrolling @@ -237,10 +253,11 @@ class _AddPastDrawDialogState extends State { itemBuilder: (context, index) { final member = members[index]; final isSelected = member.userId == _selectedMemberId; + final hasAlreadyWon = winnerIds.contains(member.userId); final isLast = index == members.length - 1; return InkWell( - onTap: () { + onTap: hasAlreadyWon ? null : () { setState(() { _selectedMemberId = member.userId; }); @@ -255,7 +272,9 @@ class _AddPastDrawDialogState extends State { vertical: 14.h, ), decoration: BoxDecoration( - color: isSelected ? Colors.green.shade50 : null, + color: hasAlreadyWon + ? Colors.grey.shade100 + : (isSelected ? Colors.green.shade50 : null), border: !isLast ? Border( bottom: BorderSide(color: Colors.grey.shade200), ) : null, @@ -263,8 +282,12 @@ class _AddPastDrawDialogState extends State { child: Row( children: [ Icon( - isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, - color: isSelected ? Colors.green.shade600 : Colors.grey.shade400, + hasAlreadyWon + ? Icons.block + : (isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked), + color: hasAlreadyWon + ? Colors.grey.shade400 + : (isSelected ? Colors.green.shade600 : Colors.grey.shade400), size: 24.w, ), SizedBox(width: 12.w), @@ -272,12 +295,40 @@ class _AddPastDrawDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - member.user?.fullName ?? 'Unknown', - style: TextStyle( - fontSize: 16.sp, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - ), + Row( + children: [ + Expanded( + child: Text( + member.user?.fullName ?? 'Unknown', + style: TextStyle( + fontSize: 16.sp, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + color: hasAlreadyWon ? Colors.grey.shade500 : null, + decoration: hasAlreadyWon ? TextDecoration.lineThrough : null, + ), + ), + ), + if (hasAlreadyWon) ...[ + Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, + vertical: 2.h, + ), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(4.r), + ), + child: Text( + 'Already Won', + style: TextStyle( + fontSize: 10.sp, + color: Colors.orange.shade800, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ], ), if (member.user?.mobileNumber != null) ...[ SizedBox(height: 2.h), @@ -285,7 +336,7 @@ class _AddPastDrawDialogState extends State { member.user!.mobileNumber, style: TextStyle( fontSize: 13.sp, - color: Colors.grey.shade600, + color: hasAlreadyWon ? Colors.grey.shade400 : Colors.grey.shade600, ), ), ],