Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions Server/src/routes/donorList.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,8 +638,12 @@ router.get('/:listId/stats', protect, async (req, res) => {
*/
router.get('/stats/summary', protect, async (req, res) => {
try {
// Get all donor lists
const donorLists = await prisma.eventDonorList.findMany();
// Get all donor lists with their eventDonors
const donorLists = await prisma.eventDonorList.findMany({
include: {
eventDonors: true
}
});

if (donorLists.length === 0) {
return res.status(200).json({
Expand All @@ -656,18 +660,44 @@ router.get('/stats/summary', protect, async (req, res) => {
});
}

// Calculate aggregated statistics
// Calculate aggregated statistics from eventDonors
const totalLists = donorLists.length;
const totalDonors = donorLists.reduce((sum, list) => sum + list.totalDonors, 0);
const totalApproved = donorLists.reduce((sum, list) => sum + list.approved, 0);
const totalExcluded = donorLists.reduce((sum, list) => sum + list.excluded, 0);
const totalPending = donorLists.reduce((sum, list) => sum + list.pending, 0);
const totalAutoExcluded = donorLists.reduce((sum, list) => sum + list.autoExcluded, 0);
const totalReviewed = totalApproved + totalExcluded;

const completedLists = donorLists.filter(list => list.reviewStatus === 'completed').length;
const pendingLists = donorLists.filter(list => list.reviewStatus === 'pending').length;


// Initialize counters
let totalDonors = 0;
let totalApproved = 0;
let totalExcluded = 0;
let totalPending = 0;
let totalAutoExcluded = 0;

// Calculate statistics from eventDonors
donorLists.forEach(list => {
const donors = list.eventDonors;
totalDonors += donors.length;

donors.forEach(donor => {
switch (donor.status) {
case 'Approved':
totalApproved++;
break;
case 'Excluded':
totalExcluded++;
break;
case 'Pending':
totalPending++;
break;
default:
break;
}
if (donor.autoExcluded) {
totalAutoExcluded++;
}
});
});

const totalReviewed = totalApproved + totalExcluded;
const overallApprovalRate = totalDonors > 0
? Math.round((totalApproved / totalDonors) * 100)
: 0;
Expand Down
5 changes: 1 addition & 4 deletions Server/src/routes/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ router.get('/', protect, async (req, res) => {

if (validStatuses.includes(status)) {
where.status = status;
} else if (status === 'active') {
// 特殊情况: 'active' 可以映射到多个"活跃"状态
where.status = { in: ['Planning', 'ListGeneration', 'Review', 'Ready'] };
}
}

Expand Down Expand Up @@ -241,7 +238,7 @@ router.post('/', protect, async (req, res) => {
timelineListGenerationDate,
timelineReviewDeadline,
timelineInvitationDate,
status = 'Planning'
status
} = req.body;

// Validate required fields
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Dashboard = () => {
const summary = await getDonorListsSummary();

// Fetch active events
const eventsResult = await getEvents({ status: 'active', limit: 10 });
const eventsResult = await getEvents({ status: 'Ready', limit: 10 });
const activeEvents = eventsResult.data || [];

// 在设置状态前检查组件是否仍然挂载
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/donors/Donors.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { FaUser, FaPlus, FaAngleDown, FaSpinner, FaDownload } from 'react-icons/fa';
import { getEvents, getEventById, getEventDonors } from '../../services/eventService';
import { getAvailableDonors, removeDonorFromEvent, getEventDonorStats, updateEventDonor, exportEventDonorsToCsv } from '../../services/donorService';

Check warning on line 4 in client/src/components/donors/Donors.jsx

View workflow job for this annotation

GitHub Actions / lint

'getAvailableDonors' is defined but never used
import { useLocation } from 'react-router-dom';
import './Donors.css';
import DonorList from './DonorList';
Expand Down Expand Up @@ -57,7 +57,7 @@
// Fetch events on component mount
useEffect(() => {
fetchEvents();
}, []);

Check warning on line 60 in client/src/components/donors/Donors.jsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'fetchEvents'. Either include it or remove the dependency array

// Handle event selection from location state
useEffect(() => {
Expand Down Expand Up @@ -90,7 +90,7 @@
fetchEventStats();
}
}
}, [selectedEvent, searchQuery, currentPage]);

Check warning on line 93 in client/src/components/donors/Donors.jsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'fetchEventDonors' and 'fetchEventStats'. Either include them or remove the dependency array

// Fetch events
const fetchEvents = async () => {
Expand Down Expand Up @@ -503,7 +503,7 @@
* @param {Object} donor - The donor to edit status for
*/
const handleOpenStatusModal = (donor) => {
if (selectedEvent.status !== 'active') {
if (selectedEvent.status !== 'Ready') {
alert('Only events in Ready status can edit donor information');
return;
}
Expand Down Expand Up @@ -584,7 +584,7 @@
// Check if the selected event is in Ready status
const isEventReady = () => {
if (!selectedEvent) return false;
return selectedEvent.status === 'active';
return selectedEvent.status === 'Ready';
};

// handle status filter
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/events/EventManagement.css
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@
}

/* Ready - Light Green */
.activity-status.active {
.activity-status.ready {
background-color: #D1FAE5;
color: #10B981;
}
Expand Down
22 changes: 3 additions & 19 deletions client/src/services/eventService.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,15 @@ export const getEvents = async (params = {}) => {
try {
const url = new URL(`${API_URL}/api/events`);

// Handle incoming active status, map to correct backend status value
const modifiedParams = { ...params };

// If status is "active", replace it with backend understood value "Ready"
if (modifiedParams.status === 'active') {
modifiedParams.status = 'Ready';
}

// Add query parameters
Object.keys(modifiedParams).forEach(key => {
if (modifiedParams[key] !== undefined && modifiedParams[key] !== '') {
url.searchParams.append(key, modifiedParams[key]);
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== '') {
url.searchParams.append(key, params[key]);
}
});

const data = await fetchWithAuth(url.toString());

// Map backend status values back to frontend values
if (data.events) {
data.events = data.events.map(event => ({
...event,
status: event.status === 'Ready' ? 'active' : event.status.toLowerCase()
}));
}

return {
data: data.events || [],
page: data.page || 1,
Expand Down
34 changes: 15 additions & 19 deletions client/src/utils/statusConversion.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,23 @@
*/
export const toFrontendStatus = (backendStatus) => {
if (!backendStatus) return '';

switch(backendStatus) {
case 'Ready': return 'active';
default: return backendStatus.toLowerCase();
}
};
return backendStatus;
};

/**
* Converts frontend event status format to backend format
* @param {string} frontendStatus - Status string from the frontend
* @returns {string} Backend-formatted status matching Prisma EventStatus enum
*/
export const toBackendStatus = (frontendStatus) => {
/**
* Converts frontend event status format to backend format
* @param {string} frontendStatus - Status string from the frontend
* @returns {string} Backend-formatted status matching Prisma EventStatus enum
*/
export const toBackendStatus = (frontendStatus) => {
if (!frontendStatus) return 'Planning'; // Default value

switch(frontendStatus) {
case 'active': return 'Ready';
case 'complete': return 'Complete';
case 'planning': return 'Planning';
case 'listgeneration': return 'ListGeneration';
case 'review': return 'Review';
default: return frontendStatus; // Fallback if already in correct format
case 'Complete': return 'Complete';
case 'Planning': return 'Planning';
case 'ListGeneration': return 'ListGeneration';
case 'Review': return 'Review';
case 'Ready': return 'Ready';
default: return frontendStatus; // Fallback if already in correct format
}
};
};
Loading