1- import React , { useEffect , useMemo , useState } from 'react' ;
1+ import React , { useEffect , useState } from 'react' ;
22import { Button , Modal , ModalHeader , ModalBody , ModalFooter , Alert , Input } from 'reactstrap' ;
33import AddProjectsAutoComplete from './AddProjectsAutoComplete' ;
44import { boxStyle , boxStyleDark } from '~/styles' ;
@@ -7,50 +7,22 @@ import { toast } from 'react-toastify';
77import axios from 'axios' ;
88import { ENDPOINTS } from '~/utils/URL' ;
99import { useDispatch } from 'react-redux' ;
10-
1110import { assignProject } from '~/actions/projectMembers' ;
12-
13- const createUserProjectMembership = async ( userId , projectId ) => {
14- // If your ENDPOINTS key is named differently, use that one.
15- // Typical: POST /userProjects body: { userId, projectId, isActive }
16- return axios . post ( ENDPOINTS . USER_PROJECTS , {
17- userId,
18- projectId,
19- isActive : true ,
20- } ) ;
21- } ;
22- /*const AddProjectPopup = React.memo(function AddProjectPopup(props) {
11+
12+ // eslint-disable-next-line react/display-name
13+ const AddProjectPopup = React . memo ( ( props ) => {
2314 const {
2415 open,
2516 onClose,
26- userId,
2717 darkMode,
28- // projects already in DB (for autocomplete / create-new check)
2918 projects = [ ] ,
30- // projects already assigned to this user (to block duplicates)
3119 userProjects = [ ] ,
32- // optional: parent callback to update its local table immediately
33- onSelectAssignProject, // (project) => void
20+ userId ,
21+ onSelectAssignProject,
3422 } = props ;
35- const safeProjects = Array.isArray(projects) ? projects : [];
36- const safeUserProjects = Array.isArray(userProjects) ? userProjects : [];
37-
38- // set data from props
39- useEffect(() => {
40- setAllProjects(safeProjects);
41- const categories = Array.from(
42- new Set(safeProjects.map(p => p?.category).filter(Boolean))
43- );
44- setCategoryOptions(categories.length ? categories : ['Unspecified']);
45- }, [projects]);*/
46-
47- // eslint-disable-next-line react/display-name
48- const AddProjectPopup = React . memo ( props => {
49- const { darkMode, projects = [ ] , onClose } = props ;
5023
5124 const dispatch = useDispatch ( ) ;
5225
53- // ---------- local state ----------
5426 const [ selectedProject , setSelectedProject ] = useState ( null ) ;
5527 const [ isValidProject , setIsValidProject ] = useState ( true ) ;
5628 const [ showDoesNotExistAlert , setShowDoesNotExistAlert ] = useState ( false ) ;
@@ -60,30 +32,27 @@ const AddProjectPopup = React.memo(props => {
6032 const [ searchText , setSearchText ] = useState ( '' ) ;
6133 const [ allProjects , setAllProjects ] = useState ( [ ] ) ;
6234
63- // set data from props
6435 useEffect ( ( ) => {
6536 setAllProjects ( projects || [ ] ) ;
66- const categories = Array . from ( new Set ( ( projects || [ ] ) . map ( p => p . category ) . filter ( Boolean ) ) ) ;
37+ const categories = Array . from ( new Set ( ( projects || [ ] ) . map ( ( p ) => p . category ) . filter ( Boolean ) ) ) ;
6738 setCategoryOptions ( categories . length ? categories : [ 'Unspecified' ] ) ;
6839 } , [ projects ] ) ;
6940
70- // reset validation each time the modal opens
7141 useEffect ( ( ) => {
7242 if ( open ) {
7343 setIsValidProject ( true ) ;
7444 setShowDoesNotExistAlert ( false ) ;
7545 setCreatingNew ( false ) ;
7646 setSelectedProject ( null ) ;
7747 setSearchText ( '' ) ;
48+ setNewProjectCategory ( 'Unspecified' ) ;
7849 }
7950 } , [ open ] ) ;
8051
81-
52+ const format = ( s ) => ( s || '' ) . toLowerCase ( ) . trim ( ) ;
8253
83-
84- // ---------- helpers ----------
85- const format = s => ( s || '' ) . toLowerCase ( ) . trim ( ) ;
86- const projectByName = name => ( allProjects || [ ] ) . find ( p => format ( p . projectName ) === format ( name ) ) ;
54+ const projectByName = ( name ) =>
55+ ( allProjects || [ ] ) . find ( ( p ) => format ( p . projectName ) === format ( name ) ) ;
8756
8857 const handleSelectProject = ( project ) => {
8958 setSelectedProject ( project ) ;
@@ -95,39 +64,42 @@ const AddProjectPopup = React.memo(props => {
9564 onClose ?. ( ) ;
9665 setCreatingNew ( false ) ;
9766 setShowDoesNotExistAlert ( false ) ;
67+ setSelectedProject ( null ) ;
68+ setSearchText ( '' ) ;
69+ setIsValidProject ( true ) ;
70+ } ;
71+
72+ const handleConfirm = async ( ) => {
73+ if ( ! selectedProject ) {
74+ setIsValidProject ( false ) ;
75+ toast . error ( 'Please select a project from the list.' ) ;
76+ return ;
77+ }
78+
79+ if ( userProjects . some ( ( p ) => p ?. _id === selectedProject . _id ) ) {
80+ setIsValidProject ( false ) ;
81+ toast . error ( 'Great idea, but they already have that one! Pick another!' ) ;
82+ return ;
83+ }
84+
85+ try {
86+ await dispatch ( assignProject ( selectedProject . _id , userId , 'Assign' ) ) ;
87+ onSelectAssignProject ?. ( selectedProject ) ;
88+ onClose ?. ( ) ;
89+ } catch ( e ) {
90+ // eslint-disable-next-line no-console
91+ console . error ( 'Error assigning project:' , e ) ;
92+ toast . error ( 'Failed to assign project. Please try again.' ) ;
93+ }
9894 } ;
9995
100- // ---------- Confirm existing project ----------
101- const handleConfirm = async ( ) => {
102- if ( ! selectedProject ) {
103- setIsValidProject ( false ) ;
104- toast . error ( 'Please select a project from the list.' ) ;
105- return ;
106- }
107- if ( ( props . userProjects || [ ] ) . some ( p => p ?. _id === selectedProject . _id ) ) {
108- setIsValidProject ( false ) ;
109- toast . error ( 'Great idea, but they already have that one! Pick another!' ) ;
110- return ;
111- }
112- try {
113- await dispatch ( assignProject ( selectedProject . _id , props . userId , 'Assign' ) ) ;
114- // ✅ Make sure we're passing the complete project object
115- props . onSelectAssignProject ?. ( selectedProject ) ;
116- props . onClose ?. ( ) ;
117- } catch ( e ) {
118- // eslint-disable-next-line no-console
119- console . error ( 'Error Assigning Project:' , e ) ;
120- toast . error ( 'Failed to assign project. Please try again.' ) ;
121- }
122- } ;
123-
124- // ---------- Create new project, then confirm ----------
12596 const handleCreateNew = async ( ) => {
12697 if ( ! searchText . trim ( ) ) {
12798 setIsValidProject ( false ) ;
12899 setSelectedProject ( null ) ;
129100 return ;
130101 }
102+
131103 if ( projectByName ( searchText ) ) {
132104 toast . error ( 'This project already exists.' ) ;
133105 return ;
@@ -142,9 +114,9 @@ const AddProjectPopup = React.memo(props => {
142114 try {
143115 await axios . post ( ENDPOINTS . PROJECTS , newProject ) ;
144116 const res = await axios . get ( ENDPOINTS . PROJECTS ) ;
117+
145118 const created =
146- ( res . data || [ ] ) . find ( p => format ( p . projectName ) === format ( searchText ) ) ||
147- null ;
119+ ( res . data || [ ] ) . find ( ( p ) => format ( p . projectName ) === format ( searchText ) ) || null ;
148120
149121 if ( ! created ) {
150122 toast . success ( 'Project created successfully, but it was not auto-selected.' ) ;
@@ -153,80 +125,141 @@ const AddProjectPopup = React.memo(props => {
153125 return ;
154126 }
155127
156- // select the newly created project so pressing Confirm assigns it
157128 setSelectedProject ( created ) ;
158129 setAllProjects ( res . data || [ ] ) ;
159130 setCreatingNew ( false ) ;
160131 toast . success ( 'Project created successfully' ) ;
161- } catch {
132+ } catch ( e ) {
162133 toast . error ( 'Project creation failed' ) ;
163134 }
164135 } ;
165136
166137 return (
167138 < Modal
168- isOpen = { props . open }
169- toggle = { onClose }
170- // eslint-disable-next-line jsx-a11y/no-autofocus
171- autoFocus = { false }
139+ isOpen = { open }
140+ toggle = { close }
141+ centered
142+ size = "lg"
172143 className = { darkMode ? 'text-light dark-mode' : '' }
173144 >
174- < ModalHeader className = { darkMode ? 'bg-space-cadet' : '' } toggle = { onClose } >
175- { creatingNew ? 'Create' : ' Add' } Project{ ' '}
145+ < ModalHeader className = { darkMode ? 'bg-space-cadet text-light ' : '' } toggle = { close } >
146+ { creatingNew ? 'Create Project ' : 'Add Project' }
176147 </ ModalHeader >
177148
178- < ModalBody className = { darkMode ? 'bg-yinmn-blue' : '' } style = { { textAlign : 'center' } } >
179- < div className = "input-group-prepend" style = { { marginBottom : 10 } } >
180- < AddProjectsAutoComplete
181- projectsData = { allProjects }
182- onDropDownSelect = { handleSelectProject }
183- selectedProject = { selectedProject }
184- setIsOpenDropdown = { setCreatingNew }
185- searchText = { searchText }
186- onInputChange = { setSearchText }
187- isSetUserIsNotSelectedAutoComplete = { setShowDoesNotExistAlert }
188- formatText = { ( s ) => format ( s ) . replace ( / \s + / g, '' ) }
189- />
190- < Button
191- color = { creatingNew ? 'success' : 'primary' }
192- style = { darkMode ? { } : { ...boxStyle , marginLeft : 5 } }
193- onClick = { creatingNew ? handleCreateNew : handleConfirm }
149+ < ModalBody
150+ className = { darkMode ? 'bg-yinmn-blue text-light' : '' }
151+ style = { { padding : '1.5rem' } }
152+ >
153+ < div
154+ style = { {
155+ display : 'flex' ,
156+ flexDirection : 'column' ,
157+ gap : '1rem' ,
158+ width : '100%' ,
159+ } }
160+ >
161+ < div
162+ style = { {
163+ display : 'flex' ,
164+ alignItems : 'stretch' ,
165+ gap : '0.75rem' ,
166+ width : '100%' ,
167+ flexWrap : 'wrap' ,
168+ } }
194169 >
195- { creatingNew ? 'Create' : 'Confirm' }
196- </ Button >
197- </ div >
198-
199- { creatingNew && (
200- < div className = "input-group-prepend" style = { { marginBottom : 10 , display : 'flex' , gap : 10 } } >
201- < Input type = "select" value = { newProjectCategory } onChange = { e => setNewProjectCategory ( e . target . value ) } >
202- { categoryOptions . map ( opt => (
203- < option key = { opt } > { opt } </ option >
204- ) ) }
205- </ Input >
170+ < div
171+ style = { {
172+ flex : '1 1 420px' ,
173+ minWidth : '260px' ,
174+ } }
175+ >
176+ < AddProjectsAutoComplete
177+ projectsData = { allProjects }
178+ onDropDownSelect = { handleSelectProject }
179+ selectedProject = { selectedProject }
180+ setIsOpenDropdown = { setCreatingNew }
181+ searchText = { searchText }
182+ onInputChange = { setSearchText }
183+ isSetUserIsNotSelectedAutoComplete = { setShowDoesNotExistAlert }
184+ formatText = { ( s ) => format ( s ) . replace ( / \s + / g, '' ) }
185+ />
186+ </ div >
206187
207188 < Button
208- color = "danger"
209- onClick = { ( ) => {
210- setCreatingNew ( false ) ;
211- setShowDoesNotExistAlert ( false ) ;
212- setSearchText ( '' ) ;
189+ color = { creatingNew ? 'success' : 'primary' }
190+ style = { {
191+ ...( darkMode ? { } : boxStyle ) ,
192+ minWidth : '120px' ,
193+ height : '38px' ,
194+ alignSelf : 'stretch' ,
213195 } }
214- style = { { width : '100%' } }
196+ onClick = { creatingNew ? handleCreateNew : handleConfirm }
215197 >
216- Cancel project creation
198+ { creatingNew ? 'Create' : 'Confirm' }
217199 </ Button >
218200 </ div >
219- ) }
220-
221- { ! isValidProject && selectedProject && (
222- < Alert color = "danger" > Great idea, but they already have that one! Pick another!</ Alert >
223- ) }
224- { ! isValidProject && ! selectedProject && (
225- < Alert color = "danger" >
226- Hey, You need to { creatingNew ? 'write the name of' : 'pick' } a project first!
227- </ Alert >
228- ) }
229- { showDoesNotExistAlert && < Alert color = "danger" > This project does not exist.</ Alert > }
201+
202+ { creatingNew && (
203+ < div
204+ style = { {
205+ display : 'flex' ,
206+ gap : '0.75rem' ,
207+ width : '100%' ,
208+ flexWrap : 'wrap' ,
209+ alignItems : 'stretch' ,
210+ } }
211+ >
212+ < Input
213+ type = "select"
214+ value = { newProjectCategory }
215+ onChange = { ( e ) => setNewProjectCategory ( e . target . value ) }
216+ className = { darkMode ? 'bg-darkmode-liblack border-0 text-light' : '' }
217+ style = { {
218+ flex : '1 1 240px' ,
219+ minWidth : '220px' ,
220+ } }
221+ >
222+ { categoryOptions . map ( ( opt ) => (
223+ < option key = { opt } > { opt } </ option >
224+ ) ) }
225+ </ Input >
226+
227+ < Button
228+ color = "danger"
229+ onClick = { ( ) => {
230+ setCreatingNew ( false ) ;
231+ setShowDoesNotExistAlert ( false ) ;
232+ setSearchText ( '' ) ;
233+ setSelectedProject ( null ) ;
234+ setIsValidProject ( true ) ;
235+ } }
236+ style = { {
237+ minWidth : '180px' ,
238+ } }
239+ >
240+ Cancel project creation
241+ </ Button >
242+ </ div >
243+ ) }
244+
245+ { ! isValidProject && selectedProject && (
246+ < Alert color = "danger" style = { { marginBottom : 0 } } >
247+ Great idea, but they already have that one! Pick another!
248+ </ Alert >
249+ ) }
250+
251+ { ! isValidProject && ! selectedProject && (
252+ < Alert color = "danger" style = { { marginBottom : 0 } } >
253+ Hey, You need to { creatingNew ? 'write the name of' : 'pick' } a project first!
254+ </ Alert >
255+ ) }
256+
257+ { showDoesNotExistAlert && (
258+ < Alert color = "danger" style = { { marginBottom : 0 } } >
259+ This project does not exist.
260+ </ Alert >
261+ ) }
262+ </ div >
230263 </ ModalBody >
231264
232265 < ModalFooter className = { darkMode ? 'bg-yinmn-blue' : '' } >
@@ -238,4 +271,4 @@ const AddProjectPopup = React.memo(props => {
238271 ) ;
239272} ) ;
240273
241- export default AddProjectPopup ;
274+ export default AddProjectPopup ;
0 commit comments