fix json
This commit is contained in:
parent
897fbe4459
commit
e98a0f32e2
1 changed files with 298 additions and 275 deletions
|
@ -1,12 +1,11 @@
|
|||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Get } from '../../../scripts/pocketbase/Get';
|
||||
import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
||||
import toast from 'react-hot-toast';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
// Define the event request interface
|
||||
export interface EventRequest {
|
||||
interface EventRequest {
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
|
@ -19,7 +18,7 @@ export interface EventRequest {
|
|||
food_drinks_being_served: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
status: string; // Status field from PocketBase: submitted, pending, completed, declined
|
||||
status?: string; // Status might not be in the schema yet
|
||||
flyer_type?: string[];
|
||||
other_flyer_type?: string;
|
||||
flyer_advertising_start_date?: string;
|
||||
|
@ -41,15 +40,6 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
|||
const [selectedRequest, setSelectedRequest] = useState<EventRequest | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
|
||||
const [isMounted, setIsMounted] = useState<boolean>(false);
|
||||
|
||||
// Set mounted state when component mounts
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
return () => {
|
||||
setIsMounted(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Format date for display
|
||||
const formatDate = (dateString: string) => {
|
||||
|
@ -69,39 +59,31 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
|||
};
|
||||
|
||||
// Get status badge class based on status
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'completed':
|
||||
const getStatusBadge = (status?: string) => {
|
||||
if (!status) return 'badge-warning';
|
||||
|
||||
switch (status.toLowerCase()) {
|
||||
case 'approved':
|
||||
return 'badge-success';
|
||||
case 'declined':
|
||||
case 'rejected':
|
||||
return 'badge-error';
|
||||
case 'pending':
|
||||
return 'badge-warning';
|
||||
case 'submitted':
|
||||
case 'in progress':
|
||||
return 'badge-info';
|
||||
default:
|
||||
return 'badge-warning'; // Default to warning for unknown status
|
||||
return 'badge-warning';
|
||||
}
|
||||
};
|
||||
|
||||
// Format status for display
|
||||
const formatStatus = (status: string) => {
|
||||
if (!status) return 'Pending';
|
||||
|
||||
// Capitalize first letter
|
||||
return status.charAt(0).toUpperCase() + status.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
// Open modal with event request details
|
||||
const openDetailModal = (request: EventRequest) => {
|
||||
console.log('Opening modal for request:', request.id, request.name);
|
||||
setSelectedRequest(request);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
// Close modal
|
||||
const closeModal = () => {
|
||||
console.log('Closing modal');
|
||||
setIsModalOpen(false);
|
||||
setSelectedRequest(null);
|
||||
};
|
||||
|
@ -142,70 +124,129 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
|||
}
|
||||
};
|
||||
|
||||
// Auto-refresh when component mounts and when refreshSubmissions event is triggered
|
||||
useEffect(() => {
|
||||
// Refresh on mount
|
||||
refreshEventRequests();
|
||||
|
||||
// Listen for refreshSubmissions event
|
||||
const handleRefreshEvent = () => {
|
||||
refreshEventRequests();
|
||||
};
|
||||
|
||||
document.addEventListener('refreshSubmissions', handleRefreshEvent);
|
||||
|
||||
// Clean up event listener
|
||||
return () => {
|
||||
document.removeEventListener('refreshSubmissions', handleRefreshEvent);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Function to render the modal using a portal
|
||||
const renderModal = () => {
|
||||
if (!isModalOpen || !selectedRequest || !isMounted) {
|
||||
console.log('Modal not rendered - conditions not met:', { isModalOpen, hasSelectedRequest: !!selectedRequest, isMounted });
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('Rendering modal for:', selectedRequest.name);
|
||||
|
||||
// Check if document.body is available (important for SSR environments)
|
||||
if (typeof document === 'undefined' || !document.body) {
|
||||
console.error('document.body is not available');
|
||||
|
||||
// Fallback to direct rendering if portal can't be used
|
||||
if (eventRequests.length === 0) {
|
||||
return (
|
||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div className="bg-base-200 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-bold">{selectedRequest.name}</h2>
|
||||
<div className="bg-base-200 rounded-lg p-8 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">No Event Requests Found</h3>
|
||||
<p className="text-gray-400 mb-6">You haven't submitted any event requests yet.</p>
|
||||
<p className="text-sm">Use the form above to submit a new event request.</p>
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost"
|
||||
onClick={closeModal}
|
||||
className="btn btn-outline btn-sm mt-4"
|
||||
onClick={refreshEventRequests}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
✕
|
||||
{isRefreshing ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-xs mr-2"></span>
|
||||
Refreshing...
|
||||
</>
|
||||
) : (
|
||||
<>Refresh</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p>Event details are available. Please close and try again if content is not displaying properly.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Use try-catch to handle any potential errors during portal creation
|
||||
try {
|
||||
return createPortal(
|
||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-50" onClick={(e) => {
|
||||
// Close modal when clicking outside the modal content
|
||||
if (e.target === e.currentTarget) {
|
||||
closeModal();
|
||||
}
|
||||
}}>
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">Your Submissions</h3>
|
||||
<button
|
||||
className="btn btn-outline btn-sm"
|
||||
onClick={refreshEventRequests}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-xs mr-2"></span>
|
||||
Refreshing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event Name</th>
|
||||
<th>Date</th>
|
||||
<th>Location</th>
|
||||
<th>PR Materials</th>
|
||||
<th>AS Funding</th>
|
||||
<th>Submitted</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{eventRequests.map((request) => (
|
||||
<tr key={request.id} className="hover">
|
||||
<td className="font-medium">{request.name}</td>
|
||||
<td>{formatDate(request.start_date_time)}</td>
|
||||
<td>{request.location}</td>
|
||||
<td>
|
||||
{request.flyers_needed ? (
|
||||
<span className="badge badge-success badge-sm">Yes</span>
|
||||
) : (
|
||||
<span className="badge badge-ghost badge-sm">No</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{request.as_funding_required ? (
|
||||
<span className="badge badge-success badge-sm">Yes</span>
|
||||
) : (
|
||||
<span className="badge badge-ghost badge-sm">No</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{formatDate(request.created)}</td>
|
||||
<td>
|
||||
<span className={`badge ${getStatusBadge(request.status)} badge-sm`}>
|
||||
{request.status || 'Pending'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-ghost btn-xs"
|
||||
onClick={() => openDetailModal(request)}
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="bg-base-300/50 p-4 rounded-lg text-sm">
|
||||
<h3 className="font-semibold mb-2">About Your Submissions</h3>
|
||||
<ul className="list-disc list-inside space-y-1 ml-2">
|
||||
<li>Event requests are typically reviewed within 1-2 business days.</li>
|
||||
<li>You'll receive email notifications when your request status changes.</li>
|
||||
<li>For urgent inquiries, please contact the PR team or coordinators in the #-events Slack channel.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Event Request Detail Modal */}
|
||||
{isModalOpen && selectedRequest && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="bg-base-200 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()} // Prevent clicks inside the modal from closing it
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
|
@ -316,46 +357,185 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
|||
<div>
|
||||
<p className="text-sm text-gray-400">Vendor</p>
|
||||
<p className="font-medium">
|
||||
{(() => {
|
||||
try {
|
||||
if (typeof selectedRequest.invoice_data === 'string') {
|
||||
return JSON.parse(selectedRequest.invoice_data).vendor || 'Not specified';
|
||||
} else if (typeof selectedRequest.invoice_data === 'object') {
|
||||
return selectedRequest.invoice_data.vendor || 'Not specified';
|
||||
}
|
||||
return 'Not specified';
|
||||
} catch (error) {
|
||||
console.error('Error parsing invoice data:', error);
|
||||
return 'Not specified (Error parsing data)';
|
||||
}
|
||||
})()}
|
||||
{selectedRequest.invoice_data.vendor || 'Not specified'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="text-sm text-gray-400">Itemized Invoice</p>
|
||||
<pre className="bg-base-300 p-3 rounded-lg text-xs overflow-x-auto mt-2">
|
||||
{(() => {
|
||||
try {
|
||||
if (typeof selectedRequest.itemized_invoice === 'object') {
|
||||
return JSON.stringify(selectedRequest.itemized_invoice, null, 2);
|
||||
} else if (typeof selectedRequest.itemized_invoice === 'string') {
|
||||
// Try to parse it as JSON to pretty-print it
|
||||
let invoiceData: any = null;
|
||||
|
||||
// Parse the invoice data if it's a string, or use it directly if it's an object
|
||||
if (typeof selectedRequest.itemized_invoice === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(selectedRequest.itemized_invoice);
|
||||
return JSON.stringify(parsed, null, 2);
|
||||
} catch {
|
||||
// If it's not valid JSON, just return the string
|
||||
return selectedRequest.itemized_invoice;
|
||||
invoiceData = JSON.parse(selectedRequest.itemized_invoice);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse invoice JSON:', e);
|
||||
return (
|
||||
<pre className="bg-base-300 p-3 rounded-lg text-xs overflow-x-auto mt-2">
|
||||
{selectedRequest.itemized_invoice || 'Not provided'}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
} else if (typeof selectedRequest.itemized_invoice === 'object') {
|
||||
invoiceData = selectedRequest.itemized_invoice;
|
||||
}
|
||||
|
||||
// If we have valid invoice data with items
|
||||
if (invoiceData && Array.isArray(invoiceData.items) && invoiceData.items.length > 0) {
|
||||
// Calculate total from items if not provided or if NaN
|
||||
let calculatedTotal = 0;
|
||||
|
||||
// Try to use the provided total first
|
||||
if (invoiceData.total !== undefined) {
|
||||
const parsedTotal = typeof invoiceData.total === 'string'
|
||||
? parseFloat(invoiceData.total)
|
||||
: invoiceData.total;
|
||||
|
||||
if (!isNaN(parsedTotal)) {
|
||||
calculatedTotal = parsedTotal;
|
||||
}
|
||||
}
|
||||
return 'Not provided';
|
||||
|
||||
// If total is NaN or not provided, calculate from items
|
||||
if (calculatedTotal === 0 || isNaN(calculatedTotal)) {
|
||||
calculatedTotal = invoiceData.items.reduce((sum: number, item: any) => {
|
||||
const quantity = typeof item.quantity === 'string'
|
||||
? parseFloat(item.quantity)
|
||||
: (item.quantity || 1);
|
||||
|
||||
const unitPrice = typeof item.unit_price === 'string'
|
||||
? parseFloat(item.unit_price)
|
||||
: (item.unit_price || 0);
|
||||
|
||||
const itemTotal = !isNaN(quantity) && !isNaN(unitPrice)
|
||||
? quantity * unitPrice
|
||||
: 0;
|
||||
|
||||
return sum + itemTotal;
|
||||
}, 0);
|
||||
|
||||
// Add tax and tip if available
|
||||
if (invoiceData.tax && !isNaN(parseFloat(invoiceData.tax))) {
|
||||
calculatedTotal += parseFloat(invoiceData.tax);
|
||||
}
|
||||
|
||||
if (invoiceData.tip && !isNaN(parseFloat(invoiceData.tip))) {
|
||||
calculatedTotal += parseFloat(invoiceData.tip);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-base-300 p-3 rounded-lg overflow-x-auto mt-2">
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th className="text-right">Qty</th>
|
||||
<th className="text-right">Price</th>
|
||||
<th className="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{invoiceData.items.map((item: any, index: number) => {
|
||||
const quantity = typeof item.quantity === 'string'
|
||||
? parseFloat(item.quantity)
|
||||
: (item.quantity || 1);
|
||||
|
||||
const unitPrice = typeof item.unit_price === 'string'
|
||||
? parseFloat(item.unit_price)
|
||||
: (item.unit_price || 0);
|
||||
|
||||
const itemTotal = !isNaN(quantity) && !isNaN(unitPrice)
|
||||
? quantity * unitPrice
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{item.item || 'Unnamed item'}</td>
|
||||
<td className="text-right">{!isNaN(quantity) ? quantity : 1}</td>
|
||||
<td className="text-right">${!isNaN(unitPrice) ? unitPrice.toFixed(2) : '0.00'}</td>
|
||||
<td className="text-right">${!isNaN(itemTotal) ? itemTotal.toFixed(2) : '0.00'}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
{invoiceData.tax !== undefined && (
|
||||
<tr>
|
||||
<td colSpan={3} className="text-right font-medium">Tax:</td>
|
||||
<td className="text-right">
|
||||
${typeof invoiceData.tax === 'string'
|
||||
? (parseFloat(invoiceData.tax) || 0).toFixed(2)
|
||||
: (invoiceData.tax || 0).toFixed(2)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{invoiceData.tip !== undefined && (
|
||||
<tr>
|
||||
<td colSpan={3} className="text-right font-medium">Tip:</td>
|
||||
<td className="text-right">
|
||||
${typeof invoiceData.tip === 'string'
|
||||
? (parseFloat(invoiceData.tip) || 0).toFixed(2)
|
||||
: (invoiceData.tip || 0).toFixed(2)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td colSpan={3} className="text-right font-bold">Total:</td>
|
||||
<td className="text-right font-bold">
|
||||
${!isNaN(calculatedTotal) ? calculatedTotal.toFixed(2) : '0.00'}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{invoiceData.vendor && (
|
||||
<div className="mt-3 text-sm">
|
||||
<span className="font-medium">Vendor:</span> {invoiceData.vendor}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (invoiceData && typeof invoiceData.total !== 'undefined') {
|
||||
// If we have a total but no items, show a simplified view
|
||||
const total = typeof invoiceData.total === 'string'
|
||||
? parseFloat(invoiceData.total)
|
||||
: invoiceData.total;
|
||||
|
||||
return (
|
||||
<div className="bg-base-300 p-3 rounded-lg mt-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-medium">Total Amount:</span>
|
||||
<span className="font-bold">${!isNaN(total) ? total.toFixed(2) : '0.00'}</span>
|
||||
</div>
|
||||
{invoiceData.vendor && (
|
||||
<div className="mt-2">
|
||||
<span className="font-medium">Vendor:</span> {invoiceData.vendor}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// Fallback to display the JSON in a readable format
|
||||
return (
|
||||
<pre className="bg-base-300 p-3 rounded-lg text-xs overflow-x-auto mt-2">
|
||||
{typeof selectedRequest.itemized_invoice === 'object'
|
||||
? JSON.stringify(selectedRequest.itemized_invoice, null, 2)
|
||||
: (selectedRequest.itemized_invoice || 'Not provided')}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error displaying itemized invoice:', error);
|
||||
return 'Error displaying invoice data';
|
||||
console.error('Error rendering invoice:', error);
|
||||
return (
|
||||
<pre className="bg-base-300 p-3 rounded-lg text-xs overflow-x-auto mt-2">
|
||||
Error displaying invoice. Please check the console for details.
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -370,170 +550,13 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
|||
<div>
|
||||
<p className="text-sm text-gray-400">Status</p>
|
||||
<span className={`badge ${getStatusBadge(selectedRequest.status)}`}>
|
||||
{formatStatus(selectedRequest.status)}
|
||||
{selectedRequest.status || 'Pending'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error rendering modal:', error);
|
||||
return (
|
||||
<div className="alert alert-error">
|
||||
<p>Error rendering modal. Please try again.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (eventRequests.length === 0) {
|
||||
return (
|
||||
<div className="bg-base-200 rounded-lg p-8 text-center">
|
||||
<h3 className="text-xl font-semibold mb-4">No Event Requests Found</h3>
|
||||
<p className="text-gray-400 mb-6">You haven't submitted any event requests yet.</p>
|
||||
<p className="text-sm">Use the form above to submit a new event request.</p>
|
||||
<button
|
||||
className="btn btn-outline btn-sm mt-4"
|
||||
onClick={refreshEventRequests}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-xs mr-2"></span>
|
||||
Refreshing...
|
||||
</>
|
||||
) : (
|
||||
<>Refresh</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="space-y-6"
|
||||
>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">Your Submissions</h3>
|
||||
<button
|
||||
className="btn btn-outline btn-sm"
|
||||
onClick={refreshEventRequests}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
{isRefreshing ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-xs mr-2"></span>
|
||||
Refreshing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Refresh
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event Name</th>
|
||||
<th>Date</th>
|
||||
<th>Location</th>
|
||||
<th>PR Materials</th>
|
||||
<th>AS Funding</th>
|
||||
<th>Submitted</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{eventRequests.map((request) => (
|
||||
<tr key={request.id} className="hover">
|
||||
<td className="font-medium">{request.name}</td>
|
||||
<td>{formatDate(request.start_date_time)}</td>
|
||||
<td>{request.location}</td>
|
||||
<td>
|
||||
{request.flyers_needed ? (
|
||||
<span className="badge badge-success badge-sm">Yes</span>
|
||||
) : (
|
||||
<span className="badge badge-ghost badge-sm">No</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{request.as_funding_required ? (
|
||||
<span className="badge badge-success badge-sm">Yes</span>
|
||||
) : (
|
||||
<span className="badge badge-ghost badge-sm">No</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{formatDate(request.created)}</td>
|
||||
<td>
|
||||
<span className={`badge ${getStatusBadge(request.status)} badge-sm`}>
|
||||
{formatStatus(request.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="btn btn-ghost btn-xs"
|
||||
onClick={() => openDetailModal(request)}
|
||||
aria-label={`View details for ${request.name}`}
|
||||
type="button"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="bg-base-300/50 p-4 rounded-lg text-sm">
|
||||
<h3 className="font-semibold mb-2">About Your Submissions</h3>
|
||||
<ul className="list-disc list-inside space-y-1 ml-2">
|
||||
<li>Event requests are typically reviewed within 1-2 business days.</li>
|
||||
<li>You'll receive email notifications when your request status changes.</li>
|
||||
<li>For urgent inquiries, please contact the PR team or coordinators in the #-events Slack channel.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Render the modal using the portal */}
|
||||
{renderModal()}
|
||||
|
||||
{/* Fallback modal implementation (rendered directly in the DOM) */}
|
||||
{isModalOpen && selectedRequest && (
|
||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black bg-opacity-50" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||
<div className="bg-base-200 rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-bold">{selectedRequest.name}</h2>
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost"
|
||||
onClick={closeModal}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p><span className="font-medium">Event:</span> {selectedRequest.name}</p>
|
||||
<p><span className="font-medium">Location:</span> {selectedRequest.location}</p>
|
||||
<p><span className="font-medium">Date:</span> {formatDate(selectedRequest.start_date_time)}</p>
|
||||
<p><span className="font-medium">Status:</span> <span className={`badge ${getStatusBadge(selectedRequest.status)}`}>{formatStatus(selectedRequest.status)}</span></p>
|
||||
<p className="text-sm text-gray-400 mt-4">This is a simplified view. Please check the console for any errors if the detailed view is not working.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
|
Loading…
Reference in a new issue