fix event request form

This commit is contained in:
chark1es 2025-03-01 03:09:55 -08:00
parent 62c4c5f735
commit 38e45f3cb3
5 changed files with 231 additions and 142 deletions

View file

@ -442,12 +442,7 @@ const EventRequestForm: React.FC = () => {
return false;
}
// Check if there are items in the invoice
if (formData.invoiceData.items.length === 0) {
toast.error('Please add at least one item to the invoice');
return false;
}
// No longer require items in the invoice
// Check if at least one invoice file is uploaded
if (!formData.invoice && (!formData.invoice_files || formData.invoice_files.length === 0)) {
toast.error('Please upload at least one invoice file');

View file

@ -108,8 +108,8 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
newErrors.quantity = 'Quantity must be greater than 0';
}
if (newItem.unitPrice <= 0) {
newErrors.unitPrice = 'Unit price must be greater than 0';
if (newItem.unitPrice < 0) {
newErrors.unitPrice = 'Unit price must be 0 or greater';
}
setErrors(newErrors);
@ -183,7 +183,7 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
const value = parseFloat(e.target.value);
onChange({
...invoiceData,
taxRate: isNaN(value) ? 0 : value
taxRate: isNaN(value) ? 0 : Math.max(0, value)
});
};
@ -192,7 +192,7 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
const value = parseFloat(e.target.value);
onChange({
...invoiceData,
tipPercentage: isNaN(value) ? 0 : value
tipPercentage: isNaN(value) ? 0 : Math.max(0, value)
});
};
@ -341,7 +341,7 @@ const InvoiceBuilder: React.FC<InvoiceBuilderProps> = ({ invoiceData, onChange }
className={`input input-bordered input-sm ${errors.unitPrice ? 'input-error' : ''}`}
value={newItem.unitPrice}
onChange={(e) => setNewItem({ ...newItem, unitPrice: parseFloat(e.target.value) || 0 })}
min="0.01"
min="0"
step="0.01"
/>
{errors.unitPrice && (

View file

@ -11,21 +11,21 @@ const auth = Authentication.getInstance();
// Extended EventRequest interface with additional properties needed for this component
interface ExtendedEventRequest extends EventRequest {
requested_user_expand?: {
name: string;
email: string;
};
expand?: {
requested_user?: {
id: string;
name: string;
email: string;
[key: string]: any;
requested_user_expand?: {
name: string;
email: string;
};
[key: string]: any;
};
feedback?: string;
[key: string]: any; // For other optional properties
expand?: {
requested_user?: {
id: string;
name: string;
email: string;
[key: string]: any;
};
[key: string]: any;
};
feedback?: string;
[key: string]: any; // For other optional properties
}
// Initialize variables for all event requests
@ -33,113 +33,160 @@ let allEventRequests: ExtendedEventRequest[] = [];
let error = null;
try {
// Expand the requested_user field to get user details
allEventRequests = await get.getAll<ExtendedEventRequest>(
"event_request",
"",
"-created",
);
// Don't check authentication here - let the client component handle it
// The server-side check is causing issues when the token is valid client-side but not server-side
// Process the event requests to add the requested_user_expand property
allEventRequests = allEventRequests.map((request) => {
const requestWithExpand = { ...request };
console.log("Fetching event requests in Astro component...");
// Expand the requested_user field to get user details
allEventRequests = await get
.getAll<ExtendedEventRequest>("event_request", "", "-created", {
expand: ["requested_user"],
})
.catch((err) => {
console.error("Error in get.getAll:", err);
// Return empty array instead of throwing
return [];
});
// Add the requested_user_expand property if the expand data is available
if (
request.expand &&
request.expand.requested_user &&
request.expand.requested_user.name &&
request.expand.requested_user.email
) {
requestWithExpand.requested_user_expand = {
name: request.expand.requested_user.name,
email: request.expand.requested_user.email,
};
}
console.log(
`Fetched ${allEventRequests.length} event requests in Astro component`
);
return requestWithExpand;
});
// Process the event requests to add the requested_user_expand property
allEventRequests = allEventRequests.map((request) => {
const requestWithExpand = { ...request };
// Add the requested_user_expand property if the expand data is available
if (
request.expand &&
request.expand.requested_user &&
request.expand.requested_user.name &&
request.expand.requested_user.email
) {
requestWithExpand.requested_user_expand = {
name: request.expand.requested_user.name,
email: request.expand.requested_user.email,
};
}
return requestWithExpand;
});
} catch (err) {
console.error("Error fetching event requests:", err);
error = err;
console.error("Error fetching event requests:", err);
error = err;
}
---
<div class="w-full max-w-7xl mx-auto py-8 px-4">
<style>
.event-table-container {
min-height: 600px;
height: auto !important;
max-height: none !important;
}
.event-table-container table {
height: auto !important;
}
.event-table-container .overflow-x-auto {
max-height: none !important;
}
</style>
<style>
.event-table-container {
min-height: 600px;
height: auto !important;
max-height: none !important;
}
.event-table-container table {
height: auto !important;
}
.event-table-container .overflow-x-auto {
max-height: none !important;
}
</style>
<div class="mb-8">
<h1 class="text-3xl font-bold text-white mb-2">Event Request Management</h1>
<p class="text-gray-300 mb-4">
Review and manage event requests submitted by officers. Update status,
provide feedback, and coordinate with the team.
</p>
<div class="bg-base-300/50 p-4 rounded-lg text-sm text-gray-300">
<p class="font-medium mb-2">As an executive officer, you can:</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>View all submitted event requests</li>
<li>Update the status of requests (Pending, Completed, Declined)</li>
<li>Add comments or feedback for the requesting officer</li>
<li>Filter and sort requests by various criteria</li>
</ul>
</div>
</div>
{
error && (
<div class="alert alert-error mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 stroke-current shrink-0"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{error}</span>
</div>
)
}
{
!error && (
<div class="bg-base-200 rounded-lg shadow-xl min-h-[600px] event-table-container">
<div class="p-4 md:p-6 h-auto">
<EventRequestManagementTable
client:load
eventRequests={allEventRequests}
/>
<div class="mb-8">
<h1 class="text-3xl font-bold text-white mb-2">
Event Request Management
</h1>
<p class="text-gray-300 mb-4">
Review and manage event requests submitted by officers. Update
status, provide feedback, and coordinate with the team.
</p>
<div class="bg-base-300/50 p-4 rounded-lg text-sm text-gray-300">
<p class="font-medium mb-2">As an executive officer, you can:</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>View all submitted event requests</li>
<li>
Update the status of requests (Pending, Completed, Declined)
</li>
<li>Add comments or feedback for the requesting officer</li>
<li>Filter and sort requests by various criteria</li>
</ul>
</div>
</div>
)
}
</div>
<!-- Toast container for notifications -->
<Toaster client:load position="bottom-right" />
{
error && (
<div class="alert alert-error mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 stroke-current shrink-0"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{error}</span>
</div>
)
}
{
!error && (
<div class="bg-base-200 rounded-lg shadow-xl min-h-[600px] event-table-container">
<div class="p-4 md:p-6 h-auto">
<EventRequestManagementTable
client:load
eventRequests={allEventRequests}
/>
</div>
</div>
)
}
<!-- Toast container for notifications -->
<Toaster client:load position="bottom-right" />
</div>
<script>
// Refresh the page when the user navigates back to it
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
window.location.reload();
}
});
// Refresh the page when the user navigates back to it
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
window.location.reload();
}
});
// Handle authentication errors
document.addEventListener("DOMContentLoaded", () => {
// Check for error message in the UI
const errorElement = document.querySelector(".alert-error span");
if (
errorElement &&
errorElement.textContent?.includes("Authentication error")
) {
console.log(
"Authentication error detected in UI, redirecting to login..."
);
// Redirect to login page after a short delay
setTimeout(() => {
window.location.href = "/login";
}, 3000);
return;
}
// Also check if we have any event requests
const tableContainer = document.querySelector(".event-table-container");
if (tableContainer) {
console.log(
"Event table container found, component should load normally"
);
} else {
console.log(
"Event table container not found, might be an issue with rendering"
);
}
});
</script>

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import toast from 'react-hot-toast';
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase';
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase/schema';
// Extended EventRequest interface with additional properties needed for this component
interface ExtendedEventRequest extends SchemaEventRequest {
@ -16,7 +16,7 @@ interface ExtendedEventRequest extends SchemaEventRequest {
interface EventRequestDetailsProps {
request: ExtendedEventRequest;
onClose: () => void;
onStatusChange: (id: string, status: string) => Promise<void>;
onStatusChange: (id: string, status: "submitted" | "pending" | "completed" | "declined") => Promise<void>;
onFeedbackChange: (id: string, feedback: string) => Promise<boolean>;
}
@ -220,16 +220,16 @@ const InvoiceTable: React.FC<{ invoiceData: any }> = ({ invoiceData }) => {
}
};
const EventRequestDetails: React.FC<EventRequestDetailsProps> = ({
const EventRequestDetails = ({
request,
onClose,
onStatusChange,
onFeedbackChange
}) => {
}: EventRequestDetailsProps): React.ReactNode => {
const [feedback, setFeedback] = useState<string>(request.feedback || '');
const [isSaving, setIsSaving] = useState<boolean>(false);
const [activeTab, setActiveTab] = useState<'details' | 'pr' | 'funding'>('details');
const [status, setStatus] = useState(request.status);
const [status, setStatus] = useState<"submitted" | "pending" | "completed" | "declined">(request.status);
const [isStatusChanging, setIsStatusChanging] = useState(false);
// Format date for display
@ -250,16 +250,18 @@ const EventRequestDetails: React.FC<EventRequestDetailsProps> = ({
};
// Get status badge class based on status
const getStatusBadge = (status?: string) => {
const getStatusBadge = (status?: "submitted" | "pending" | "completed" | "declined") => {
if (!status) return 'badge-warning';
switch (status.toLowerCase()) {
switch (status) {
case 'completed':
return 'badge-success';
case 'declined':
return 'badge-error';
case 'pending':
return 'badge-warning';
case 'submitted':
return 'badge-info';
default:
return 'badge-warning';
}
@ -282,10 +284,10 @@ const EventRequestDetails: React.FC<EventRequestDetailsProps> = ({
};
// Handle status change
const handleStatusChange = async (status: string) => {
const handleStatusChange = async (newStatus: "submitted" | "pending" | "completed" | "declined") => {
setIsStatusChanging(true);
await onStatusChange(request.id, status);
setStatus(status);
await onStatusChange(request.id, newStatus);
setStatus(newStatus);
setIsStatusChanging(false);
};

View file

@ -5,7 +5,7 @@ import { Update } from '../../../scripts/pocketbase/Update';
import { Authentication } from '../../../scripts/pocketbase/Authentication';
import toast from 'react-hot-toast';
import EventRequestDetails from './EventRequestDetails';
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase';
import type { EventRequest as SchemaEventRequest } from '../../../schemas/pocketbase/schema';
// Extended EventRequest interface with additional properties needed for this component
interface ExtendedEventRequest extends SchemaEventRequest {
@ -24,6 +24,7 @@ interface ExtendedEventRequest extends SchemaEventRequest {
};
invoice_data?: any;
feedback?: string;
status: "submitted" | "pending" | "completed" | "declined";
}
interface EventRequestManagementTableProps {
@ -50,11 +51,10 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
const get = Get.getInstance();
const auth = Authentication.getInstance();
if (!auth.isAuthenticated()) {
toast.error('You must be logged in to refresh event requests', { id: refreshToast });
return;
}
// Don't check authentication here - try to fetch anyway
// The token might be valid for the API even if isAuthenticated() returns false
console.log("Fetching event requests...");
const updatedRequests = await get.getAll<ExtendedEventRequest>(
'event_request',
'',
@ -64,13 +64,26 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
expand: ['requested_user']
}
);
console.log(`Fetched ${updatedRequests.length} event requests`);
setEventRequests(updatedRequests);
applyFilters(updatedRequests);
toast.success('Event requests refreshed successfully', { id: refreshToast });
} catch (err) {
console.error('Failed to refresh event requests:', err);
toast.error('Failed to refresh event requests. Please try again.', { id: refreshToast });
// Check if it's an authentication error
if (err instanceof Error &&
(err.message.includes('authentication') ||
err.message.includes('auth') ||
err.message.includes('logged in'))) {
toast.error('Authentication error. Please log in again.', { id: refreshToast });
setTimeout(() => {
window.location.href = "/login";
}, 2000);
} else {
toast.error('Failed to refresh event requests. Please try again.', { id: refreshToast });
}
} finally {
setIsRefreshing(false);
}
@ -129,7 +142,7 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
};
// Update event request status
const updateEventRequestStatus = async (id: string, status: string) => {
const updateEventRequestStatus = async (id: string, status: "submitted" | "pending" | "completed" | "declined") => {
const updateToast = toast.loading(`Updating status to ${status}...`);
try {
@ -209,16 +222,18 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
};
// Get status badge class based on status
const getStatusBadge = (status?: string) => {
const getStatusBadge = (status?: "submitted" | "pending" | "completed" | "declined") => {
if (!status) return 'badge-warning';
switch (status.toLowerCase()) {
switch (status) {
case 'completed':
return 'badge-success';
case 'declined':
return 'badge-error';
case 'pending':
return 'badge-warning';
case 'submitted':
return 'badge-info';
default:
return 'badge-warning';
}
@ -253,6 +268,36 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
applyFilters();
}, [statusFilter, searchTerm, sortField, sortDirection]);
// Check authentication and refresh token if needed
useEffect(() => {
const checkAuth = async () => {
const auth = Authentication.getInstance();
// Check if we're authenticated
if (!auth.isAuthenticated()) {
console.log("Authentication check failed - attempting to continue anyway");
// Don't show error or redirect immediately - try to refresh first
try {
// Try to refresh event requests anyway - the token might be valid
await refreshEventRequests();
} catch (err) {
console.error("Failed to refresh after auth check:", err);
toast.error("Authentication error. Please log in again.");
// Only redirect if refresh fails
setTimeout(() => {
window.location.href = "/login";
}, 2000);
}
} else {
console.log("Authentication check passed");
}
};
checkAuth();
}, []);
// Auto refresh on component mount
useEffect(() => {
refreshEventRequests();
@ -498,9 +543,9 @@ const EventRequestManagementTable = ({ eventRequests: initialEventRequests }: Ev
Update
</label>
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52">
<li><a onClick={() => updateEventRequestStatus(request.id, 'Pending')}>Pending</a></li>
<li><a onClick={() => updateEventRequestStatus(request.id, 'Completed')}>Completed</a></li>
<li><a onClick={() => updateEventRequestStatus(request.id, 'Declined')}>Declined</a></li>
<li><a onClick={() => updateEventRequestStatus(request.id, "pending")}>Pending</a></li>
<li><a onClick={() => updateEventRequestStatus(request.id, "completed")}>Completed</a></li>
<li><a onClick={() => updateEventRequestStatus(request.id, "declined")}>Declined</a></li>
</ul>
</div>
<button