Feature: Filter out claimed items and show items status
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
|||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Chip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
@@ -59,6 +60,7 @@ const EventAdmin: React.FC = () => {
|
|||||||
const [event, setEvent] = useState<Event | null>(null);
|
const [event, setEvent] = useState<Event | null>(null);
|
||||||
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 [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);
|
||||||
@@ -100,9 +102,9 @@ const EventAdmin: React.FC = () => {
|
|||||||
items = [];
|
items = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNeededItems(items);
|
|
||||||
|
|
||||||
// Parse items_bringing for each RSVP
|
// Get all claimed items from existing RSVPs
|
||||||
|
const claimed = 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 {
|
||||||
@@ -111,6 +113,7 @@ const EventAdmin: React.FC = () => {
|
|||||||
: Array.isArray(rsvp.items_bringing)
|
: Array.isArray(rsvp.items_bringing)
|
||||||
? rsvp.items_bringing
|
? rsvp.items_bringing
|
||||||
: [];
|
: [];
|
||||||
|
itemsBringing.forEach(item => claimed.add(item));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing items_bringing:', e);
|
console.error('Error parsing items_bringing:', e);
|
||||||
itemsBringing = [];
|
itemsBringing = [];
|
||||||
@@ -122,6 +125,11 @@ const EventAdmin: React.FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Filter out claimed items from needed items
|
||||||
|
const availableItems = items.filter(item => !claimed.has(item));
|
||||||
|
|
||||||
|
setNeededItems(availableItems);
|
||||||
|
setClaimedItems(Array.from(claimed));
|
||||||
setRsvps(processedRsvps);
|
setRsvps(processedRsvps);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -229,6 +237,54 @@ const EventAdmin: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Add items status section */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Items Status
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 4 }}>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
|
Still Needed:
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
{neededItems.map((item, index) => (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
label={item}
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{neededItems.length === 0 && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
All items have been claimed
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
|
Claimed Items:
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
|
{claimedItems.map((item, index) => (
|
||||||
|
<Chip
|
||||||
|
key={index}
|
||||||
|
label={item}
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{claimedItems.length === 0 && (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
No items have been claimed yet
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
RSVPs ({rsvps.length})
|
RSVPs ({rsvps.length})
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const RSVPForm: React.FC = () => {
|
|||||||
items_bringing: []
|
items_bringing: []
|
||||||
});
|
});
|
||||||
const [neededItems, setNeededItems] = useState<string[]>([]);
|
const [neededItems, setNeededItems] = useState<string[]>([]);
|
||||||
|
const [claimedItems, setClaimedItems] = useState<string[]>([]);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState(false);
|
const [success, setSuccess] = useState(false);
|
||||||
@@ -46,26 +47,48 @@ const RSVPForm: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchEventDetails = async () => {
|
const fetchEventDetails = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/events/${slug}`);
|
const [eventResponse, rsvpsResponse] = await Promise.all([
|
||||||
console.log('API Response:', response.data);
|
axios.get(`/api/events/${slug}`),
|
||||||
|
axios.get(`/api/events/${slug}/rsvps`)
|
||||||
|
]);
|
||||||
|
console.log('API Response:', eventResponse.data);
|
||||||
|
|
||||||
// Ensure needed_items is an array
|
// Process needed items
|
||||||
let items: string[] = [];
|
let items: string[] = [];
|
||||||
if (response.data.needed_items) {
|
if (eventResponse.data.needed_items) {
|
||||||
try {
|
try {
|
||||||
items = typeof response.data.needed_items === 'string'
|
items = typeof eventResponse.data.needed_items === 'string'
|
||||||
? JSON.parse(response.data.needed_items)
|
? JSON.parse(eventResponse.data.needed_items)
|
||||||
: Array.isArray(response.data.needed_items)
|
: Array.isArray(eventResponse.data.needed_items)
|
||||||
? response.data.needed_items
|
? eventResponse.data.needed_items
|
||||||
: [];
|
: [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing needed_items:', e);
|
console.error('Error parsing needed_items:', e);
|
||||||
items = [];
|
items = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all claimed items from existing RSVPs
|
||||||
|
const claimed = new Set<string>();
|
||||||
|
rsvpsResponse.data.forEach((rsvp: any) => {
|
||||||
|
try {
|
||||||
|
const rsvpItems = typeof rsvp.items_bringing === 'string'
|
||||||
|
? JSON.parse(rsvp.items_bringing)
|
||||||
|
: Array.isArray(rsvp.items_bringing)
|
||||||
|
? rsvp.items_bringing
|
||||||
|
: [];
|
||||||
|
rsvpItems.forEach((item: string) => claimed.add(item));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing RSVP items:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Processed needed items:', items);
|
// Filter out claimed items
|
||||||
setNeededItems(items);
|
const availableItems = items.filter(item => !claimed.has(item));
|
||||||
|
|
||||||
|
console.log('Available items:', availableItems);
|
||||||
|
setNeededItems(availableItems);
|
||||||
|
setClaimedItems(Array.from(claimed));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching event details:', error);
|
console.error('Error fetching event details:', error);
|
||||||
setError('Failed to load event details');
|
setError('Failed to load event details');
|
||||||
|
|||||||
Reference in New Issue
Block a user