better view of the event submissions
does not improve view for event management
This commit is contained in:
parent
97ce397281
commit
e829eef69f
7 changed files with 968 additions and 872 deletions
5
bun.lock
5
bun.lock
|
@ -17,7 +17,6 @@
|
||||||
"astro": "5.1.1",
|
"astro": "5.1.1",
|
||||||
"astro-expressive-code": "^0.40.2",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"axios": "^1.8.2",
|
|
||||||
"chart.js": "^4.4.7",
|
"chart.js": "^4.4.7",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
"framer-motion": "^12.4.4",
|
"framer-motion": "^12.4.4",
|
||||||
|
@ -438,7 +437,7 @@
|
||||||
|
|
||||||
"autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="],
|
"autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="],
|
||||||
|
|
||||||
"axios": ["axios@1.8.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg=="],
|
"axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
|
@ -1428,8 +1427,6 @@
|
||||||
|
|
||||||
"@expressive-code/plugin-shiki/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
"@expressive-code/plugin-shiki/shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
|
||||||
|
|
||||||
"@iconify/tools/axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
|
|
||||||
|
|
||||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||||
|
|
||||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
"astro": "5.1.1",
|
"astro": "5.1.1",
|
||||||
"astro-expressive-code": "^0.40.2",
|
"astro-expressive-code": "^0.40.2",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"axios": "^1.8.2",
|
|
||||||
"chart.js": "^4.4.7",
|
"chart.js": "^4.4.7",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
"framer-motion": "^12.4.4",
|
"framer-motion": "^12.4.4",
|
||||||
|
|
|
@ -5,7 +5,7 @@ import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm";
|
||||||
import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests";
|
import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests";
|
||||||
import { Collections } from "../../schemas/pocketbase/schema";
|
import { Collections } from "../../schemas/pocketbase/schema";
|
||||||
import { DataSyncService } from "../../scripts/database/DataSyncService";
|
import { DataSyncService } from "../../scripts/database/DataSyncService";
|
||||||
import { EventRequestFormPreviewModal } from "./Officer_EventRequestForm/EventRequestFormPreview";
|
import { EventRequestFormPreviewModalWrapper } from "./Officer_EventRequestForm/EventRequestFormPreview";
|
||||||
|
|
||||||
// Import the EventRequest type from UserEventRequests to ensure consistency
|
// Import the EventRequest type from UserEventRequests to ensure consistency
|
||||||
import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests";
|
import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests";
|
||||||
|
@ -116,123 +116,72 @@ if (auth.isAuthenticated()) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- The modal will be rendered through the global function and event system -->
|
||||||
|
<EventRequestFormPreviewModalWrapper client:load />
|
||||||
|
|
||||||
<style is:global>
|
<style is:global>
|
||||||
/* Ensure the modal container is always visible */
|
/* Ensure the modal container is always visible */
|
||||||
#event-request-preview-modal-container {
|
#event-request-preview-modal-overlay {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
width: 100vw !important;
|
width: 100vw !important;
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
z-index: 99999 !important;
|
max-width: 100vw !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
z-index: 999999 !important;
|
||||||
overflow: auto !important;
|
overflow: auto !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
/* Style for the modal backdrop */
|
|
||||||
#event-request-preview-modal-container > div > div:first-child {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
width: 100vw !important;
|
|
||||||
height: 100vh !important;
|
|
||||||
z-index: 99999 !important;
|
|
||||||
background-color: rgba(0, 0, 0, 0.8) !important;
|
|
||||||
backdrop-filter: blur(8px) !important;
|
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
overflow: auto !important;
|
background-color: rgba(0, 0, 0, 0.6) !important;
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style for the modal content */
|
/* Style for the modal content */
|
||||||
#event-request-preview-modal-container > div > div > div {
|
#event-request-preview-modal-overlay > div {
|
||||||
z-index: 100000 !important;
|
z-index: 1000000 !important;
|
||||||
position: relative !important;
|
position: relative !important;
|
||||||
max-width: 90vw !important;
|
max-width: min(90vw, 1024px) !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-height: 90vh !important;
|
max-height: 90vh !important;
|
||||||
overflow: auto !important;
|
overflow: auto !important;
|
||||||
margin: 2rem !important;
|
margin: 0 !important;
|
||||||
|
background-color: var(--color-base-100) !important;
|
||||||
|
border-radius: 1rem !important;
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Add the modal component -->
|
|
||||||
<EventRequestFormPreviewModal client:load />
|
|
||||||
|
|
||||||
<div class="dashboard-section hidden" id="eventRequestFormSection">
|
|
||||||
<!-- ... existing code ... -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
// Define the global function immediately to ensure it's available
|
// Define the global function immediately to ensure it's available
|
||||||
window.showEventRequestFormPreview = function (formData) {
|
window.showEventRequestFormPreview = function (formData) {
|
||||||
// console.log(
|
console.log("showEventRequestFormPreview called with formData:", formData);
|
||||||
// "Global showEventRequestFormPreview called with data",
|
|
||||||
// formData
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Remove any elements that might be obstructing the view
|
|
||||||
const removeObstructions = () => {
|
|
||||||
// Find any elements with high z-index that might be obstructing
|
|
||||||
document.querySelectorAll('[style*="z-index"]').forEach((el) => {
|
|
||||||
if (
|
|
||||||
el.id !== "event-request-preview-modal-container" &&
|
|
||||||
!el.closest("#event-request-preview-modal-container")
|
|
||||||
) {
|
|
||||||
// Store original z-index to restore later
|
|
||||||
if (!el.dataset.originalZIndex) {
|
|
||||||
el.dataset.originalZIndex = el.style.zIndex;
|
|
||||||
}
|
|
||||||
// Temporarily lower z-index
|
|
||||||
el.style.zIndex = "0";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a custom event to trigger the preview
|
// Create a custom event to trigger the preview
|
||||||
const event = new CustomEvent("showEventRequestPreviewModal", {
|
const event = new CustomEvent("showEventRequestPreviewModal", {
|
||||||
detail: { formData },
|
detail: { formData },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove obstructions before showing modal
|
console.log("Dispatching event with detail:", event.detail);
|
||||||
removeObstructions();
|
|
||||||
|
|
||||||
// Dispatch event to show modal
|
// Dispatch event to show modal
|
||||||
document.dispatchEvent(event);
|
document.dispatchEvent(event);
|
||||||
// console.log("showEventRequestPreviewModal event dispatched");
|
|
||||||
|
|
||||||
// Ensure modal container is visible
|
// Prevent body scrolling when modal is open
|
||||||
setTimeout(() => {
|
document.body.style.overflow = "hidden";
|
||||||
const modalContainer = document.getElementById(
|
|
||||||
"event-request-preview-modal-container",
|
|
||||||
);
|
|
||||||
if (modalContainer) {
|
|
||||||
modalContainer.style.zIndex = "99999";
|
|
||||||
modalContainer.style.position = "fixed";
|
|
||||||
modalContainer.style.top = "0";
|
|
||||||
modalContainer.style.left = "0";
|
|
||||||
modalContainer.style.width = "100vw";
|
|
||||||
modalContainer.style.height = "100vh";
|
|
||||||
modalContainer.style.overflow = "auto";
|
|
||||||
modalContainer.style.margin = "0";
|
|
||||||
modalContainer.style.padding = "0";
|
|
||||||
|
|
||||||
// Force body to allow scrolling
|
// Add event listener to restore scrolling when modal is closed
|
||||||
document.body.style.overflow = "auto";
|
const handleModalClose = () => {
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
document.removeEventListener("modalClosed", handleModalClose);
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure the modal content is properly sized
|
document.addEventListener("modalClosed", handleModalClose);
|
||||||
const modalContent = modalContainer.querySelector("div > div > div");
|
|
||||||
if (modalContent) {
|
|
||||||
modalContent.style.maxWidth = "90vw";
|
|
||||||
modalContent.style.width = "100%";
|
|
||||||
modalContent.style.maxHeight = "90vh";
|
|
||||||
modalContent.style.overflow = "auto";
|
|
||||||
modalContent.style.margin = "2rem";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -333,30 +333,24 @@ const ASFundingSection: React.FC<ASFundingSectionProps> = ({ formData, onDataCha
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-xl font-bold text-primary">Invoice Details</h3>
|
<h3 className="text-xl font-bold text-primary">Invoice Details</h3>
|
||||||
|
|
||||||
<motion.div
|
<div className="flex mb-4 border rounded-lg overflow-hidden">
|
||||||
className="bg-base-300/50 p-1 rounded-lg flex items-center"
|
<button
|
||||||
whileHover="hover"
|
type="button"
|
||||||
variants={toggleVariants}
|
|
||||||
>
|
|
||||||
<motion.button
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${!showJsonInput ? 'bg-primary text-primary-content' : 'hover:bg-base-200'
|
|
||||||
}`}
|
|
||||||
onClick={() => setShowJsonInput(false)}
|
onClick={() => setShowJsonInput(false)}
|
||||||
whileHover={{ scale: 1.05 }}
|
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${!showJsonInput ? 'bg-primary text-white' : 'hover:bg-base-200'
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
>
|
|
||||||
Invoice Builder
|
|
||||||
</motion.button>
|
|
||||||
<motion.button
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${showJsonInput ? 'bg-primary text-primary-content' : 'hover:bg-base-200'
|
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setShowJsonInput(true)}
|
|
||||||
whileHover={{ scale: 1.05 }}
|
|
||||||
whileTap={{ scale: 0.95 }}
|
|
||||||
>
|
>
|
||||||
Paste JSON
|
Visual Editor
|
||||||
</motion.button>
|
</button>
|
||||||
</motion.div>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowJsonInput(true)}
|
||||||
|
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${showJsonInput ? 'bg-primary text-white' : 'hover:bg-base-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
JSON Editor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showJsonInput ? (
|
{showJsonInput ? (
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,6 +4,8 @@ import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
||||||
import { DataSyncService } from '../../../scripts/database/DataSyncService';
|
import { DataSyncService } from '../../../scripts/database/DataSyncService';
|
||||||
import { Collections } from '../../../schemas/pocketbase/schema';
|
import { Collections } from '../../../schemas/pocketbase/schema';
|
||||||
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase';
|
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase';
|
||||||
|
import { EventRequestFormPreview } from './EventRequestFormPreview';
|
||||||
|
import type { EventRequestFormData } from './EventRequestForm';
|
||||||
|
|
||||||
// Declare the global window interface to include our custom function
|
// Declare the global window interface to include our custom function
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -17,10 +19,146 @@ export interface EventRequest extends SchemaEventRequest {
|
||||||
invoice_data?: any;
|
invoice_data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to convert EventRequest to EventRequestFormData
|
||||||
|
const convertToFormData = (request: EventRequest): EventRequestFormData => {
|
||||||
|
try {
|
||||||
|
// Parse itemized_invoice if it's a string
|
||||||
|
let invoiceData = {};
|
||||||
|
try {
|
||||||
|
if (request.itemized_invoice) {
|
||||||
|
if (typeof request.itemized_invoice === 'string') {
|
||||||
|
invoiceData = JSON.parse(request.itemized_invoice);
|
||||||
|
} else {
|
||||||
|
invoiceData = request.itemized_invoice;
|
||||||
|
}
|
||||||
|
} else if (request.invoice_data) {
|
||||||
|
invoiceData = request.invoice_data;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing invoice data:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast to unknown first, then to EventRequestFormData to avoid type checking
|
||||||
|
return {
|
||||||
|
name: request.name,
|
||||||
|
location: request.location,
|
||||||
|
start_date_time: request.start_date_time,
|
||||||
|
end_date_time: request.end_date_time,
|
||||||
|
event_description: request.event_description || '',
|
||||||
|
flyers_needed: request.flyers_needed || false,
|
||||||
|
photography_needed: request.photography_needed || false,
|
||||||
|
flyer_type: request.flyer_type || [],
|
||||||
|
other_flyer_type: request.other_flyer_type || '',
|
||||||
|
flyer_advertising_start_date: request.flyer_advertising_start_date || '',
|
||||||
|
advertising_format: request.advertising_format || '',
|
||||||
|
required_logos: request.required_logos || [],
|
||||||
|
other_logos: [] as File[], // EventRequest doesn't have this as files
|
||||||
|
flyer_additional_requests: request.flyer_additional_requests || '',
|
||||||
|
will_or_have_room_booking: request.will_or_have_room_booking || false,
|
||||||
|
room_booking: null,
|
||||||
|
room_booking_confirmation: [] as File[], // EventRequest doesn't have this as files
|
||||||
|
expected_attendance: request.expected_attendance || 0,
|
||||||
|
food_drinks_being_served: request.food_drinks_being_served || false,
|
||||||
|
needs_as_funding: request.as_funding_required || false,
|
||||||
|
as_funding_required: request.as_funding_required || false,
|
||||||
|
invoice: null,
|
||||||
|
invoice_files: [],
|
||||||
|
invoiceData: invoiceData,
|
||||||
|
needs_graphics: request.flyers_needed || false,
|
||||||
|
status: request.status || '',
|
||||||
|
created_by: request.requested_user || '',
|
||||||
|
id: request.id || '',
|
||||||
|
created: request.created || '',
|
||||||
|
updated: request.updated || '',
|
||||||
|
itemized_invoice: request.itemized_invoice || '',
|
||||||
|
} as unknown as EventRequestFormData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error converting EventRequest to EventRequestFormData:", error);
|
||||||
|
|
||||||
|
// Return a minimal valid object to prevent rendering errors
|
||||||
|
return {
|
||||||
|
name: request?.name || "Unknown Event",
|
||||||
|
location: request?.location || "",
|
||||||
|
start_date_time: request?.start_date_time || new Date().toISOString(),
|
||||||
|
end_date_time: request?.end_date_time || new Date().toISOString(),
|
||||||
|
event_description: request?.event_description || "",
|
||||||
|
flyers_needed: false,
|
||||||
|
photography_needed: false,
|
||||||
|
flyer_type: [],
|
||||||
|
other_flyer_type: "",
|
||||||
|
flyer_advertising_start_date: "",
|
||||||
|
advertising_format: "",
|
||||||
|
required_logos: [],
|
||||||
|
other_logos: [] as File[],
|
||||||
|
flyer_additional_requests: "",
|
||||||
|
will_or_have_room_booking: false,
|
||||||
|
room_booking: null,
|
||||||
|
room_booking_confirmation: [] as File[],
|
||||||
|
expected_attendance: 0,
|
||||||
|
food_drinks_being_served: false,
|
||||||
|
needs_as_funding: false,
|
||||||
|
as_funding_required: false,
|
||||||
|
invoice: null,
|
||||||
|
invoice_files: [],
|
||||||
|
invoiceData: {},
|
||||||
|
needs_graphics: false,
|
||||||
|
status: request?.status || "",
|
||||||
|
created_by: "",
|
||||||
|
id: request?.id || "",
|
||||||
|
created: request?.created || "",
|
||||||
|
updated: request?.updated || "",
|
||||||
|
itemized_invoice: ""
|
||||||
|
} as unknown as EventRequestFormData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
interface UserEventRequestsProps {
|
interface UserEventRequestsProps {
|
||||||
eventRequests: EventRequest[];
|
eventRequests: EventRequest[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a portal component for the modal to ensure it's rendered at the root level
|
||||||
|
const EventRequestModal: React.FC<{ isOpen: boolean, onClose: () => void, children: React.ReactNode }> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[99999]"
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '1rem',
|
||||||
|
margin: 0,
|
||||||
|
overflow: 'auto'
|
||||||
|
}}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-base-100 rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-y-auto"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
margin: 'auto',
|
||||||
|
zIndex: 100000
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: initialEventRequests }) => {
|
const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: initialEventRequests }) => {
|
||||||
const [eventRequests, setEventRequests] = useState<EventRequest[]>(initialEventRequests);
|
const [eventRequests, setEventRequests] = useState<EventRequest[]>(initialEventRequests);
|
||||||
const [selectedRequest, setSelectedRequest] = useState<EventRequest | null>(null);
|
const [selectedRequest, setSelectedRequest] = useState<EventRequest | null>(null);
|
||||||
|
@ -191,8 +329,8 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
|
||||||
|
@ -280,15 +418,17 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-ghost btn-sm rounded-full"
|
className="btn btn-sm btn-outline"
|
||||||
onClick={() => openDetailModal(request)}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openDetailModal(request);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
View Details
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
@ -339,14 +479,19 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="card-actions justify-end mt-4">
|
<div className="card-actions justify-end mt-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary btn-sm"
|
className="btn btn-sm btn-outline"
|
||||||
onClick={() => openDetailModal(request)}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openDetailModal(request);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
View Details
|
View Details
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -381,382 +526,45 @@ const UserEventRequests: React.FC<UserEventRequestsProps> = ({ eventRequests: in
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Event Request Detail Modal */}
|
{/* Use the new portal component for the modal */}
|
||||||
<AnimatePresence>
|
|
||||||
{isModalOpen && selectedRequest && (
|
{isModalOpen && selectedRequest && (
|
||||||
<motion.div
|
<EventRequestModal
|
||||||
initial={{ opacity: 0 }}
|
isOpen={isModalOpen}
|
||||||
animate={{ opacity: 1 }}
|
onClose={closeModal}
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60 backdrop-blur-sm"
|
|
||||||
onClick={closeModal}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
||||||
transition={{ type: "spring", damping: 25, stiffness: 300 }}
|
|
||||||
className="bg-base-100 rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-y-auto"
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
>
|
||||||
<div className="sticky top-0 z-10 bg-base-100 px-6 py-4 border-b border-base-300 flex justify-between items-center">
|
<div className="sticky top-0 z-10 bg-base-100 px-6 py-4 border-b border-base-300 flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="text-xl font-bold">{selectedRequest.name}</h2>
|
<h2 className="text-xl font-bold text-base-content">{selectedRequest.name}</h2>
|
||||||
<span className={`badge ${getStatusBadge(selectedRequest.status)}`}>
|
<span className={`badge ${getStatusBadge(selectedRequest.status)}`}>
|
||||||
{selectedRequest.status || 'Pending'}
|
{selectedRequest.status || 'Pending'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-primary"
|
className="btn btn-sm btn-circle"
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
// console.log('Full Preview button clicked', selectedRequest);
|
|
||||||
try {
|
|
||||||
// Direct call to the global function
|
|
||||||
if (typeof window.showEventRequestFormPreview === 'function') {
|
|
||||||
window.showEventRequestFormPreview(selectedRequest);
|
|
||||||
} else {
|
|
||||||
// console.log('Fallback: showEventRequestPreviewModal event dispatched');
|
|
||||||
// Fallback to event dispatch if function is not available
|
|
||||||
const event = new CustomEvent("showEventRequestPreviewModal", {
|
|
||||||
detail: { formData: selectedRequest }
|
|
||||||
});
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
// console.log('Fallback: showEventRequestPreviewModal event dispatched');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing full preview:', error);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Full Preview
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-circle btn-ghost"
|
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
>
|
>
|
||||||
✕
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
{selectedRequest ? (
|
||||||
<div>
|
<EventRequestFormPreview
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-primary">
|
formData={convertToFormData(selectedRequest)}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
isModal={true}
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
/>
|
||||||
</svg>
|
) : (
|
||||||
Event Details
|
<div className="flex items-center justify-center h-64">
|
||||||
</h3>
|
<div className="loading loading-spinner loading-lg text-primary"></div>
|
||||||
<div className="space-y-4 bg-base-200/50 p-4 rounded-lg">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Event Name</p>
|
|
||||||
<p className="font-medium">{selectedRequest.name}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Location</p>
|
|
||||||
<p className="font-medium">{selectedRequest.location}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Start Date & Time</p>
|
|
||||||
<p className="font-medium">{formatDate(selectedRequest.start_date_time)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">End Date & Time</p>
|
|
||||||
<p className="font-medium">{formatDate(selectedRequest.end_date_time)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Room Booking</p>
|
|
||||||
<p className="font-medium">{selectedRequest.will_or_have_room_booking ? 'Yes' : 'No'}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Expected Attendance</p>
|
|
||||||
<p className="font-medium">{selectedRequest.expected_attendance || 'Not specified'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-primary">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
||||||
</svg>
|
|
||||||
Event Description
|
|
||||||
</h3>
|
|
||||||
<div className="bg-base-200/50 p-4 rounded-lg h-full">
|
|
||||||
<p className="whitespace-pre-line">{selectedRequest.event_description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedRequest.flyers_needed && (
|
|
||||||
<div className="mb-8">
|
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-primary">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
|
||||||
</svg>
|
|
||||||
PR Materials
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 bg-base-200/50 p-4 rounded-lg">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Flyer Types</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{selectedRequest.flyer_type?.join(', ') || 'Not specified'}
|
|
||||||
{selectedRequest.other_flyer_type && ` (${selectedRequest.other_flyer_type})`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Advertising Start Date</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{selectedRequest.flyer_advertising_start_date
|
|
||||||
? formatDate(selectedRequest.flyer_advertising_start_date)
|
|
||||||
: 'Not specified'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Required Logos</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{selectedRequest.required_logos?.join(', ') || 'None'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Advertising Format</p>
|
|
||||||
<p className="font-medium">{selectedRequest.advertising_format || 'Not specified'}</p>
|
|
||||||
</div>
|
|
||||||
<div className="md:col-span-2">
|
|
||||||
<p className="text-sm text-base-content/60">Additional Requests</p>
|
|
||||||
<p className="font-medium whitespace-pre-line">
|
|
||||||
{selectedRequest.flyer_additional_requests || 'None'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedRequest.as_funding_required && (
|
|
||||||
<div className="mb-8">
|
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-primary">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
|
||||||
AS Funding Details
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-4 bg-base-200/50 p-4 rounded-lg">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Food/Drinks Being Served</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{selectedRequest.food_drinks_being_served ? 'Yes' : 'No'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{selectedRequest.invoice_data && (
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Vendor</p>
|
|
||||||
<p className="font-medium">
|
|
||||||
{selectedRequest.invoice_data.vendor || 'Not specified'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Itemized Invoice</p>
|
|
||||||
{(() => {
|
|
||||||
try {
|
|
||||||
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 {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
</EventRequestModal>
|
||||||
} 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 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-8 pt-4 border-t border-base-300">
|
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-base-content/60">Submission Date</p>
|
|
||||||
<p className="font-medium">{formatDate(selectedRequest.created)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<p className="text-sm text-base-content/60">Status:</p>
|
|
||||||
<span className={`badge ${getStatusBadge(selectedRequest.status)} badge-lg`}>
|
|
||||||
{selectedRequest.status || 'Pending'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -547,7 +547,7 @@ export default function ReimbursementList() {
|
||||||
? 'bg-success text-success-content ring-2 ring-success/20'
|
? 'bg-success text-success-content ring-2 ring-success/20'
|
||||||
: status === 'in_progress'
|
: status === 'in_progress'
|
||||||
? 'bg-warning text-warning-content ring-2 ring-warning/20'
|
? 'bg-warning text-warning-content ring-2 ring-warning/20'
|
||||||
: 'bg-primary text-primary-content ring-2 ring-primary/20'
|
: 'bg-primary text-white ring-2 ring-primary/20'
|
||||||
: isActive
|
: isActive
|
||||||
? status === 'rejected'
|
? status === 'rejected'
|
||||||
? 'bg-error/20 text-error'
|
? 'bg-error/20 text-error'
|
||||||
|
|
Loading…
Reference in a new issue