fix event request form
This commit is contained in:
parent
62c4c5f735
commit
38e45f3cb3
5 changed files with 231 additions and 142 deletions
|
@ -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');
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue