Skip to content

Commit 55281f5

Browse files
authored
Merge pull request #2623 from objectcomputing/feature-2608/adjustments-to-volunteer-tracking
Complete Functionality for Volunteer Tracking
2 parents 5fcb464 + dadcaec commit 55281f5

File tree

3 files changed

+248
-77
lines changed

3 files changed

+248
-77
lines changed

web-ui/src/components/volunteer/VolunteerEvents.jsx

Lines changed: 189 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { resolve } from '../../api/api.js';
1818
import DatePickerField from '../date-picker-field/DatePickerField';
1919
import ConfirmationDialog from '../dialogs/ConfirmationDialog';
20+
import OrganizationDialog from '../dialogs/OrganizationDialog'; // Include OrganizationDialog
2021
import { AppContext } from '../../context/AppContext';
2122
import { selectCsrfToken, selectCurrentUser, selectProfileMap } from '../../context/selectors';
2223
import { formatDate } from '../../helpers/datetime';
@@ -30,11 +31,13 @@ const propTypes = { forceUpdate: PropTypes.func, onlyMe: PropTypes.bool };
3031
const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
3132
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
3233
const [eventDialogOpen, setEventDialogOpen] = useState(false);
34+
const [organizationDialogOpen, setOrganizationDialogOpen] = useState(false); // Organization dialog state
3335
const [events, setEvents] = useState([]);
3436
const [organizationMap, setOrganizationMap] = useState({});
3537
const [relationshipMap, setRelationshipMap] = useState({});
3638
const [relationships, setRelationships] = useState([]);
3739
const [selectedEvent, setSelectedEvent] = useState(null);
40+
const [newOrganization, setNewOrganization] = useState({ name: '', description: '', website: '' });
3841
const [sortAscending, setSortAscending] = useState(true);
3942
const [sortColumn, setSortColumn] = useState('Relationship');
4043

@@ -63,7 +66,6 @@ const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
6366

6467
let events = res.payload.data;
6568
if (onlyMe) {
66-
// Only keep the events for my relationships.
6769
events = events.filter(e => Boolean(relationshipMap[e.relationshipId]));
6870
}
6971
events.sort((event1, event2) => event1.eventDate.localeCompare(event2.eventDate));
@@ -168,40 +170,113 @@ const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
168170
[relationshipMap]
169171
);
170172

