@@ -305,34 +305,336 @@ def fetch_group_expenses(group_id):
305305 # Fetch members for payer selection
306306 members = fetch_group_members (group .get ('_id' ))
307307 member_options = {member .get ('user' , {}).get ('name' , f'User { i } ' ): member .get ('userId' ) for i , member in enumerate (members , 1 )}
308- selected_payer = st .selectbox ("Paid by" , options = list (member_options .keys ()), key = f"expense_payer_page_{ group .get ('_id' )} " )
308+
309+ col1 , col2 = st .columns ([3 , 1 ])
310+ with col1 :
311+ selected_payer = st .selectbox ("Paid by" , options = list (member_options .keys ()), key = f"expense_payer_page_{ group .get ('_id' )} " )
312+ with col2 :
313+ st .write ("💰 **Total Amount**" )
314+ st .write (f"**₹{ expense_amount :.2f} **" )
315+
316+ # Split options with tabs
317+ st .write ("### Split Options" )
318+ split_method_tooltip = """
319+ - Equally: Split the amount equally between selected members
320+ - By Percentages: Specify what percentage of the bill each person pays
321+ - By Shares: Assign shares to each person (e.g., 1 share = 1 portion)
322+ - By Exact Value: Enter the exact amount each person should pay
323+ """
324+ st .info (split_method_tooltip )
325+
326+ # Set up tab tracking - make the radio button visually match the tabs
327+ tab_options = ["Equally" , "By Percentages" , "By Shares" , "By Exact Value" ]
328+ tab_key = f"active_tab_{ group .get ('_id' )} "
329+
330+ if tab_key not in st .session_state :
331+ st .session_state [tab_key ] = "Equally"
332+
333+ # Create a visible tab selector using radio buttons
334+ active_tab = st .radio (
335+ "Split Method" ,
336+ tab_options ,
337+ horizontal = True ,
338+ key = tab_key
339+ )
340+
341+ # Create the tabs that show content based on the active tab
342+ split_tabs = st .tabs (tab_options )
343+
344+ # Only show the content for the active tab
345+ active_tab_index = tab_options .index (active_tab )
346+
347+ # Initialize session state for member selection
348+ if f"selected_members_{ group .get ('_id' )} " not in st .session_state :
349+ st .session_state [f"selected_members_{ group .get ('_id' )} " ] = [m .get ("userId" ) for m in members ]
350+
351+ # Tab 1: Split Equally
352+ with split_tabs [0 ]:
353+ st .write ("Split equally between:" )
354+
355+ # Initialize selected members dict if not exists
356+ tab_key = f"equal_members_{ group .get ('_id' )} "
357+ if tab_key not in st .session_state :
358+ st .session_state [tab_key ] = {m .get ("userId" ): True for m in members }
359+
360+ # Select/Deselect All checkbox
361+ all_selected_key = f"all_members_equal_{ group .get ('_id' )} "
362+
363+ # Check if all are currently selected
364+ all_currently_selected = all (st .session_state [tab_key ].values ())
365+
366+ # The checkbox for Select All / Deselect All
367+ all_selected = st .checkbox (
368+ "Select/Deselect All" ,
369+ value = all_currently_selected ,
370+ key = all_selected_key
371+ )
372+
373+ # If the checkbox state changes, update all members
374+ if all_selected != all_currently_selected :
375+ for member in members :
376+ st .session_state [tab_key ][member .get ('userId' )] = all_selected
377+
378+ # Individual member checkboxes
379+ member_cols = st .columns (2 ) # Display in 2 columns for better space usage
380+ for i , member in enumerate (members ):
381+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
382+ with member_cols [i % 2 ]:
383+ is_selected = st .checkbox (
384+ user_name ,
385+ value = st .session_state [tab_key ].get (member .get ('userId' ), True ),
386+ key = f"equal_member_{ member .get ('userId' )} _{ group .get ('_id' )} "
387+ )
388+ st .session_state [tab_key ][member .get ('userId' )] = is_selected
389+
390+ # Get list of selected member IDs
391+ selected_member_ids = [
392+ member_id for member_id , is_selected in st .session_state [tab_key ].items ()
393+ if is_selected
394+ ]
395+
396+ # Display warning if no members are selected
397+ if not selected_member_ids :
398+ st .warning ("Please select at least one member to split the expense." )
399+
400+ # Tab 2: By Percentages
401+ with split_tabs [1 ]:
402+ st .write ("Split by percentages" )
403+
404+ # Initialize percentages
405+ percentage_inputs = {}
406+ total_percentage = 0
407+
408+ for member in members :
409+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
410+ default_value = round (100 / len (members ), 2 ) if len (members ) > 0 else 0
411+ percentage = st .number_input (
412+ f"{ user_name } (%)" ,
413+ min_value = 0.0 ,
414+ max_value = 100.0 ,
415+ value = default_value ,
416+ step = 0.1 ,
417+ format = "%.2f" ,
418+ key = f"percent_{ member .get ('userId' )} "
419+ )
420+ percentage_inputs [member .get ('userId' )] = percentage
421+ total_percentage += percentage
422+
423+ # Show total percentage
424+ if total_percentage != 100 :
425+ st .warning (f"Total percentage: { total_percentage } % (should be 100%)" )
426+ else :
427+ st .success (f"Total percentage: { total_percentage } %" )
428+
429+ # Tab 3: By Shares
430+ with split_tabs [2 ]:
431+ st .write ("Split by shares" )
432+
433+ # Initialize shares
434+ share_inputs = {}
435+ total_shares = 0
436+
437+ for member in members :
438+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
439+ shares = st .number_input (
440+ f"{ user_name } (shares)" ,
441+ min_value = 0 ,
442+ value = 1 ,
443+ step = 1 ,
444+ key = f"shares_{ member .get ('userId' )} "
445+ )
446+ share_inputs [member .get ('userId' )] = shares
447+ total_shares += shares
448+
449+ # Show total shares
450+ if total_shares == 0 :
451+ st .error ("Total shares cannot be 0" )
452+ else :
453+ st .info (f"Total shares: { total_shares } " )
454+
455+ # Show preview of amount per person
456+ st .write ("### Preview:" )
457+ for member in members :
458+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
459+ user_id = member .get ('userId' )
460+ if user_id in share_inputs and total_shares > 0 :
461+ share_percentage = share_inputs [user_id ] / total_shares
462+ amount = expense_amount * share_percentage
463+ st .write (f"{ user_name } : ₹{ amount :.2f} ({ share_percentage * 100 :.2f} %)" )
464+
465+ # Tab 4: By Exact Value
466+ with split_tabs [3 ]:
467+ st .write ("Split by exact amounts" )
468+
469+ # Initialize exact amounts
470+ exact_inputs = {}
471+ total_exact = 0
472+
473+ for member in members :
474+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
475+ exact_amount = st .number_input (
476+ f"{ user_name } (₹)" ,
477+ min_value = 0.0 ,
478+ max_value = float (expense_amount ),
479+ value = round (expense_amount / len (members ), 2 ) if len (members ) > 0 else 0 ,
480+ step = 0.01 ,
481+ format = "%.2f" ,
482+ key = f"exact_{ member .get ('userId' )} "
483+ )
484+ exact_inputs [member .get ('userId' )] = exact_amount
485+ total_exact += exact_amount
486+
487+ # Show total amount
488+ if abs (total_exact - expense_amount ) > 0.01 :
489+ st .warning (f"Total: ₹{ total_exact :.2f} (should be ₹{ expense_amount :.2f} )" )
490+ else :
491+ st .success (f"Total: ₹{ total_exact :.2f} " )
492+
493+ # Active tab is already set by the set_active_tab function in each tab
494+ # This ensures we're using the correctly selected tab for calculations
495+
496+ # Show a summary of the split
497+ st .write ("---" )
498+ st .write ("### Split Summary" )
499+
500+ # Calculate and display split summary based on the active tab
501+ if active_tab == "Equally" :
502+ # Get the list of selected members from the session state
503+ equal_tab_key = f"equal_members_{ group .get ('_id' )} "
504+ selected_member_ids = [
505+ member_id for member_id , is_selected in st .session_state [equal_tab_key ].items ()
506+ if is_selected
507+ ]
508+
509+ if selected_member_ids :
510+ equal_split_amount = round (expense_amount / len (selected_member_ids ), 2 )
511+ for member in members :
512+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
513+ if member .get ('userId' ) in selected_member_ids :
514+ st .write (f"• { user_name } : ₹{ equal_split_amount :.2f} " )
515+ else :
516+ st .write (f"• { user_name } : ₹0.00" )
517+ else :
518+ st .warning ("No members selected for splitting" )
519+
520+ elif active_tab == "By Percentages" :
521+ for member in members :
522+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
523+ percentage = percentage_inputs .get (member .get ('userId' ), 0 )
524+ amount = round (expense_amount * percentage / 100 , 2 )
525+ st .write (f"• { user_name } : ₹{ amount :.2f} ({ percentage } %)" )
526+
527+ elif active_tab == "By Shares" :
528+ if total_shares > 0 :
529+ for member in members :
530+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
531+ shares = share_inputs .get (member .get ('userId' ), 0 )
532+ amount = round (expense_amount * shares / total_shares , 2 ) if shares > 0 else 0
533+ st .write (f"• { user_name } : ₹{ amount :.2f} ({ shares } shares)" )
534+ else :
535+ st .warning ("Total shares must be greater than 0" )
536+
537+ elif active_tab == "By Exact Value" :
538+ for member in members :
539+ user_name = member .get ('user' , {}).get ('name' , 'Unknown User' )
540+ amount = exact_inputs .get (member .get ('userId' ), 0 )
541+ st .write (f"• { user_name } : ₹{ amount :.2f} " )
542+
543+ if abs (total_exact - expense_amount ) > 0.01 :
544+ remaining = expense_amount - total_exact
545+ st .warning (f"Remaining amount to be allocated: ₹{ remaining :.2f} " )
309546
310547 submit_button = st .form_submit_button ("Add Expense" )
311548
312549 if submit_button and expense_title and expense_amount :
313550 try :
314551 headers = {"Authorization" : f"Bearer { st .session_state .access_token } " }
315- # Create equal splits for all members
552+ # Create splits based on selected tab
316553 try :
317554 if not members :
318555 st .error ("No members found in group. Cannot create expense." )
319556 st .stop ()
557+
558+ splits = []
559+ split_type = "equal"
560+
561+ if active_tab == "Equally" :
562+ # Get the list of selected members from the session state
563+ equal_tab_key = f"equal_members_{ group .get ('_id' )} "
564+ selected_member_ids = [
565+ member_id for member_id , is_selected in st .session_state [equal_tab_key ].items ()
566+ if is_selected
567+ ]
568+
569+ if not selected_member_ids :
570+ st .error ("No members selected for splitting the expense." )
571+ st .stop ()
572+
573+ equal_split_amount = round (expense_amount / len (selected_member_ids ), 2 )
574+ splits = [
575+ {
576+ "userId" : member_id ,
577+ "amount" : equal_split_amount ,
578+ "type" : "equal"
579+ }
580+ for member_id in selected_member_ids
581+ ]
582+ split_type = "equal"
320583
321- equal_split_amount = round (expense_amount / len (members ), 2 )
322- splits = [
323- {
324- "userId" : member .get ("userId" ),
325- "amount" : equal_split_amount ,
326- "type" : "equal"
327- }
328- for member in members
329- ]
584+ elif active_tab == "By Percentages" :
585+ if abs (total_percentage - 100 ) > 0.01 :
586+ st .error ("Total percentage must be 100%." )
587+ st .stop ()
588+
589+ splits = [
590+ {
591+ "userId" : member .get ('userId' ),
592+ "amount" : round (expense_amount * percentage_inputs [member .get ('userId' )] / 100 , 2 ),
593+ "type" : "percentage"
594+ }
595+ for member in members if percentage_inputs .get (member .get ('userId' ), 0 ) > 0
596+ ]
597+ split_type = "percentage"
598+
599+ elif active_tab == "By Shares" :
600+ if total_shares == 0 :
601+ st .error ("Total shares cannot be 0." )
602+ st .stop ()
603+
604+ splits = [
605+ {
606+ "userId" : member .get ('userId' ),
607+ "amount" : round (expense_amount * share_inputs [member .get ('userId' )] / total_shares , 2 ),
608+ "type" : "unequal"
609+ }
610+ for member in members if share_inputs .get (member .get ('userId' ), 0 ) > 0
611+ ]
612+ split_type = "unequal"
613+
614+ elif active_tab == "By Exact Value" :
615+ if abs (total_exact - expense_amount ) > 0.01 :
616+ st .error (f"Total amount must be equal to ₹{ expense_amount :.2f} ." )
617+ st .stop ()
618+
619+ splits = [
620+ {
621+ "userId" : member .get ('userId' ),
622+ "amount" : exact_inputs [member .get ('userId' )],
623+ "type" : "exact"
624+ }
625+ for member in members if exact_inputs .get (member .get ('userId' ), 0 ) > 0
626+ ]
627+ split_type = "exact"
628+
629+ # Get the payer's ID from the selected name
630+ payer_id = member_options .get (selected_payer )
330631
331632 expense_data = {
332633 "description" : expense_title + (f" - { expense_description } " if expense_description else "" ),
333634 "amount" : expense_amount ,
334635 "splits" : splits ,
335- "splitType" : "equal" ,
636+ "splitType" : split_type ,
637+ "paidBy" : payer_id , # Add the payer ID
336638 "tags" : []
337639 }
338640
@@ -344,12 +646,12 @@ def fetch_group_expenses(group_id):
344646 st .stop ()
345647
346648 with st .spinner ("Creating expense..." ):
347- response = make_api_request (
348- 'post' ,
349- f"{ API_URL } /groups/{ group .get ('_id' )} /expenses" ,
350- headers = headers ,
351- json_data = expense_data
352- )
649+ response = make_api_request (
650+ 'post' ,
651+ f"{ API_URL } /groups/{ group .get ('_id' )} /expenses" ,
652+ headers = headers ,
653+ json_data = expense_data
654+ )
353655
354656 if response .status_code == 201 :
355657 st .success ("Expense added successfully!" )
0 commit comments