From d4fe0bf2b0286f5afad2c46cf97e68344f8df768 Mon Sep 17 00:00:00 2001 From: chark1es Date: Wed, 26 Feb 2025 20:24:42 -0800 Subject: [PATCH] idk i forgot --- .vscode/settings.json | 7 + .../UserEventRequests.tsx | 4 +- .../Officer_EventRequestManagement.astro | 145 +++++ .../EventRequestDetails.tsx | 535 +++++++++++++++++ .../EventRequestManagementTable.tsx | 560 ++++++++++++++++++ src/config/dashboard.yaml | 11 +- src/scripts/pocketbase/Get.ts | 2 + 7 files changed, 1260 insertions(+), 4 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/components/dashboard/Officer_EventRequestManagement.astro create mode 100644 src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx create mode 100644 src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5e901d0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#221489", + "titleBar.activeBackground": "#301DC0", + "titleBar.activeForeground": "#F9F9FE" + } +} \ No newline at end of file diff --git a/src/components/dashboard/Officer_EventRequestForm/UserEventRequests.tsx b/src/components/dashboard/Officer_EventRequestForm/UserEventRequests.tsx index 9509d73..c01ff73 100644 --- a/src/components/dashboard/Officer_EventRequestForm/UserEventRequests.tsx +++ b/src/components/dashboard/Officer_EventRequestForm/UserEventRequests.tsx @@ -111,7 +111,7 @@ const UserEventRequests: React.FC = ({ eventRequests: in return 'badge-error'; case 'pending': return 'badge-warning'; - case 'in progress': + case 'submitted': return 'badge-info'; default: return 'badge-warning'; @@ -255,7 +255,7 @@ const UserEventRequests: React.FC = ({ eventRequests: in {formatDate(request.created)} - {request.status || 'Pending'} + {request.status || 'Submitted'} diff --git a/src/components/dashboard/Officer_EventRequestManagement.astro b/src/components/dashboard/Officer_EventRequestManagement.astro new file mode 100644 index 0000000..ef23d55 --- /dev/null +++ b/src/components/dashboard/Officer_EventRequestManagement.astro @@ -0,0 +1,145 @@ +--- +import { Authentication } from "../../scripts/pocketbase/Authentication"; +import { Get } from "../../scripts/pocketbase/Get"; +import { Toaster } from "react-hot-toast"; +import EventRequestManagementTable from "./Officer_EventRequestManagement/EventRequestManagementTable"; + +// Get instances +const get = Get.getInstance(); +const auth = Authentication.getInstance(); + +// Define the EventRequest interface +interface EventRequest { + id: string; + name: string; + location: string; + start_date_time: string; + end_date_time: string; + event_description: string; + flyers_needed: boolean; + photography_needed: boolean; + as_funding_required: boolean; + food_drinks_being_served: boolean; + created: string; + updated: string; + status: string; + requested_user: string; + requested_user_expand?: { + name: string; + email: string; + }; + 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 +let allEventRequests: EventRequest[] = []; +let error = null; + +// Fetch all event requests if authenticated +if (auth.isAuthenticated()) { + try { + // Expand the requested_user field to get user details + allEventRequests = await get.getAll( + "event_request", + "", + "-created", + { + fields: ["*"], + expand: ["requested_user"], + }, + ); + } catch (err) { + console.error("Failed to fetch event requests:", err); + error = "Failed to load event requests. Please try again later."; + } +} +--- + +
+ + +
+

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 new file mode 100644 index 0000000..b53b1eb --- /dev/null +++ b/src/components/dashboard/Officer_EventRequestManagement/EventRequestDetails.tsx @@ -0,0 +1,535 @@ +import React, { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import toast from 'react-hot-toast'; + +// Define the EventRequest interface +interface EventRequest { + id: string; + name: string; + location: string; + start_date_time: string; + end_date_time: string; + event_description: string; + flyers_needed: boolean; + photography_needed: boolean; + as_funding_required: boolean; + food_drinks_being_served: boolean; + created: string; + updated: string; + status: string; + requested_user: string; + requested_user_expand?: { + name: string; + email: string; + }; + flyer_type?: string[]; + other_flyer_type?: string; + flyer_advertising_start_date?: string; + flyer_additional_requests?: string; + required_logos?: string[]; + advertising_format?: string; + will_or_have_room_booking?: boolean; + expected_attendance?: number; + itemized_invoice?: string; + invoice_data?: string | any; + feedback?: string; +} + +interface EventRequestDetailsProps { + request: EventRequest; + onClose: () => void; + onStatusChange: (id: string, status: string) => Promise; + onFeedbackChange: (id: string, feedback: string) => Promise; +} + +// Separate component for AS Funding tab to isolate any issues +const ASFundingTab: React.FC<{ request: EventRequest }> = ({ request }) => { + if (!request.as_funding_required) { + return ( +
+

AS Funding Required

+

No

+
+ ); + } + + // Process invoice data for display + let invoiceData = request.invoice_data; + + // If invoice_data is not available, try to parse itemized_invoice + if (!invoiceData && request.itemized_invoice) { + try { + if (typeof request.itemized_invoice === 'string') { + invoiceData = JSON.parse(request.itemized_invoice); + } else if (typeof request.itemized_invoice === 'object') { + invoiceData = request.itemized_invoice; + } + } catch (e) { + console.error('Failed to parse itemized_invoice:', e); + } + } + + return ( +
+
+

