Merge pull request #1 from Ryderjj89/dev

Added other items to display on the view rsvps & manage rsvps pages
This commit is contained in:
Joshua Ryder
2025-05-04 18:10:07 -04:00
committed by GitHub
4 changed files with 64 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
services: services:
app: app:
container_name: rsvp_manager container_name: rsvp_manager-dev
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile

View File

@@ -79,6 +79,7 @@ const EventAdmin: React.FC = () => {
const [rsvps, setRsvps] = useState<RSVP[]>([]); const [rsvps, setRsvps] = useState<RSVP[]>([]);
const [neededItems, setNeededItems] = useState<string[]>([]); const [neededItems, setNeededItems] = useState<string[]>([]);
const [claimedItems, setClaimedItems] = useState<string[]>([]); const [claimedItems, setClaimedItems] = useState<string[]>([]);
const [otherItems, setOtherItems] = useState<string[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -149,6 +150,7 @@ const EventAdmin: React.FC = () => {
// Get all claimed items from existing RSVPs // Get all claimed items from existing RSVPs
const claimed = new Set<string>(); const claimed = new Set<string>();
const otherItemsSet = new Set<string>();
const processedRsvps = rsvpsResponse.data.map((rsvp: RSVP) => { const processedRsvps = rsvpsResponse.data.map((rsvp: RSVP) => {
let itemsBringing: string[] = []; let itemsBringing: string[] = [];
try { try {
@@ -169,6 +171,11 @@ const EventAdmin: React.FC = () => {
console.error('Error processing items for RSVP:', e); console.error('Error processing items for RSVP:', e);
} }
// Add non-empty other_items to set
if (typeof rsvp.other_items === 'string' && rsvp.other_items.trim() !== '') {
otherItemsSet.add(rsvp.other_items.trim());
}
return { return {
...rsvp, ...rsvp,
items_bringing: itemsBringing items_bringing: itemsBringing
@@ -178,8 +185,8 @@ const EventAdmin: React.FC = () => {
// Update state with processed data // Update state with processed data
setRsvps(processedRsvps); setRsvps(processedRsvps);
setClaimedItems(Array.from(claimed)); setClaimedItems(Array.from(claimed));
// Filter needed items to only show unclaimed ones
setNeededItems(items.filter(item => !claimed.has(item))); setNeededItems(items.filter(item => !claimed.has(item)));
setOtherItems(Array.from(otherItemsSet));
setLoading(false); setLoading(false);
} catch (error) { } catch (error) {
console.error('Failed to load event data:', error); console.error('Failed to load event data:', error);
@@ -208,6 +215,7 @@ const EventAdmin: React.FC = () => {
setRsvps(rsvps.filter((r: RSVP) => r.id !== rsvpToDelete.id)); setRsvps(rsvps.filter((r: RSVP) => r.id !== rsvpToDelete.id));
setDeleteDialogOpen(false); setDeleteDialogOpen(false);
setRsvpToDelete(null); setRsvpToDelete(null);
fetchEventAndRsvps();
} catch (error) { } catch (error) {
setError('Failed to delete RSVP'); setError('Failed to delete RSVP');
} }
@@ -221,6 +229,12 @@ const EventAdmin: React.FC = () => {
processedGuestNames = rsvp.guest_names.split(',').map(name => name.trim()); processedGuestNames = rsvp.guest_names.split(',').map(name => name.trim());
} }
// Convert stored comma-separated other_items to multiline for textarea
let otherItemsMultiline = rsvp.other_items || '';
if (otherItemsMultiline.includes(',')) {
otherItemsMultiline = otherItemsMultiline.split(',').map(s => s.trim()).join('\n');
}
setRsvpToEdit(rsvp); setRsvpToEdit(rsvp);
setEditForm({ setEditForm({
name: rsvp.name, name: rsvp.name,
@@ -231,7 +245,7 @@ const EventAdmin: React.FC = () => {
items_bringing: Array.isArray(rsvp.items_bringing) ? rsvp.items_bringing : items_bringing: Array.isArray(rsvp.items_bringing) ? rsvp.items_bringing :
typeof rsvp.items_bringing === 'string' ? typeof rsvp.items_bringing === 'string' ?
(rsvp.items_bringing ? JSON.parse(rsvp.items_bringing) : []) : [], (rsvp.items_bringing ? JSON.parse(rsvp.items_bringing) : []) : [],
other_items: rsvp.other_items || '' other_items: otherItemsMultiline
}); });
setEditDialogOpen(true); setEditDialogOpen(true);
}; };
@@ -326,7 +340,14 @@ const EventAdmin: React.FC = () => {
const filteredGuestNames = editForm.guest_names const filteredGuestNames = editForm.guest_names
.map(name => name.trim()) .map(name => name.trim())
.filter(name => name.length > 0); .filter(name => name.length > 0);
// Split other_items on newlines and commas, trim, filter, join with commas
const splitOtherItems = editForm.other_items
.split(/\r?\n|,/)
.map(s => s.trim())
.filter(Boolean)
.join(', ');
// Prepare submission data in the exact format the backend expects // Prepare submission data in the exact format the backend expects
const submissionData = { const submissionData = {
name: editForm.name, name: editForm.name,
@@ -335,7 +356,7 @@ const EventAdmin: React.FC = () => {
guest_count: editForm.bringing_guests === 'yes' ? Math.max(1, parseInt(editForm.guest_count.toString(), 10)) : 0, guest_count: editForm.bringing_guests === 'yes' ? Math.max(1, parseInt(editForm.guest_count.toString(), 10)) : 0,
guest_names: filteredGuestNames, guest_names: filteredGuestNames,
items_bringing: JSON.stringify(editForm.items_bringing), items_bringing: JSON.stringify(editForm.items_bringing),
other_items: editForm.other_items, other_items: splitOtherItems,
event_id: event.id event_id: event.id
}; };
@@ -370,7 +391,7 @@ const EventAdmin: React.FC = () => {
...submissionData, ...submissionData,
guest_names: filteredGuestNames, guest_names: filteredGuestNames,
items_bringing: editForm.items_bringing, items_bringing: editForm.items_bringing,
other_items: editForm.other_items other_items: splitOtherItems
}; };
// Update the local state // Update the local state
@@ -423,6 +444,7 @@ const EventAdmin: React.FC = () => {
setClaimedItems(claimedArray); setClaimedItems(claimedArray);
setEditDialogOpen(false); setEditDialogOpen(false);
setRsvpToEdit(null); setRsvpToEdit(null);
fetchEventAndRsvps();
// Verify the update was successful but don't throw error if verification response is empty // Verify the update was successful but don't throw error if verification response is empty
try { try {
@@ -761,6 +783,17 @@ const EventAdmin: React.FC = () => {
)} )}
</Box> </Box>
</Box> </Box>
{/* Other Items Section */}
<Box>
<Typography variant="subtitle1" gutterBottom>
Other Items:
</Typography>
<Typography variant="body2" color="text.secondary">
{otherItems.length > 0
? otherItems.join(', ')
: 'No other items have been brought'}
</Typography>
</Box>
</Box> </Box>
</Box> </Box>

View File

@@ -24,7 +24,7 @@ interface RSVP {
guest_count: number; guest_count: number;
guest_names: string[] | string; guest_names: string[] | string;
items_bringing: string[] | string; items_bringing: string[] | string;
other_items?: string[]; other_items?: string;
} }
interface Event { interface Event {
@@ -46,6 +46,7 @@ const EventView: React.FC = () => {
const [rsvps, setRsvps] = useState<RSVP[]>([]); const [rsvps, setRsvps] = useState<RSVP[]>([]);
const [neededItems, setNeededItems] = useState<string[]>([]); const [neededItems, setNeededItems] = useState<string[]>([]);
const [claimedItems, setClaimedItems] = useState<string[]>([]); const [claimedItems, setClaimedItems] = useState<string[]>([]);
const [otherItems, setOtherItems] = useState<string[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -79,6 +80,7 @@ const EventView: React.FC = () => {
// Get all claimed items from existing RSVPs // Get all claimed items from existing RSVPs
const claimed = new Set<string>(); const claimed = new Set<string>();
const otherItemsSet = new Set<string>();
const processedRsvps = rsvpsResponse.data.map((rsvp: RSVP) => { const processedRsvps = rsvpsResponse.data.map((rsvp: RSVP) => {
let itemsBringing: string[] = []; let itemsBringing: string[] = [];
try { try {
@@ -98,6 +100,10 @@ const EventView: React.FC = () => {
} catch (e) { } catch (e) {
console.error('Error processing items for RSVP:', e); console.error('Error processing items for RSVP:', e);
} }
// Add non-empty other_items to set
if (typeof rsvp.other_items === 'string' && rsvp.other_items.trim() !== '') {
otherItemsSet.add(rsvp.other_items.trim());
}
return { return {
...rsvp, ...rsvp,
@@ -110,6 +116,7 @@ const EventView: React.FC = () => {
setClaimedItems(Array.from(claimed)); setClaimedItems(Array.from(claimed));
// Filter needed items to only show unclaimed ones // Filter needed items to only show unclaimed ones
setNeededItems(items.filter(item => !claimed.has(item))); setNeededItems(items.filter(item => !claimed.has(item)));
setOtherItems(Array.from(otherItemsSet));
setLoading(false); setLoading(false);
} catch (error) { } catch (error) {
setError('Failed to load event data'); setError('Failed to load event data');
@@ -239,6 +246,16 @@ const EventView: React.FC = () => {
)} )}
</Box> </Box>
</Box> </Box>
<Box>
<Typography variant="subtitle1" gutterBottom>
Other Items:
</Typography>
<Typography variant="body2" color="text.secondary">
{otherItems.length > 0
? otherItems.join(', ')
: 'No other items have been brought'}
</Typography>
</Box>
</Box> </Box>
</Box> </Box>

View File

@@ -252,9 +252,15 @@ const RSVPForm: React.FC = () => {
} }
try { try {
const splitOtherItems = formData.other_items
.split(/\r?\n|,/)
.map(s => s.trim())
.filter(Boolean)
.join(', ');
const submissionData = { const submissionData = {
...formData, ...formData,
items_bringing: formData.items_bringing items_bringing: formData.items_bringing,
other_items: splitOtherItems
}; };
const response = await axios.post(`/api/events/${slug}/rsvp`, submissionData); const response = await axios.post(`/api/events/${slug}/rsvp`, submissionData);