diff --git a/src/components/dashboard/Officer_EventRequestForm/EventRequestForm.tsx b/src/components/dashboard/Officer_EventRequestForm/EventRequestForm.tsx index 7293096..2e990b3 100644 --- a/src/components/dashboard/Officer_EventRequestForm/EventRequestForm.tsx +++ b/src/components/dashboard/Officer_EventRequestForm/EventRequestForm.tsx @@ -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'); diff --git a/src/components/dashboard/Officer_EventRequestForm/InvoiceBuilder.tsx b/src/components/dashboard/Officer_EventRequestForm/InvoiceBuilder.tsx index a6195f5..3110757 100644 --- a/src/components/dashboard/Officer_EventRequestForm/InvoiceBuilder.tsx +++ b/src/components/dashboard/Officer_EventRequestForm/InvoiceBuilder.tsx @@ -108,8 +108,8 @@ const InvoiceBuilder: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ 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 && ( diff --git a/src/components/dashboard/Officer_EventRequestManagement.astro b/src/components/dashboard/Officer_EventRequestManagement.astro index 9a6d818..8f417f0 100644 --- a/src/components/dashboard/Officer_EventRequestManagement.astro +++ b/src/components/dashboard/Officer_EventRequestManagement.astro @@ -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( - "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("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; } ---
- + -
-

Event Request Management

-

- Review and manage event requests submitted by officers. Update status, - provide feedback, and coordinate with the team. -

-
-

As an executive officer, you can:

-
    -
  • View all submitted event requests
  • -
  • Update the status of requests (Pending, Completed, Declined)
  • -
  • Add comments or feedback for the requesting officer
  • -
  • Filter and sort requests by various criteria
  • -
-
-
- - { - error && ( -
- - - - {error} -
- ) - } - - { - !error && ( -
-
- +
+

+ Event Request Management +

+

+ Review and manage event requests submitted by officers. Update + status, provide feedback, and coordinate with the team. +

+
+

As an executive officer, you can:

+
    +
  • View all submitted event requests
  • +
  • + Update the status of requests (Pending, Completed, Declined) +
  • +
  • Add comments or feedback for the requesting officer
  • +
  • Filter and sort requests by various criteria
  • +
-
- ) - } +
- - + { + error && ( +
+ + + + {error} +
+ ) + } + + { + !error && ( +
+
+ +
+
+ ) + } + + +
diff --git a/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx b/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx index f5fb585..d82046a 100644 --- a/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx +++ b/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx @@ -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; + onStatusChange: (id: string, status: "submitted" | "pending" | "completed" | "declined") => Promise; onFeedbackChange: (id: string, feedback: string) => Promise; } @@ -220,16 +220,16 @@ const InvoiceTable: React.FC<{ invoiceData: any }> = ({ invoiceData }) => { } }; -const EventRequestDetails: React.FC = ({ +const EventRequestDetails = ({ request, onClose, onStatusChange, onFeedbackChange -}) => { +}: EventRequestDetailsProps): React.ReactNode => { const [feedback, setFeedback] = useState(request.feedback || ''); const [isSaving, setIsSaving] = useState(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 = ({ }; // 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 = ({ }; // 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); }; diff --git a/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx b/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx index c3783ea..cb2b977 100644 --- a/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx +++ b/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx @@ -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( '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