@@ -20,6 +20,7 @@ import {
2020 DialogTitle ,
2121} from "@/components/ui/dialog" ;
2222import { MemberSelection } from "@/components/ui/MemberSelection" ;
23+ import { SelectMemberModal } from "@/components/ui/SelectMemberModal" ;
2324import { Skeleton } from "@/components/ui/skeleton/skeleton" ;
2425import { useModal as useGlobalModal } from "@/hooks/use-modal" ;
2526import { useModal } from "@/hooks/use-modal-projects" ;
@@ -80,6 +81,10 @@ const ProjectEditModal = () => {
8081 // ─────────────────────────────────────────────────────────────────────────
8182 const [ selectedMembers , setSelectedMembers ] = useState < Codev [ ] > ( [ ] ) ;
8283
84+ // Modal states for member selection
85+ const [ teamLeaderModalOpen , setTeamLeaderModalOpen ] = useState ( false ) ;
86+ const [ subLeadModalOpen , setSubLeadModalOpen ] = useState ( false ) ;
87+
8388 // Image states
8489 const [ projectImage , setProjectImage ] = useState < string | null > ( null ) ;
8590 const [ openImageCropper , setOpenImageCropper ] = useState ( false ) ;
@@ -669,91 +674,71 @@ const ProjectEditModal = () => {
669674 < label className = "text-xs font-semibold text-gray-700 dark:text-gray-300" >
670675 Team Leader *
671676 </ label >
672- < CustomSelect
673- options = { enhancedUserOptions . filter (
674- ( user ) => ! selectedMembers . find ( ( member ) => member . id === user . value ) ,
677+ < button
678+ type = "button"
679+ onClick = { ( ) => setTeamLeaderModalOpen ( true ) }
680+ className = "w-full h-11 px-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-left flex items-center gap-3 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
681+ >
682+ { currentTeamLeader ? (
683+ < >
684+ < img
685+ src = { currentTeamLeader . image_url || "https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg" }
686+ alt = { `${ currentTeamLeader . first_name } ${ currentTeamLeader . last_name } ` }
687+ className = "w-8 h-8 rounded-full object-cover"
688+ />
689+ < div className = "flex-1 min-w-0" >
690+ < div className = "text-sm font-medium text-gray-900 dark:text-white truncate" >
691+ { currentTeamLeader . first_name } { currentTeamLeader . last_name }
692+ </ div >
693+ { currentTeamLeader . display_position && (
694+ < div className = "text-xs text-gray-500 dark:text-gray-400 truncate" >
695+ { currentTeamLeader . display_position }
696+ </ div >
697+ ) }
698+ </ div >
699+ </ >
700+ ) : (
701+ < span className = "text-gray-400" > Select Team Leader</ span >
675702 ) }
676- value = { currentTeamLeader ?. id || "" }
677- onChange = { ( value ) => {
678- const leader = users . find ( ( u ) => u . id === value ) ;
679- if ( leader ) setCurrentTeamLeader ( leader ) ;
680- } }
681- placeholder = "Select Team Leader"
682- disabled = { isUsersLoading }
683- searchable
684- />
703+ </ button >
685704 </ div >
686705
687- { /* ── CBP-116: Sublead selector ─────────────────────────────────────
688- Optional — no asterisk, no toast validation required.
689- Excluded from members list and excluded from team leader list.
690- Clearing the select (empty string value) sets sublead to null.
691- ──────────────────────────────────────────────────────────────────── */ }
706+ { /* Sub Lead */ }
692707 < div className = "space-y-1" >
693708 < label className = "text-xs font-semibold text-gray-700 dark:text-gray-300" >
694709 Sub Lead{ " " }
695710 < span className = "font-normal text-gray-500 dark:text-gray-400" >
696711 (Optional — acts as team lead when lead is unavailable)
697712 </ span >
698713 </ label >
699- < CustomSelect
700- options = { [
701- // "None" option — value must not be empty string (Radix UI Select.Item constraint)
702- { id : "none" , value : "none" , label : "No sublead assigned" , subLabel : "" } ,
703- // Only show members of this project, excluding the team leader.
704- // Source: fullProjectData.project_members (bypasses RLS filtering
705- // that drops some users from getProjectCodevs results).
706- ...( fullProjectData ?. project_members ?? [ ] )
707- . filter (
708- ( pm : any ) =>
709- pm . codev &&
710- pm . codev_id !== currentTeamLeader ?. id &&
711- pm . role !== "team_leader" ,
712- )
713- . map ( ( pm : any ) => ( {
714- id : pm . codev_id ,
715- value : pm . codev_id ,
716- label : `${ pm . codev . first_name } ${ pm . codev . last_name } ` ,
717- subLabel : pm . codev . display_position || "" ,
718- imageUrl : pm . codev . image_url || null ,
719- } ) ) ,
720- ] }
721- value = { currentSubLead ?. id || "none" }
722- onChange = { ( value ) => {
723- if ( ! value || value === "none" ) {
724- // User selected "None" — clear the sublead
725- setCurrentSubLead ( null ) ;
726- return ;
727- }
728- // Find in users first, fall back to project_members embedded data
729- const subLeadFromUsers = users . find ( ( u ) => u . id === value ) ;
730- if ( subLeadFromUsers ) {
731- setCurrentSubLead ( subLeadFromUsers ) ;
732- return ;
733- }
734- // Fallback: construct from fullProjectData (handles RLS-filtered users)
735- const subLeadMember = fullProjectData ?. project_members ?. find (
736- ( pm : any ) => pm . codev_id === value ,
737- ) ;
738- if ( subLeadMember ?. codev ) {
739- setCurrentSubLead ( {
740- id : subLeadMember . codev_id ,
741- first_name : subLeadMember . codev . first_name ,
742- last_name : subLeadMember . codev . last_name ,
743- image_url : subLeadMember . codev . image_url || null ,
744- display_position : subLeadMember . codev . display_position || null ,
745- email_address : subLeadMember . codev . email_address || "" ,
746- positions : [ ] ,
747- tech_stacks : [ ] ,
748- } as unknown as Codev ) ;
749- }
750- } }
751- placeholder = "Select Sub Lead"
752- disabled = { isUsersLoading }
753- searchable
754- />
714+ < button
715+ type = "button"
716+ onClick = { ( ) => setSubLeadModalOpen ( true ) }
717+ className = "w-full h-11 px-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-left flex items-center gap-3 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
718+ >
719+ { currentSubLead ? (
720+ < >
721+ < img
722+ src = { currentSubLead . image_url || "https://codebility-cdn.pages.dev/assets/images/default-avatar-200x200.jpg" }
723+ alt = { `${ currentSubLead . first_name } ${ currentSubLead . last_name } ` }
724+ className = "w-8 h-8 rounded-full object-cover"
725+ />
726+ < div className = "flex-1 min-w-0" >
727+ < div className = "text-sm font-medium text-gray-900 dark:text-white truncate" >
728+ { currentSubLead . first_name } { currentSubLead . last_name }
729+ </ div >
730+ { currentSubLead . display_position && (
731+ < div className = "text-xs text-gray-500 dark:text-gray-400 truncate" >
732+ { currentSubLead . display_position }
733+ </ div >
734+ ) }
735+ </ div >
736+ </ >
737+ ) : (
738+ < span className = "text-gray-400" > No sublead assigned</ span >
739+ ) }
740+ </ button >
755741 </ div >
756- { /* ─────────────────────────────────────────────────────────────── */ }
757742
758743 { /* Team Members */ }
759744 < div className = "space-y-1" >
@@ -1005,6 +990,30 @@ const ProjectEditModal = () => {
1005990 </ div >
1006991 </ DialogFooter >
1007992 </ DialogContent >
993+
994+ { /* Team Leader Selection Modal */ }
995+ < SelectMemberModal
996+ isOpen = { teamLeaderModalOpen }
997+ onClose = { ( ) => setTeamLeaderModalOpen ( false ) }
998+ onSelect = { ( member ) => setCurrentTeamLeader ( member ) }
999+ users = { users }
1000+ selectedMember = { currentTeamLeader }
1001+ title = "Select Team Leader"
1002+ mode = "single"
1003+ excludeUserIds = { selectedMembers . map ( m => m . id ) }
1004+ />
1005+
1006+ { /* Sub Lead Selection Modal */ }
1007+ < SelectMemberModal
1008+ isOpen = { subLeadModalOpen }
1009+ onClose = { ( ) => setSubLeadModalOpen ( false ) }
1010+ onSelect = { ( member ) => setCurrentSubLead ( member ) }
1011+ users = { users }
1012+ selectedMember = { currentSubLead }
1013+ title = "Select Sub Lead"
1014+ mode = "optional-single"
1015+ excludeUserIds = { [ currentTeamLeader ?. id || '' , ...selectedMembers . map ( m => m . id ) ] . filter ( Boolean ) }
1016+ />
10081017 </ Dialog >
10091018 ) ;
10101019} ;
0 commit comments