fix file uploads
This commit is contained in:
parent
a57a4e6889
commit
4349b4d034
6 changed files with 307 additions and 105 deletions
|
@ -86,11 +86,26 @@ const ASFundingSection: React.FC<ASFundingSectionProps> = ({ formData, onDataCha
|
|||
const handleInvoiceFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const newFiles = Array.from(e.target.files) as File[];
|
||||
setInvoiceFiles(newFiles);
|
||||
onDataChange({ invoice_files: newFiles });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...invoiceFiles, ...newFiles];
|
||||
setInvoiceFiles(combinedFiles);
|
||||
onDataChange({ invoice_files: combinedFiles });
|
||||
}
|
||||
};
|
||||
|
||||
// Handle removing individual files
|
||||
const handleRemoveFile = (indexToRemove: number) => {
|
||||
const updatedFiles = invoiceFiles.filter((_, index) => index !== indexToRemove);
|
||||
setInvoiceFiles(updatedFiles);
|
||||
onDataChange({ invoice_files: updatedFiles });
|
||||
};
|
||||
|
||||
// Handle clearing all files
|
||||
const handleClearAllFiles = () => {
|
||||
setInvoiceFiles([]);
|
||||
onDataChange({ invoice_files: [] });
|
||||
};
|
||||
|
||||
// Handle JSON input change
|
||||
const handleJsonInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setJsonInput(e.target.value);
|
||||
|
@ -234,8 +249,10 @@ const ASFundingSection: React.FC<ASFundingSectionProps> = ({ formData, onDataCha
|
|||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
const newFiles = Array.from(e.dataTransfer.files) as File[];
|
||||
setInvoiceFiles(newFiles);
|
||||
onDataChange({ invoice_files: newFiles });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...invoiceFiles, ...newFiles];
|
||||
setInvoiceFiles(combinedFiles);
|
||||
onDataChange({ invoice_files: combinedFiles });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -311,20 +328,44 @@ const ASFundingSection: React.FC<ASFundingSectionProps> = ({ formData, onDataCha
|
|||
|
||||
{invoiceFiles.length > 0 ? (
|
||||
<>
|
||||
<p className="font-medium text-primary">{invoiceFiles.length} file(s) selected:</p>
|
||||
<div className="max-h-24 overflow-y-auto text-left w-full">
|
||||
<ul className="list-disc list-inside text-sm">
|
||||
{invoiceFiles.map((file, index) => (
|
||||
<li key={index} className="truncate">{file.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="font-medium text-primary">{invoiceFiles.length} file(s) selected:</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearAllFiles();
|
||||
}}
|
||||
className="btn btn-xs btn-outline btn-error"
|
||||
title="Clear all files"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Click or drag to replace</p>
|
||||
<div className="max-h-32 overflow-y-auto text-left w-full space-y-1">
|
||||
{invoiceFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between bg-base-100 p-2 rounded">
|
||||
<span className="text-sm truncate flex-1">{file.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveFile(index);
|
||||
}}
|
||||
className="btn btn-xs btn-error ml-2"
|
||||
title="Remove file"
|
||||
>
|
||||
<Icon icon="heroicons:x-mark" className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Click or drag to add more files</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="font-medium">Drop your invoice files here or click to browse</p>
|
||||
<p className="text-xs text-gray-500">Supports PDF, JPG, JPEG, PNG</p>
|
||||
<p className="text-xs text-gray-500">Supports PDF, JPG, JPEG, PNG (multiple files allowed)</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -70,13 +70,13 @@ export interface EventRequestFormData {
|
|||
flyer_advertising_start_date: string;
|
||||
flyer_additional_requests: string;
|
||||
required_logos: string[];
|
||||
other_logos: File[]; // Form uses File objects, schema uses strings
|
||||
other_logos: File[]; // Form uses File objects, schema uses strings - MULTIPLE FILES
|
||||
advertising_format: string;
|
||||
will_or_have_room_booking: boolean;
|
||||
expected_attendance: number;
|
||||
room_booking: File | null;
|
||||
room_booking_files: File[]; // CHANGED: Multiple room booking files instead of single
|
||||
invoice: File | null;
|
||||
invoice_files: File[];
|
||||
invoice_files: File[]; // MULTIPLE FILES
|
||||
invoiceData: InvoiceData;
|
||||
needs_graphics?: boolean | null;
|
||||
needs_as_funding?: boolean | null;
|
||||
|
@ -108,7 +108,7 @@ const EventRequestForm: React.FC = () => {
|
|||
advertising_format: '',
|
||||
will_or_have_room_booking: false,
|
||||
expected_attendance: 0,
|
||||
room_booking: null,
|
||||
room_booking_files: [],
|
||||
as_funding_required: false,
|
||||
food_drinks_being_served: false,
|
||||
itemized_invoice: '',
|
||||
|
@ -134,7 +134,7 @@ const EventRequestForm: React.FC = () => {
|
|||
const dataToStore = {
|
||||
...formDataToSave,
|
||||
other_logos: [],
|
||||
room_booking: null,
|
||||
room_booking_files: [],
|
||||
invoice: null,
|
||||
invoice_files: []
|
||||
};
|
||||
|
@ -184,7 +184,7 @@ const EventRequestForm: React.FC = () => {
|
|||
...updatedData,
|
||||
// Remove file objects before saving to localStorage
|
||||
other_logos: [],
|
||||
room_booking: null,
|
||||
room_booking_files: [],
|
||||
invoice: null,
|
||||
invoice_files: []
|
||||
};
|
||||
|
@ -221,7 +221,7 @@ const EventRequestForm: React.FC = () => {
|
|||
advertising_format: '',
|
||||
will_or_have_room_booking: false,
|
||||
expected_attendance: 0,
|
||||
room_booking: null, // No room booking by default
|
||||
room_booking_files: [],
|
||||
as_funding_required: false,
|
||||
food_drinks_being_served: false,
|
||||
itemized_invoice: '',
|
||||
|
@ -377,16 +377,28 @@ const EventRequestForm: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Upload room booking
|
||||
if (formData.room_booking) {
|
||||
// Upload room booking files
|
||||
if (formData.room_booking_files && formData.room_booking_files.length > 0) {
|
||||
try {
|
||||
console.log('Uploading room booking file:', { name: formData.room_booking.name, size: formData.room_booking.size, type: formData.room_booking.type });
|
||||
await fileManager.uploadFile('event_request', record.id, 'room_booking', formData.room_booking);
|
||||
console.log('Room booking file uploaded successfully');
|
||||
console.log('Uploading room booking files:', formData.room_booking_files.length, 'files');
|
||||
console.log('Room booking files:', formData.room_booking_files.map(f => ({ name: f.name, size: f.size, type: f.type })));
|
||||
console.log('Using collection:', 'event_request', 'record ID:', record.id, 'field:', 'room_booking');
|
||||
|
||||
// Use the correct field name 'room_booking' instead of 'room_booking_files'
|
||||
await fileManager.uploadFiles('event_request', record.id, 'room_booking', formData.room_booking_files);
|
||||
console.log('Room booking files uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to upload room booking:', error);
|
||||
console.error('Failed to upload room booking files:', error);
|
||||
console.error('Error details:', {
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
collection: 'event_request',
|
||||
recordId: record.id,
|
||||
field: 'room_booking',
|
||||
fileCount: formData.room_booking_files.length
|
||||
});
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
fileUploadErrors.push(`Failed to upload room booking file: ${errorMessage}`);
|
||||
fileUploadErrors.push(`Failed to upload room booking files: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,17 +407,21 @@ const EventRequestForm: React.FC = () => {
|
|||
try {
|
||||
console.log('Uploading invoice files:', formData.invoice_files.length, 'files');
|
||||
console.log('Invoice files:', formData.invoice_files.map(f => ({ name: f.name, size: f.size, type: f.type })));
|
||||
await fileManager.appendFiles('event_request', record.id, 'invoice_files', formData.invoice_files);
|
||||
console.log('Using collection:', 'event_request', 'record ID:', record.id, 'field:', 'invoice');
|
||||
|
||||
// For backward compatibility, also upload the first file as the main invoice
|
||||
if (formData.invoice || formData.invoice_files[0]) {
|
||||
const mainInvoice = formData.invoice || formData.invoice_files[0];
|
||||
console.log('Uploading main invoice file:', { name: mainInvoice.name, size: mainInvoice.size, type: mainInvoice.type });
|
||||
await fileManager.uploadFile('event_request', record.id, 'invoice', mainInvoice);
|
||||
}
|
||||
// Use the correct field name 'invoice' instead of 'invoice_files'
|
||||
await fileManager.uploadFiles('event_request', record.id, 'invoice', formData.invoice_files);
|
||||
console.log('Invoice files uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to upload invoice files:', error);
|
||||
console.error('Error details:', {
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
collection: 'event_request',
|
||||
recordId: record.id,
|
||||
field: 'invoice',
|
||||
fileCount: formData.invoice_files.length
|
||||
});
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
fileUploadErrors.push(`Failed to upload invoice files: ${errorMessage}`);
|
||||
}
|
||||
|
@ -416,6 +432,13 @@ const EventRequestForm: React.FC = () => {
|
|||
console.log('Invoice file uploaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to upload invoice file:', error);
|
||||
console.error('Error details:', {
|
||||
message: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
collection: 'event_request',
|
||||
recordId: record.id,
|
||||
field: 'invoice'
|
||||
});
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
fileUploadErrors.push(`Failed to upload invoice file: ${errorMessage}`);
|
||||
}
|
||||
|
@ -601,9 +624,9 @@ const EventRequestForm: React.FC = () => {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Only require room booking file if will_or_have_room_booking is true
|
||||
if (formData.will_or_have_room_booking && !formData.room_booking) {
|
||||
toast.error('Please upload your room booking confirmation');
|
||||
// REQUIRED: Room booking files if will_or_have_room_booking is true
|
||||
if (formData.will_or_have_room_booking && formData.room_booking_files.length === 0) {
|
||||
toast.error('Room booking files are required when you need a room booking');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -623,7 +646,13 @@ const EventRequestForm: React.FC = () => {
|
|||
|
||||
// Validate AS Funding Section
|
||||
const validateASFundingSection = () => {
|
||||
if (formData.as_funding_required) {
|
||||
if (formData.as_funding_required || formData.needs_as_funding) {
|
||||
// REQUIRED: Invoice files if AS funding is needed
|
||||
if (!formData.invoice_files || formData.invoice_files.length === 0) {
|
||||
toast.error('Invoice files are required when requesting AS funding');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if invoice data is present and has items
|
||||
if (!formData.invoiceData || !formData.invoiceData.items || formData.invoiceData.items.length === 0) {
|
||||
toast.error('Please add at least one item to your invoice');
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { EventRequestFormData } from './EventRequestForm';
|
|||
import type { EventRequest } from '../../../schemas/pocketbase';
|
||||
import { FlyerTypes, LogoOptions } from '../../../schemas/pocketbase';
|
||||
import CustomAlert from '../universal/CustomAlert';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
// Enhanced animation variants
|
||||
const containerVariants = {
|
||||
|
@ -122,11 +123,26 @@ const PRSection: React.FC<PRSectionProps> = ({ formData, onDataChange }) => {
|
|||
const handleLogoFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const newFiles = Array.from(e.target.files) as File[];
|
||||
setOtherLogoFiles(newFiles);
|
||||
onDataChange({ other_logos: newFiles });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...otherLogoFiles, ...newFiles];
|
||||
setOtherLogoFiles(combinedFiles);
|
||||
onDataChange({ other_logos: combinedFiles });
|
||||
}
|
||||
};
|
||||
|
||||
// Handle removing individual files
|
||||
const handleRemoveLogoFile = (indexToRemove: number) => {
|
||||
const updatedFiles = otherLogoFiles.filter((_, index) => index !== indexToRemove);
|
||||
setOtherLogoFiles(updatedFiles);
|
||||
onDataChange({ other_logos: updatedFiles });
|
||||
};
|
||||
|
||||
// Handle clearing all files
|
||||
const handleClearAllLogoFiles = () => {
|
||||
setOtherLogoFiles([]);
|
||||
onDataChange({ other_logos: [] });
|
||||
};
|
||||
|
||||
// Handle drag events for file upload
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
|
@ -144,8 +160,10 @@ const PRSection: React.FC<PRSectionProps> = ({ formData, onDataChange }) => {
|
|||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
const newFiles = Array.from(e.dataTransfer.files) as File[];
|
||||
setOtherLogoFiles(newFiles);
|
||||
onDataChange({ other_logos: newFiles });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...otherLogoFiles, ...newFiles];
|
||||
setOtherLogoFiles(combinedFiles);
|
||||
onDataChange({ other_logos: combinedFiles });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -349,20 +367,44 @@ const PRSection: React.FC<PRSectionProps> = ({ formData, onDataChange }) => {
|
|||
|
||||
{otherLogoFiles.length > 0 ? (
|
||||
<>
|
||||
<p className="font-medium text-primary">{otherLogoFiles.length} file(s) selected:</p>
|
||||
<div className="max-h-24 overflow-y-auto text-left w-full">
|
||||
<ul className="list-disc list-inside text-sm">
|
||||
{otherLogoFiles.map((file, index) => (
|
||||
<li key={index} className="truncate">{file.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="font-medium text-primary">{otherLogoFiles.length} file(s) selected:</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearAllLogoFiles();
|
||||
}}
|
||||
className="btn btn-xs btn-outline btn-error"
|
||||
title="Clear all files"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Click or drag to replace</p>
|
||||
<div className="max-h-32 overflow-y-auto text-left w-full space-y-1">
|
||||
{otherLogoFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between bg-base-100 p-2 rounded">
|
||||
<span className="text-sm truncate flex-1">{file.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveLogoFile(index);
|
||||
}}
|
||||
className="btn btn-xs btn-error ml-2"
|
||||
title="Remove file"
|
||||
>
|
||||
<Icon icon="heroicons:x-mark" className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Click or drag to add more files</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="font-medium">Drop your logo files here or click to browse</p>
|
||||
<p className="text-xs text-gray-500">Please upload transparent logo files (PNG preferred)</p>
|
||||
<p className="text-xs text-gray-500">Please upload transparent logo files (PNG preferred, multiple files allowed)</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import type { EventRequestFormData } from './EventRequestForm';
|
|||
import type { EventRequest } from '../../../schemas/pocketbase';
|
||||
import CustomAlert from '../universal/CustomAlert';
|
||||
import FilePreview from '../universal/FilePreview';
|
||||
import { Icon } from '@iconify/react';
|
||||
|
||||
// Enhanced animation variants
|
||||
const containerVariants = {
|
||||
|
@ -69,11 +70,12 @@ interface TAPFormSectionProps {
|
|||
}
|
||||
|
||||
const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange }) => {
|
||||
const [roomBookingFile, setRoomBookingFile] = useState<File | null>(formData.room_booking);
|
||||
const [roomBookingFiles, setRoomBookingFiles] = useState<File[]>(formData.room_booking_files || []);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [fileError, setFileError] = useState<string | null>(null);
|
||||
const [showFilePreview, setShowFilePreview] = useState(false);
|
||||
const [filePreviewUrl, setFilePreviewUrl] = useState<string | null>(null);
|
||||
const [selectedPreviewFile, setSelectedPreviewFile] = useState<File | null>(null);
|
||||
|
||||
// Add style tag for hidden arrows
|
||||
useEffect(() => {
|
||||
|
@ -89,27 +91,58 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
// Handle room booking file upload with size limit
|
||||
const handleRoomBookingFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
const file = e.target.files[0];
|
||||
const newFiles = Array.from(e.target.files) as File[];
|
||||
|
||||
// Check file size - 1MB limit
|
||||
if (file.size > 1024 * 1024) {
|
||||
setFileError("Room booking file size must be under 1MB");
|
||||
// Check file sizes - 1MB limit for each file
|
||||
const oversizedFiles = newFiles.filter(file => file.size > 1024 * 1024);
|
||||
if (oversizedFiles.length > 0) {
|
||||
setFileError(`${oversizedFiles.length} file(s) exceed 1MB limit`);
|
||||
return;
|
||||
}
|
||||
|
||||
setFileError(null);
|
||||
setRoomBookingFile(file);
|
||||
onDataChange({ room_booking: file });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...roomBookingFiles, ...newFiles];
|
||||
setRoomBookingFiles(combinedFiles);
|
||||
onDataChange({ room_booking_files: combinedFiles });
|
||||
|
||||
// Create preview URL
|
||||
// Create preview URL for the first new file
|
||||
if (filePreviewUrl) {
|
||||
URL.revokeObjectURL(filePreviewUrl);
|
||||
}
|
||||
const url = URL.createObjectURL(file);
|
||||
const url = URL.createObjectURL(newFiles[0]);
|
||||
setFilePreviewUrl(url);
|
||||
setSelectedPreviewFile(newFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle removing individual files
|
||||
const handleRemoveFile = (indexToRemove: number) => {
|
||||
const updatedFiles = roomBookingFiles.filter((_, index) => index !== indexToRemove);
|
||||
setRoomBookingFiles(updatedFiles);
|
||||
onDataChange({ room_booking_files: updatedFiles });
|
||||
|
||||
// Clear preview if we removed the previewed file
|
||||
if (selectedPreviewFile && updatedFiles.length === 0) {
|
||||
if (filePreviewUrl) {
|
||||
URL.revokeObjectURL(filePreviewUrl);
|
||||
setFilePreviewUrl(null);
|
||||
}
|
||||
setSelectedPreviewFile(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle clearing all files
|
||||
const handleClearAllFiles = () => {
|
||||
setRoomBookingFiles([]);
|
||||
onDataChange({ room_booking_files: [] });
|
||||
if (filePreviewUrl) {
|
||||
URL.revokeObjectURL(filePreviewUrl);
|
||||
setFilePreviewUrl(null);
|
||||
}
|
||||
setSelectedPreviewFile(null);
|
||||
};
|
||||
|
||||
// Handle drag events for file upload
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
|
@ -126,24 +159,28 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
setIsDragging(false);
|
||||
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
const file = e.dataTransfer.files[0];
|
||||
const newFiles = Array.from(e.dataTransfer.files) as File[];
|
||||
|
||||
// Check file size - 1MB limit
|
||||
if (file.size > 1024 * 1024) {
|
||||
setFileError("Room booking file size must be under 1MB");
|
||||
// Check file sizes - 1MB limit for each file
|
||||
const oversizedFiles = newFiles.filter(file => file.size > 1024 * 1024);
|
||||
if (oversizedFiles.length > 0) {
|
||||
setFileError(`${oversizedFiles.length} file(s) exceed 1MB limit`);
|
||||
return;
|
||||
}
|
||||
|
||||
setFileError(null);
|
||||
setRoomBookingFile(file);
|
||||
onDataChange({ room_booking: file });
|
||||
// Combine existing files with new files instead of replacing
|
||||
const combinedFiles = [...roomBookingFiles, ...newFiles];
|
||||
setRoomBookingFiles(combinedFiles);
|
||||
onDataChange({ room_booking_files: combinedFiles });
|
||||
|
||||
// Create preview URL
|
||||
// Create preview URL for the first new file
|
||||
if (filePreviewUrl) {
|
||||
URL.revokeObjectURL(filePreviewUrl);
|
||||
}
|
||||
const url = URL.createObjectURL(file);
|
||||
const url = URL.createObjectURL(newFiles[0]);
|
||||
setFilePreviewUrl(url);
|
||||
setSelectedPreviewFile(newFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -262,6 +299,11 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
<span className="label-text font-medium text-lg">Room Booking Confirmation</span>
|
||||
{formData.will_or_have_room_booking && <span className="label-text-alt text-error">*</span>}
|
||||
</label>
|
||||
{formData.will_or_have_room_booking && (
|
||||
<p className="text-sm text-gray-500 mb-3">
|
||||
<strong>Required:</strong> Upload your room booking confirmation document.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{fileError && (
|
||||
<div className="mt-2 mb-2">
|
||||
|
@ -292,6 +334,7 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
className="hidden"
|
||||
onChange={handleRoomBookingFileChange}
|
||||
accept=".pdf,.png,.jpg,.jpeg"
|
||||
multiple
|
||||
/>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-3">
|
||||
|
@ -304,16 +347,46 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
</svg>
|
||||
</motion.div>
|
||||
|
||||
{roomBookingFile ? (
|
||||
{roomBookingFiles.length > 0 ? (
|
||||
<>
|
||||
<p className="font-medium text-primary">File selected:</p>
|
||||
<p className="text-sm">{roomBookingFile.name}</p>
|
||||
<p className="text-xs text-gray-500">Click or drag to replace (Max size: 1MB)</p>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p className="font-medium text-primary">{roomBookingFiles.length} file(s) selected:</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClearAllFiles();
|
||||
}}
|
||||
className="btn btn-xs btn-outline btn-error"
|
||||
title="Clear all files"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div className="max-h-32 overflow-y-auto text-left w-full space-y-1">
|
||||
{roomBookingFiles.map((file, index) => (
|
||||
<div key={index} className="flex items-center justify-between bg-base-100 p-2 rounded">
|
||||
<span className="text-sm truncate flex-1">{file.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveFile(index);
|
||||
}}
|
||||
className="btn btn-xs btn-error ml-2"
|
||||
title="Remove file"
|
||||
>
|
||||
<Icon icon="heroicons:x-mark" className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Click or drag to add more files (Max size: 1MB each)</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="font-medium">Drop your file here or click to browse</p>
|
||||
<p className="text-xs text-gray-500">Accepted formats: PDF, PNG, JPG (Max size: 1MB)</p>
|
||||
<p className="font-medium">Drop your files here or click to browse</p>
|
||||
<p className="text-xs text-gray-500">Accepted formats: PDF, PNG, JPG (Max size: 1MB each, multiple files allowed)</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -329,20 +402,20 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
)}
|
||||
|
||||
{/* Preview File Button - Outside the upload area */}
|
||||
{formData.will_or_have_room_booking && roomBookingFile && (
|
||||
{formData.will_or_have_room_booking && roomBookingFiles.length > 0 && (
|
||||
<div className="mt-3 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={toggleFilePreview}
|
||||
>
|
||||
{showFilePreview ? 'Hide Preview' : 'Preview File'}
|
||||
{showFilePreview ? 'Hide Preview' : `Preview Files (${roomBookingFiles.length})`}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* File Preview Component */}
|
||||
{showFilePreview && filePreviewUrl && roomBookingFile && (
|
||||
{showFilePreview && roomBookingFiles.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
@ -350,7 +423,7 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
className="mt-4 p-4 bg-base-200 rounded-lg"
|
||||
>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="font-medium">File Preview</h3>
|
||||
<h3 className="font-medium">File Preview ({roomBookingFiles.length} files)</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-circle"
|
||||
|
@ -361,7 +434,17 @@ const TAPFormSection: React.FC<TAPFormSectionProps> = ({ formData, onDataChange
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<FilePreview url={filePreviewUrl} filename={roomBookingFile.name} />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{roomBookingFiles.map((file, index) => {
|
||||
const url = URL.createObjectURL(file);
|
||||
return (
|
||||
<div key={index} className="border rounded-lg p-2">
|
||||
<p className="text-sm font-medium mb-2 truncate">{file.name}</p>
|
||||
<FilePreview url={url} filename={file.name} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
|
|
@ -29,7 +29,7 @@ interface ExtendedEventRequest extends SchemaEventRequest {
|
|||
flyer_files?: string[]; // Add this for PR-related files
|
||||
files?: string[]; // Generic files field
|
||||
will_or_have_room_booking?: boolean;
|
||||
room_booking?: string; // Single file for room booking
|
||||
room_booking_files?: string[]; // CHANGED: Multiple room booking files instead of single
|
||||
room_reservation_needed?: boolean; // Keep for backward compatibility
|
||||
additional_notes?: string;
|
||||
flyers_completed?: boolean; // Track if flyers have been completed by PR team
|
||||
|
@ -82,7 +82,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|||
setFileUrl(secureUrl);
|
||||
|
||||
// Determine file type from extension
|
||||
const extension = fileName.split('.').pop()?.toLowerCase() || '';
|
||||
const extension = (typeof fileName === 'string' ? fileName.split('.').pop()?.toLowerCase() : '') || '';
|
||||
setFileType(extension);
|
||||
|
||||
setIsLoading(false);
|
||||
|
@ -1125,6 +1125,7 @@ const PRMaterialsTab: React.FC<{ request: ExtendedEventRequest }> = ({ request }
|
|||
|
||||
// Use the same utility functions as in the ASFundingTab
|
||||
const getFileExtension = (filename: string): string => {
|
||||
if (!filename || typeof filename !== 'string') return '';
|
||||
const parts = filename.split('.');
|
||||
return parts.length > 1 ? parts.pop()?.toLowerCase() || '' : '';
|
||||
};
|
||||
|
@ -1139,6 +1140,7 @@ const PRMaterialsTab: React.FC<{ request: ExtendedEventRequest }> = ({ request }
|
|||
};
|
||||
|
||||
const getFriendlyFileName = (filename: string, maxLength: number = 20): string => {
|
||||
if (!filename || typeof filename !== 'string') return 'Unknown File';
|
||||
const basename = filename.split('/').pop() || filename;
|
||||
if (basename.length <= maxLength) return basename;
|
||||
const extension = getFileExtension(basename);
|
||||
|
@ -1836,29 +1838,34 @@ const EventRequestDetails = ({
|
|||
<div className="bg-base-200/30 p-3 rounded-lg">
|
||||
<label className="text-xs text-gray-400 block mb-1">Confirmation Status</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`badge ${request.room_booking ? 'badge-success' : 'badge-warning'}`}>
|
||||
{request.room_booking ? 'Booking File Uploaded' : 'No Booking File'}
|
||||
<div className={`badge ${request.room_booking_files && request.room_booking_files.length > 0 ? 'badge-success' : 'badge-warning'}`}>
|
||||
{request.room_booking_files && request.room_booking_files.length > 0 ? 'Booking Files Uploaded' : 'No Booking Files'}
|
||||
</div>
|
||||
{request.room_booking && (
|
||||
<button
|
||||
onClick={() => {
|
||||
// Dispatch event to update file preview modal
|
||||
const event = new CustomEvent('filePreviewStateChange', {
|
||||
detail: {
|
||||
url: `https://pocketbase.ieeeucsd.org/api/files/event_request/${request.id}/${request.room_booking}`,
|
||||
filename: request.room_booking
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
{request.room_booking_files && request.room_booking_files.length > 0 && (
|
||||
<div className="flex gap-2">
|
||||
{request.room_booking_files.map((fileId, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => {
|
||||
// Dispatch event to update file preview modal
|
||||
const event = new CustomEvent('filePreviewStateChange', {
|
||||
detail: {
|
||||
url: `https://pocketbase.ieeeucsd.org/api/files/event_request/${request.id}/${fileId}`,
|
||||
filename: fileId
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
|
||||
// Open the modal
|
||||
const modal = document.getElementById('file-preview-modal') as HTMLDialogElement;
|
||||
if (modal) modal.showModal();
|
||||
}}
|
||||
className="btn btn-xs btn-primary ml-2"
|
||||
>
|
||||
View File
|
||||
</button>
|
||||
// Open the modal
|
||||
const modal = document.getElementById('file-preview-modal') as HTMLDialogElement;
|
||||
if (modal) modal.showModal();
|
||||
}}
|
||||
className="btn btn-xs btn-primary"
|
||||
>
|
||||
View File {index + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -119,7 +119,7 @@ export interface EventRequest extends BaseRecord {
|
|||
advertising_format?: string;
|
||||
will_or_have_room_booking?: boolean;
|
||||
expected_attendance?: number;
|
||||
room_booking?: string; // signle file
|
||||
room_booking_files?: string[]; // CHANGED: Multiple files instead of single file
|
||||
as_funding_required: boolean;
|
||||
food_drinks_being_served: boolean;
|
||||
itemized_invoice?: string; // JSON string
|
||||
|
|
Loading…
Reference in a new issue