173+
const openCreateOrganizationDialog = useCallback(() => {
174+
setNewOrganization({ name: '', description: '', website: '' });
175+
setOrganizationDialogOpen(true);
176+
}, []);
177+
178+
const saveOrganizationAndRelationship = useCallback(async () => {
179+
const { name, description, website } = newOrganization;
180+
if (!name || !description) {
181+
console.error('Missing organization name or description');
182+
return;
183+
}
184+
185+
try {
186+
// Step 1: Create the organization
187+
const res = await resolve({
188+
method: 'POST',
189+
url: organizationBaseUrl,
190+
headers: {
191+
'X-CSRF-Header': csrf,
192+
Accept: 'application/json',
193+
'Content-Type': 'application/json;charset=UTF-8'
194+
},
195+
data: { name, description, website }
196+
});
197+
198+
if (res.error) {
199+
console.error('Error creating organization', res.error);
200+
return;
201+
}
202+
203+
const createdOrg = res.payload.data;
204+
205+
// Step 2: Create a relationship between the current user and the newly created organization
206+
const relationshipRes = await resolve({
207+
method: 'POST',
208+
url: relationshipBaseUrl, // Ensure the correct URL is used
209+
headers: {
210+
'X-CSRF-Header': csrf,
211+
Accept: 'application/json',
212+
'Content-Type': 'application/json;charset=UTF-8'
213+
},
214+
data: {
215+
memberId: currentUser.id,
216+
organizationId: createdOrg.id,
217+
startDate: formatDate(new Date()), // Set the start date as the current date
218+
endDate: null // Leave endDate as null for an active relationship
219+
}
220+
});
221+
222+
if (relationshipRes.error) {
223+
console.error('Error creating relationship', relationshipRes.error);
224+
return;
225+
}
226+
227+
const createdRelationship = relationshipRes.payload.data;
228+
229+
// Step 3: Update the organization and relationship maps
230+
setOrganizationMap(prev => ({ ...prev, [createdOrg.id]: createdOrg }));
231+
setRelationshipMap(prev => ({ ...prev, [createdRelationship.id]: createdRelationship }));
232+
233+
// Step 4: Update selectedEvent with the new relationship
234+
setSelectedEvent({
235+
...selectedEvent,
236+
relationshipId: createdRelationship.id // Set the new relationship ID
237+
});
238+
239+
// Step 5: Close organization dialog and open event dialog
240+
setOrganizationDialogOpen(false);
241+
setEventDialogOpen(true);
242+
243+
} catch (error) {
244+
console.error('Failed to create organization and relationship', error);
245+
}
246+
}, [newOrganization, csrf, currentUser.id, selectedEvent]);
247+
171248
const eventDialog = useCallback(
172249
() => (
173250
<Dialog classes={{ root: 'volunteer-dialog' }} open={eventDialogOpen} onClose={cancelEvent}>
174251
<DialogTitle>{selectedEvent?.id ? 'Edit' : 'Add'} Event</DialogTitle>
175252
<DialogContent>
176-
<Autocomplete
177-
disableClearable
178-
getOptionLabel={relationshipName}
179-
isOptionEqualToValue={(option, value) => option.id === value.id}
180-
onChange={(event, relationship) => {
253+
<Autocomplete
254+
disableClearable
255+
getOptionLabel={(option) =>
256+
option === 'new' ? 'Create a New Organization' : (relationshipMap[option]?.organizationId && organizationMap[relationshipMap[option].organizationId]?.name) || 'Unknown'
257+
}
258+
options={['new', ...relationships.filter((rel) => !rel.endDate).map((rel) => rel.id)]} // Use relationship IDs
259+
onChange={(event, value) => {
260+
if (value === 'new') {
261+
openCreateOrganizationDialog(); // Open the organization creation dialog
262+
} else {
181263
setSelectedEvent({
182264
...selectedEvent,
183-
relationshipId: relationship.id
265+
relationshipId: value // Set relationshipId correctly
184266
});
185-
}}
186-
options={relationships}
187-
renderInput={params => (
188-
<TextField {...params} className="fullWidth" label={onlyMe ? 'Organization' : 'Volunteer Relationship'} />
189-
)}
190-
value={selectedEvent?.relationshipId ? relationshipMap[selectedEvent.relationshipId] : null}
191-
/>
267+
}
268+
}}
269+
renderInput={(params) => <TextField {...params} className="fullWidth" label="Organization" />}
270+
value={selectedEvent?.relationshipId || ''} // Bind to the correct relationship ID
271+
/>
192272
<DatePickerField
193273
date={getDate(selectedEvent?.eventDate)}
194274
label="Date"
195-
setDate={date => {
196-
setSelectedEvent({
197-
...selectedEvent,
198-
eventDate: formatDate(date)
199-
});
200-
}}
275+
setDate={(date) => setSelectedEvent({ ...selectedEvent, eventDate: formatDate(date) })}
201276
/>
202277
<TextField
203-
label="Hours"
204-
onChange={e => {
278+
label="Hours You Volunteered"
279+
onChange={(e) => {
205280
const hours = Number(e.target.value);
206281
if (hours >= 0) setSelectedEvent({ ...selectedEvent, hours });
207282
}}
@@ -210,29 +285,39 @@ const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
210285
/>
211286
<TextField
212287
label="Notes"
213-
onChange={e => setSelectedEvent({ ...selectedEvent, notes: e.target.value })}
288+
onChange={(e) => setSelectedEvent({ ...selectedEvent, notes: e.target.value })}
214289
value={selectedEvent?.notes ?? ''}
215290
/>
216291
</DialogContent>
217292
<DialogActions>
218293
<Button onClick={cancelEvent}>Cancel</Button>
219-
<Button disabled={!validEvent()} onClick={saveEvent}>
220-
Save
221-
</Button>
294+
<Button disabled={!validEvent()} onClick={saveEvent}>Save</Button>
222295
</DialogActions>
223296
</Dialog>
224297
),
225-
[eventDialogOpen, selectedEvent]
298+
[eventDialogOpen, selectedEvent, relationships, relationshipMap]
226299
);
227300

228301
const eventRow = useCallback(
229-
event => {
302+
(event) => {
230303
const relationship = relationshipMap[event.relationshipId];
231-
const member = profileMap[relationship.memberId];
232-
const org = organizationMap[relationship.organizationId];
304+
305+
if (!relationship) {
306+
console.error(`Relationship ${event.relationshipId} not found in relationshipMap.`);
307+
return null;
308+
}
309+
310+
const member = profileMap[relationship?.memberId];
311+
const org = organizationMap[relationship?.organizationId];
312+
313+
if (!member || !org) {
314+
console.error(`Member or Organization not found for relationship ${event.relationshipId}`);
315+
return null;
316+
}
317+
233318
return (
234319
<tr key={event.id}>
235-
<td>{onlyMe ? org.name : member.name + ' - ' + org.name}</td>
320+
<td>{onlyMe ? org.name : `${member.name} - ${org.name}`}</td>
236321
<td>{event.eventDate}</td>
237322
<td>{event.hours}</td>
238323
<td>{event.notes}</td>
@@ -317,33 +402,71 @@ const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
317402
};
318403

319404
const saveEvent = useCallback(async () => {
320-
const { id } = selectedEvent;
321-
const res = await resolve({
322-
method: id ? 'PUT' : 'POST',
323-
url: id ? `${eventBaseUrl}/${id}` : eventBaseUrl,
324-
headers: {
325-
'X-CSRF-Header': csrf,
326-
Accept: 'application/json',
327-
'Content-Type': 'application/json;charset=UTF-8'
328-
},
329-
data: selectedEvent
330-
});
331-
if (res.error) return;
332-
333-
const newRel = res.payload.data;
334-
335-
if (id) {
336-
const index = events.findIndex(rel => rel.id === id);
337-
events[index] = newRel;
338-
} else {
339-
events.push(newRel);
405+
const { id, relationshipId, eventDate, hours, notes } = selectedEvent;
406+
407+
// Check if relationshipId is valid
408+
if (!relationshipId) {
409+
console.error('No relationship selected for the event.');
410+
return;
340411
}
341-
sortEvents(events);
342-
setEvents(events);
343-
344-
setSelectedEvent(null);
345-
setEventDialogOpen(false);
346-
}, [relationshipMap, selectedEvent]);
412+
413+
// Check that all required fields are filled in
414+
if (!eventDate || hours <= 0) {
415+
console.error("Missing required fields: date or hours.");
416+
return;
417+
}
418+
419+
try {
420+
// Ensure the relationship exists
421+
const relationshipCheck = await resolve({
422+
method: 'GET',
423+
url: `${relationshipBaseUrl}/${relationshipId}`,
424+
headers: {
425+
'X-CSRF-Header': csrf,
426+
Accept: 'application/json',
427+
'Content-Type': 'application/json;charset=UTF-8'
428+
}
429+
});
430+
431+
if (relationshipCheck.error || !relationshipCheck.payload) {
432+
console.error(`Relationship ${relationshipId} doesn't exist.`);
433+
return;
434+
}
435+
436+
// Proceed with saving the event
437+
const res = await resolve({
438+
method: id ? 'PUT' : 'POST',
439+
url: id ? `${eventBaseUrl}/${id}` : eventBaseUrl,
440+
headers: {
441+
'X-CSRF-Header': csrf,
442+
Accept: 'application/json',
443+
'Content-Type': 'application/json;charset=UTF-8'
444+
},
445+
data: { relationshipId, eventDate, hours, notes }
446+
});
447+
448+
if (res.error) {
449+
console.error('Error saving event:', res.error);
450+
return;
451+
}
452+
453+
// Update event list and close dialog
454+
const newEvent = res.payload.data;
455+
if (id) {
456+
const index = events.findIndex(e => e.id === id);
457+
events[index] = newEvent;
458+
} else {
459+
events.push(newEvent);
460+
}
461+
462+
sortEvents(events);
463+
setEvents(events);
464+
setSelectedEvent(null);
465+
setEventDialogOpen(false); // Close dialog after save
466+
} catch (error) {
467+
console.error('Failed to save event:', error);
468+
}
469+
}, [selectedEvent, events, csrf]);
347470

348471
const sortEvents = useCallback(
349472
events => {
@@ -391,6 +514,15 @@ const VolunteerEvents = ({ forceUpdate = () => {}, onlyMe = false }) => {
391514

392515
{eventDialog()}
393516

517+
{/* Dialog for creating a new organization */}
518+
<OrganizationDialog
519+
open={organizationDialogOpen}
520+
onClose={() => setOrganizationDialogOpen(false)}
521+
onSave={saveOrganizationAndRelationship}
522+
organization={newOrganization}
523+
setOrganization={setNewOrganization}
524+
/>
525+
394526
<ConfirmationDialog
395527
open={confirmDeleteOpen}
396528
onYes={() => deleteEvent(selectedEvent)}

0 commit comments

Comments
 (0)