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

@ -33,11 +33,23 @@ let allEventRequests: ExtendedEventRequest[] = [];
let error = null;
try {
// 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
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",
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 [];
});
console.log(
`Fetched ${allEventRequests.length} event requests in Astro component`
);
// Process the event requests to add the requested_user_expand property
@ -81,16 +93,20 @@ try {
</style>
<div class="mb-8">
<h1 class="text-3xl font-bold text-white mb-2">Event Request Management</h1>
<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.
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>
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>
@ -142,4 +158,35 @@ try {
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);
// 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