AS Funding Required

+

Yes

+
+ + {request.food_drinks_being_served && ( +
+

Food/Drinks Being Served

+

Yes

+
+ )} + +
+

Invoice Data

+ +
+
+ ); +}; + +// Separate component for invoice table +const InvoiceTable: React.FC<{ invoiceData: any }> = ({ invoiceData }) => { + try { + // Parse invoice data if it's a string + let parsedInvoice = null; + + if (typeof invoiceData === 'string') { + try { + parsedInvoice = JSON.parse(invoiceData); + } catch (e) { + console.error('Failed to parse invoice data string:', e); + return ( +
+ + Invalid invoice data format. +
+ ); + } + } else if (typeof invoiceData === 'object' && invoiceData !== null) { + parsedInvoice = invoiceData; + } + + // Check if we have valid invoice data + if (!parsedInvoice || typeof parsedInvoice !== 'object') { + return ( +
+ + No structured invoice data available. +
+ ); + } + + // Extract items array + let items = []; + if (parsedInvoice.items && Array.isArray(parsedInvoice.items)) { + items = parsedInvoice.items; + } else if (Array.isArray(parsedInvoice)) { + items = parsedInvoice; + } else if (parsedInvoice.items && typeof parsedInvoice.items === 'object') { + items = [parsedInvoice.items]; // Wrap single item in array + } else { + // Try to find any array in the object + for (const key in parsedInvoice) { + if (Array.isArray(parsedInvoice[key])) { + items = parsedInvoice[key]; + break; + } + } + } + + // If we still don't have items, check if the object itself looks like an item + if (items.length === 0 && parsedInvoice.item || parsedInvoice.description || parsedInvoice.name) { + items = [parsedInvoice]; + } + + // If we still don't have items, show a message + if (items.length === 0) { + return ( +
+ + No invoice items found in the data. +
+ ); + } + + // Calculate subtotal from items + const subtotal = items.reduce((sum: number, item: any) => { + const quantity = parseFloat(item?.quantity || 1); + const price = parseFloat(item?.unit_price || item?.price || 0); + return sum + (quantity * price); + }, 0); + + // Get tax, tip and total + const tax = parseFloat(parsedInvoice.tax || parsedInvoice.taxAmount || 0); + const tip = parseFloat(parsedInvoice.tip || parsedInvoice.tipAmount || 0); + const total = parseFloat(parsedInvoice.total || 0) || (subtotal + tax + tip); + + // Render the invoice table + return ( +
+ + + + + + + + + + + {items.map((item: any, index: number) => { + // Ensure we're not trying to render an object directly + const itemName = typeof item?.item === 'object' + ? JSON.stringify(item.item) + : (item?.item || item?.description || item?.name || 'N/A'); + + const quantity = parseFloat(item?.quantity || 1); + const unitPrice = parseFloat(item?.unit_price || item?.price || 0); + const itemTotal = quantity * unitPrice; + + return ( + + + + + + + ); + })} + + + + + + + {tax > 0 && ( + + + + + )} + {tip > 0 && ( + + + + + )} + + + + + +
ItemQuantityPriceTotal
{itemName}{quantity}${unitPrice.toFixed(2)}${itemTotal.toFixed(2)}
Subtotal:${subtotal.toFixed(2)}
Tax:${tax.toFixed(2)}
Tip:${tip.toFixed(2)}
Total:${total.toFixed(2)}
+ {parsedInvoice.vendor && ( +
+ Vendor: {parsedInvoice.vendor} +
+ )} +
+ ); + } catch (error) { + console.error('Error rendering invoice table:', error); + return ( +
+ + An unexpected error occurred while processing the invoice. +
+ ); + } +}; + +const EventRequestDetails: React.FC = ({ + request, + onClose, + onStatusChange, + onFeedbackChange +}) => { + 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 [isStatusChanging, setIsStatusChanging] = useState(false); + + // Format date for display + const formatDate = (dateString: string) => { + if (!dateString) return 'Not specified'; + try { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + } catch (e) { + return dateString; + } + }; + + // Get status badge class based on status + const getStatusBadge = (status?: string) => { + if (!status) return 'badge-warning'; + + switch (status.toLowerCase()) { + case 'completed': + return 'badge-success'; + case 'declined': + return 'badge-error'; + case 'pending': + return 'badge-warning'; + default: + return 'badge-warning'; + } + }; + + // Handle saving feedback + const handleSaveFeedback = async () => { + if (feedback === request.feedback) { + toast('No changes to save', { icon: 'ℹ️' }); + return; + } + + setIsSaving(true); + const success = await onFeedbackChange(request.id, feedback); + setIsSaving(false); + + if (success) { + toast.success('Feedback saved successfully'); + } + }; + + // Handle status change + const handleStatusChange = async (status: string) => { + setIsStatusChanging(true); + await onStatusChange(request.id, status); + setStatus(status); + setIsStatusChanging(false); + }; + + return ( +
+ + {/* Header */} +
+

{request.name}

+ +
+ + {/* Status and controls */} +
+
+
+ Status: + + {status || 'Pending'} + +
+
+ Requested by: {request.requested_user_expand?.name || request.requested_user || 'Unknown'} +
+
+ +
+ + {/* Tabs */} + + + {/* Content */} +
+ {/* Event Details Tab */} + {activeTab === 'details' && ( +
+
+
+
+

Event Name

+

{request.name}

+
+
+

Location

+

{request.location || 'Not specified'}

+
+
+

Start Date & Time

+

{formatDate(request.start_date_time)}

+
+
+

End Date & Time

+

{formatDate(request.end_date_time)}

+
+
+

Expected Attendance

+

{request.expected_attendance || 'Not specified'}

+
+
+
+
+

Event Description

+

{request.event_description || 'No description provided'}

+
+
+

Room Booking

+

{request.will_or_have_room_booking ? 'Yes' : 'No'}

+
+
+

Food/Drinks Served

+

{request.food_drinks_being_served ? 'Yes' : 'No'}

+
+
+

Submission Date

+

{formatDate(request.created)}

+
+
+
+
+ )} + + {/* PR Materials Tab */} + {activeTab === 'pr' && ( +
+
+
+
+

Flyers Needed

+

{request.flyers_needed ? 'Yes' : 'No'}

+
+ {request.flyers_needed && ( + <> +
+

Flyer Types

+
    + {request.flyer_type?.map((type, index) => ( +
  • {type}
  • + ))} + {request.other_flyer_type &&
  • {request.other_flyer_type}
  • } +
+
+
+

Advertising Start Date

+

{formatDate(request.flyer_advertising_start_date || '')}

+
+
+

Advertising Format

+

{request.advertising_format || 'Not specified'}

+
+ + )} +
+
+
+

Photography Needed

+

{request.photography_needed ? 'Yes' : 'No'}

+
+ {request.flyers_needed && ( + <> +
+

Required Logos

+
    + {request.required_logos?.map((logo, index) => ( +
  • {logo}
  • + ))} + {(!request.required_logos || request.required_logos.length === 0) && +
  • No specific logos required
  • + } +
+
+
+

Additional Requests

+

{request.flyer_additional_requests || 'None'}

+
+ + )} +
+
+
+ )} + + {/* AS Funding Tab */} + {activeTab === 'funding' && ( + + )} +
+ + {/* Feedback section */} +
+

Feedback for Requester

+
+ +
+ +
+
+
+
+
+ ); +}; + +export default EventRequestDetails; \ No newline at end of file diff --git a/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx b/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx new file mode 100644 index 0000000..86a2735 --- /dev/null +++ b/src/components/dashboard/Officer_EventRequestManagement/EventRequestManagementTable.tsx @@ -0,0 +1,560 @@ +import React, { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Get } from '../../../scripts/pocketbase/Get'; +import { Update } from '../../../scripts/pocketbase/Update'; +import { Authentication } from '../../../scripts/pocketbase/Authentication'; +import toast from 'react-hot-toast'; +import EventRequestDetails from './EventRequestDetails'; + +// Define the EventRequest interface +interface EventRequest { + id: string; + name: string; + location: string; + start_date_time: string; + end_date_time: string; + event_description: string; + flyers_needed: boolean; + photography_needed: boolean; + as_funding_required: boolean; + food_drinks_being_served: boolean; + created: string; + updated: string; + status: string; + requested_user: string; + requested_user_expand?: { + name: string; + email: string; + }; + expand?: { + requested_user?: { + id: string; + name: string; + email: string; + [key: string]: any; + }; + [key: string]: any; + }; + flyer_type?: string[]; + other_flyer_type?: string; + flyer_advertising_start_date?: string; + flyer_additional_requests?: string; + required_logos?: string[]; + advertising_format?: string; + will_or_have_room_booking?: boolean; + expected_attendance?: number; + itemized_invoice?: string; + invoice_data?: any; + feedback?: string; +} + +interface EventRequestManagementTableProps { + eventRequests: EventRequest[]; +} + +const EventRequestManagementTable: React.FC = ({ eventRequests: initialEventRequests }) => { + const [eventRequests, setEventRequests] = useState(initialEventRequests); + const [filteredRequests, setFilteredRequests] = useState(initialEventRequests); + const [selectedRequest, setSelectedRequest] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isRefreshing, setIsRefreshing] = useState(false); + const [statusFilter, setStatusFilter] = useState('all'); + const [searchTerm, setSearchTerm] = useState(''); + const [sortField, setSortField] = useState('created'); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + + // Refresh event requests + const refreshEventRequests = async () => { + setIsRefreshing(true); + const refreshToast = toast.loading('Refreshing event requests...'); + + try { + 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; + } + + const updatedRequests = await get.getAll( + 'event_request', + '', + '-created', + { + fields: ['*'], + expand: ['requested_user'] + } + ); + + 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 }); + } finally { + setIsRefreshing(false); + } + }; + + // Apply filters and sorting + const applyFilters = (requests = eventRequests) => { + let filtered = [...requests]; + + // Apply status filter + if (statusFilter !== 'all') { + filtered = filtered.filter(request => + request.status?.toLowerCase() === statusFilter.toLowerCase() + ); + } + + // Apply search filter + if (searchTerm) { + const term = searchTerm.toLowerCase(); + filtered = filtered.filter(request => + request.name.toLowerCase().includes(term) || + request.location.toLowerCase().includes(term) || + request.event_description.toLowerCase().includes(term) || + request.expand?.requested_user?.name?.toLowerCase().includes(term) || + request.expand?.requested_user?.email?.toLowerCase().includes(term) + ); + } + + // Apply sorting + filtered.sort((a, b) => { + let aValue: any = a[sortField as keyof EventRequest]; + let bValue: any = b[sortField as keyof EventRequest]; + + // Handle special cases + if (sortField === 'requested_user') { + aValue = a.expand?.requested_user?.name || ''; + bValue = b.expand?.requested_user?.name || ''; + } + + // Handle date fields + if (sortField === 'created' || sortField === 'updated' || + sortField === 'start_date_time' || sortField === 'end_date_time') { + aValue = new Date(aValue || '').getTime(); + bValue = new Date(bValue || '').getTime(); + } + + // Compare values based on sort direction + if (sortDirection === 'asc') { + return aValue > bValue ? 1 : -1; + } else { + return aValue < bValue ? 1 : -1; + } + }); + + setFilteredRequests(filtered); + }; + + // Update event request status + const updateEventRequestStatus = async (id: string, status: string) => { + const updateToast = toast.loading(`Updating status to ${status}...`); + + try { + const update = Update.getInstance(); + const result = await update.updateField('event_request', id, 'status', status); + + // Update local state + setEventRequests(prev => + prev.map(request => + request.id === id ? { ...request, status } : request + ) + ); + + setFilteredRequests(prev => + prev.map(request => + request.id === id ? { ...request, status } : request + ) + ); + + // Update selected request if open + if (selectedRequest && selectedRequest.id === id) { + setSelectedRequest({ ...selectedRequest, status }); + } + + toast.success(`Status updated to ${status}`, { id: updateToast }); + } catch (err) { + console.error('Failed to update event request status:', err); + toast.error('Failed to update status. Please try again.', { id: updateToast }); + } + }; + + // Add feedback to event request + const addFeedback = async (id: string, feedback: string) => { + const feedbackToast = toast.loading('Saving feedback...'); + + try { + const update = Update.getInstance(); + const result = await update.updateField('event_request', id, 'feedback', feedback); + + // Update local state + setEventRequests(prev => + prev.map(request => + request.id === id ? { ...request, feedback } : request + ) + ); + + setFilteredRequests(prev => + prev.map(request => + request.id === id ? { ...request, feedback } : request + ) + ); + + toast.success('Feedback saved successfully', { id: feedbackToast }); + return true; + } catch (err) { + console.error('Failed to save feedback:', err); + toast.error('Failed to save feedback. Please try again.', { id: feedbackToast }); + return false; + } + }; + + // Format date for display + const formatDate = (dateString: string) => { + if (!dateString) return 'Not specified'; + try { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + } catch (e) { + return dateString; + } + }; + + // Get status badge class based on status + const getStatusBadge = (status?: string) => { + if (!status) return 'badge-warning'; + + switch (status.toLowerCase()) { + case 'completed': + return 'badge-success'; + case 'declined': + return 'badge-error'; + case 'pending': + return 'badge-warning'; + default: + return 'badge-warning'; + } + }; + + // Open modal with event request details + const openDetailModal = (request: EventRequest) => { + setSelectedRequest(request); + setIsModalOpen(true); + }; + + // Close modal + const closeModal = () => { + setIsModalOpen(false); + setSelectedRequest(null); + }; + + // Handle sort change + const handleSortChange = (field: string) => { + if (sortField === field) { + // Toggle direction if same field + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + // Set new field and default to descending + setSortField(field); + setSortDirection('desc'); + } + }; + + // Apply filters when filter state changes + useEffect(() => { + applyFilters(); + }, [statusFilter, searchTerm, sortField, sortDirection]); + + // Auto refresh on component mount + useEffect(() => { + refreshEventRequests(); + }, []); + + if (filteredRequests.length === 0) { + return ( + +
+ + + +

No Event Requests Found

+

+ {statusFilter !== 'all' || searchTerm + ? 'No event requests match your current filters. Try adjusting your search criteria.' + : 'There are no event requests in the system yet.'} +

+
+ + {(statusFilter !== 'all' || searchTerm) && ( + + )} +
+
+
+ ); + } + + return ( + + {/* Filters and controls */} +
+
+
+
+ setSearchTerm(e.target.value)} + /> + +
+
+ +
+
+ + {filteredRequests.length} {filteredRequests.length === 1 ? 'request' : 'requests'} found + + +
+
+ + {/* Event requests table */} +
+ + + + + + + + + + + + + + + {filteredRequests.map((request) => ( + + + + + + + + + + + ))} + +
handleSortChange('name')} + > +
+ Event Name + {sortField === 'name' && ( + + + + )} +
+
handleSortChange('start_date_time')} + > +
+ Date + {sortField === 'start_date_time' && ( + + + + )} +
+
handleSortChange('requested_user')} + > +
+ Requested By + {sortField === 'requested_user' && ( + + + + )} +
+
PR MaterialsAS Funding handleSortChange('created')} + > +
+ Submitted + {sortField === 'created' && ( + + + + )} +
+
handleSortChange('status')} + > +
+ Status + {sortField === 'status' && ( + + + + )} +
+
Actions
{request.name}{formatDate(request.start_date_time)} +
+ {request.expand?.requested_user?.name || 'Unknown'} + {request.expand?.requested_user?.email} +
+
+ {request.flyers_needed ? ( + Yes + ) : ( + No + )} + + {request.as_funding_required ? ( + Yes + ) : ( + No + )} + {formatDate(request.created)} + + {request.status || 'Pending'} + + + +
+
+ + {/* Event request details modal */} + + {isModalOpen && selectedRequest && ( + + )} + +
+ ); +}; + +export default EventRequestManagementTable; \ No newline at end of file diff --git a/src/config/dashboard.yaml b/src/config/dashboard.yaml index 408ae0f..4c3487e 100644 --- a/src/config/dashboard.yaml +++ b/src/config/dashboard.yaml @@ -32,10 +32,17 @@ sections: reimbursementManagement: title: "Reimbursement Management" icon: "heroicons:credit-card" - role: "general" + role: "executive" component: "Officer_ReimbursementManagement" class: "text-info hover:text-info-focus" + eventRequestManagement: + title: "Event Request Management" + icon: "heroicons:document-text" + role: "executive" + component: "Officer_EventRequestManagement" + class: "text-info hover:text-info-focus" + eventRequestForm: title: "Event Request Form" icon: "heroicons:document-text" @@ -86,7 +93,7 @@ categories: executive: title: "Executive Menu" - sections: ["reimbursementManagement"] + sections: ["reimbursementManagement", "eventRequestManagement"] role: "executive" admin: diff --git a/src/scripts/pocketbase/Get.ts b/src/scripts/pocketbase/Get.ts index c87a159..b206166 100644 --- a/src/scripts/pocketbase/Get.ts +++ b/src/scripts/pocketbase/Get.ts @@ -10,6 +10,7 @@ interface BaseRecord { interface RequestOptions { fields?: string[]; disableAutoCancellation?: boolean; + expand?: string[]; } // Utility function to check if a value is a UTC date string @@ -222,6 +223,7 @@ export class Get { ...(filter && { filter }), ...(sort && { sort }), ...(options?.fields && { fields: options.fields.join(",") }), + ...(options?.expand && { expand: options.expand.join(",") }), ...(options?.disableAutoCancellation && { requestKey: null }), };