fix preview errors
This commit is contained in:
parent
b4c238de1f
commit
05a92208ce
3 changed files with 466 additions and 289 deletions
|
@ -3,7 +3,7 @@ import { Icon } from "astro-icon/components";
|
||||||
import { Get } from "../pocketbase/Get";
|
import { Get } from "../pocketbase/Get";
|
||||||
import { Authentication } from "../pocketbase/Authentication";
|
import { Authentication } from "../pocketbase/Authentication";
|
||||||
import EventEditor from "./Officer_EventManagement/EventEditor";
|
import EventEditor from "./Officer_EventManagement/EventEditor";
|
||||||
import FilePreview from "../modals/FilePreview";
|
import FilePreview from "./Officer_EventManagement/FilePreview";
|
||||||
|
|
||||||
// Get instances
|
// Get instances
|
||||||
const get = Get.getInstance();
|
const get = Get.getInstance();
|
||||||
|
@ -714,7 +714,7 @@ const currentPage = eventResponse.page;
|
||||||
import { Update } from "../pocketbase/Update";
|
import { Update } from "../pocketbase/Update";
|
||||||
import { FileManager } from "../pocketbase/FileManager";
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
import { SendLog } from "../pocketbase/SendLog";
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
import FilePreview from "../modals/FilePreview";
|
import FilePreview from "./Officer_EventManagement/FilePreview";
|
||||||
|
|
||||||
// Add file storage
|
// Add file storage
|
||||||
const selectedFileStorage = new Map<string, File>();
|
const selectedFileStorage = new Map<string, File>();
|
||||||
|
@ -1832,12 +1832,8 @@ const currentPage = eventResponse.page;
|
||||||
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
|
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
|
||||||
const filePreview = document.getElementById("universalFilePreview") as any;
|
const filePreview = document.getElementById("universalFilePreview") as any;
|
||||||
const previewFileName = document.getElementById("previewFileName");
|
const previewFileName = document.getElementById("previewFileName");
|
||||||
const loadingSpinner = document.getElementById("previewLoadingSpinner");
|
|
||||||
|
|
||||||
if (filePreview && modal && previewFileName && loadingSpinner) {
|
if (filePreview && modal && previewFileName) {
|
||||||
// Show loading spinner
|
|
||||||
loadingSpinner.classList.remove("hidden");
|
|
||||||
|
|
||||||
// Update the filename display
|
// Update the filename display
|
||||||
previewFileName.textContent = filename;
|
previewFileName.textContent = filename;
|
||||||
|
|
||||||
|
@ -1847,27 +1843,20 @@ const currentPage = eventResponse.page;
|
||||||
// Update the preview component
|
// Update the preview component
|
||||||
filePreview.setAttribute("url", url);
|
filePreview.setAttribute("url", url);
|
||||||
filePreview.setAttribute("filename", filename);
|
filePreview.setAttribute("filename", filename);
|
||||||
|
|
||||||
// Hide loading spinner after a short delay
|
|
||||||
setTimeout(() => {
|
|
||||||
loadingSpinner.classList.add("hidden");
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Close file preview
|
// Close file preview
|
||||||
window.closeFilePreview = function () {
|
window.closeFilePreview = function () {
|
||||||
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
|
const modal = document.getElementById("filePreviewModal") as HTMLDialogElement;
|
||||||
const filePreview = document.getElementById("universalFilePreview");
|
const filePreview = document.getElementById("universalFilePreview") as any;
|
||||||
const previewFileName = document.getElementById("previewFileName");
|
const previewFileName = document.getElementById("previewFileName");
|
||||||
const filesContent = document.getElementById("filesContent");
|
const filesContent = document.getElementById("filesContent");
|
||||||
|
|
||||||
if (modal && filePreview && previewFileName) {
|
if (modal && filePreview && previewFileName) {
|
||||||
// Reset the preview
|
// Reset the preview
|
||||||
const event = new CustomEvent("updateFilePreview", {
|
filePreview.setAttribute("url", "");
|
||||||
detail: { url: "", filename: "" },
|
filePreview.setAttribute("filename", "");
|
||||||
});
|
|
||||||
filePreview.dispatchEvent(event);
|
|
||||||
previewFileName.textContent = "";
|
previewFileName.textContent = "";
|
||||||
modal.close();
|
modal.close();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Authentication } from "../../pocketbase/Authentication";
|
||||||
import { Update } from "../../pocketbase/Update";
|
import { Update } from "../../pocketbase/Update";
|
||||||
import { FileManager } from "../../pocketbase/FileManager";
|
import { FileManager } from "../../pocketbase/FileManager";
|
||||||
import { SendLog } from "../../pocketbase/SendLog";
|
import { SendLog } from "../../pocketbase/SendLog";
|
||||||
import FilePreview from "../../modals/FilePreview";
|
import FilePreview from "./FilePreview";
|
||||||
|
|
||||||
// Extend Window interface
|
// Extend Window interface
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -129,6 +129,7 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
|
||||||
setPreviewFilename(filename);
|
setPreviewFilename(filename);
|
||||||
setShowPreview(true);
|
setShowPreview(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -256,290 +257,306 @@ export default function EventEditor({ onEventSaved }: EventEditorProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add preview section
|
|
||||||
const previewSection = showPreview && event?.id && (
|
|
||||||
<FilePreview
|
|
||||||
url={fileManager.getFileUrl("events", event.id, previewFilename)}
|
|
||||||
filename={previewFilename}
|
|
||||||
onClose={() => {
|
|
||||||
setShowPreview(false);
|
|
||||||
const modal = document.getElementById('filePreviewModal') as HTMLDialogElement;
|
|
||||||
if (modal) {
|
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog id="editEventModal" className="modal">
|
<dialog id="editEventModal" className="modal">
|
||||||
{/* Preview Section */}
|
{showPreview ? (
|
||||||
{previewSection}
|
<div className="modal-box max-w-4xl">
|
||||||
<div className="modal-box max-w-2xl">
|
<div className="flex justify-between items-center mb-4">
|
||||||
{/* Main Edit Form Section */}
|
<div className="flex items-center gap-3">
|
||||||
<div id="editFormSection">
|
<button
|
||||||
<h3 className="font-bold text-lg mb-4" id="editModalTitle">Edit Event</h3>
|
className="btn btn-ghost btn-sm"
|
||||||
<form id="editEventForm" onSubmit={handleSubmit} className="space-y-4">
|
onClick={() => setShowPreview(false)}
|
||||||
<input type="hidden" id="editEventId" name="editEventId" value={event?.id || ''} />
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
← Back
|
||||||
{/* Event Name */}
|
</button>
|
||||||
<div className="form-control">
|
<h3 className="font-bold text-lg truncate">
|
||||||
<label className="label">
|
{previewFilename}
|
||||||
<span className="label-text">Event Name</span>
|
</h3>
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="editEventName"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.event_name || ""}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, event_name: e.target.value } : null)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Event Code */}
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">Event Code</span>
|
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="editEventCode"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.event_code || ""}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, event_code: e.target.value } : null)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Location */}
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">Location</span>
|
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="editEventLocation"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.location || ""}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, location: e.target.value } : null)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Points to Reward */}
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">Points to Reward</span>
|
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
name="editEventPoints"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.points_to_reward || 0}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, points_to_reward: Number(e.target.value) } : null)}
|
|
||||||
min="0"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Start Date */}
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">Start Date</span>
|
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
name="editEventStartDate"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : ""}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, start_date: e.target.value } : null)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* End Date */}
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">End Date</span>
|
|
||||||
<span className="label-text-alt text-error">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
name="editEventEndDate"
|
|
||||||
className="input input-bordered"
|
|
||||||
value={event?.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : ""}
|
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, end_date: e.target.value } : null)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<FilePreview
|
||||||
|
url={previewUrl}
|
||||||
|
filename={previewFilename}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="modal-box max-w-2xl">
|
||||||
|
{/* Main Edit Form Section */}
|
||||||
|
<div id="editFormSection">
|
||||||
|
<h3 className="font-bold text-lg mb-4" id="editModalTitle">
|
||||||
|
{event?.id ? 'Edit Event' : 'Add New Event'}
|
||||||
|
</h3>
|
||||||
|
<form id="editEventForm" onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<input type="hidden" id="editEventId" name="editEventId" value={event?.id || ''} />
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{/* Event Name */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Event Name</span>
|
||||||
|
<span className="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="editEventName"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={event?.event_name || ""}
|
||||||
|
onChange={(e) => setEvent(prev => prev ? { ...prev, event_name: e.target.value } : null)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Event Code */}
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text">Description</span>
|
<span className="label-text">Event Code</span>
|
||||||
<span className="label-text-alt text-error">*</span>
|
<span className="label-text-alt text-error">*</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<input
|
||||||
name="editEventDescription"
|
type="text"
|
||||||
className="textarea textarea-bordered"
|
name="editEventCode"
|
||||||
value={event?.event_description || ""}
|
className="input input-bordered"
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, event_description: e.target.value } : null)}
|
value={event?.event_code || ""}
|
||||||
rows={3}
|
onChange={(e) => setEvent(prev => prev ? { ...prev, event_code: e.target.value } : null)}
|
||||||
required
|
required
|
||||||
></textarea>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Files */}
|
{/* Location */}
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text">Upload Files</span>
|
<span className="label-text">Location</span>
|
||||||
</label>
|
<span className="label-text-alt text-error">*</span>
|
||||||
<input
|
</label>
|
||||||
type="file"
|
<input
|
||||||
onChange={handleFileSelect}
|
type="text"
|
||||||
className="file-input file-input-bordered"
|
name="editEventLocation"
|
||||||
multiple
|
className="input input-bordered"
|
||||||
/>
|
value={event?.location || ""}
|
||||||
<div className="mt-4 space-y-2">
|
onChange={(e) => setEvent(prev => prev ? { ...prev, location: e.target.value } : null)}
|
||||||
{/* New Files */}
|
required
|
||||||
{Array.from(selectedFiles.entries()).map(([name, file]) => (
|
/>
|
||||||
<div key={name} className="flex items-center justify-between p-2 bg-base-200 rounded-lg">
|
</div>
|
||||||
<span className="truncate">{name}</span>
|
|
||||||
<div className="flex gap-2">
|
{/* Points to Reward */}
|
||||||
<div className="badge badge-primary">New</div>
|
<div className="form-control">
|
||||||
<button
|
<label className="label">
|
||||||
type="button"
|
<span className="label-text">Points to Reward</span>
|
||||||
className="btn btn-ghost btn-xs text-error"
|
<span className="label-text-alt text-error">*</span>
|
||||||
onClick={() => {
|
</label>
|
||||||
const updatedFiles = new Map(selectedFiles);
|
<input
|
||||||
updatedFiles.delete(name);
|
type="number"
|
||||||
setSelectedFiles(updatedFiles);
|
name="editEventPoints"
|
||||||
}}
|
className="input input-bordered"
|
||||||
>
|
value={event?.points_to_reward || 0}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
onChange={(e) => setEvent(prev => prev ? { ...prev, points_to_reward: Number(e.target.value) } : null)}
|
||||||
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
min="0"
|
||||||
</svg>
|
required
|
||||||
</button>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Start Date */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Start Date</span>
|
||||||
|
<span className="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
name="editEventStartDate"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={event?.start_date ? new Date(event.start_date).toISOString().slice(0, 16) : ""}
|
||||||
|
onChange={(e) => setEvent(prev => prev ? { ...prev, start_date: e.target.value } : null)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* End Date */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">End Date</span>
|
||||||
|
<span className="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
name="editEventEndDate"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={event?.end_date ? new Date(event.end_date).toISOString().slice(0, 16) : ""}
|
||||||
|
onChange={(e) => setEvent(prev => prev ? { ...prev, end_date: e.target.value } : null)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Description</span>
|
||||||
|
<span className="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="editEventDescription"
|
||||||
|
className="textarea textarea-bordered"
|
||||||
|
value={event?.event_description || ""}
|
||||||
|
onChange={(e) => setEvent(prev => prev ? { ...prev, event_description: e.target.value } : null)}
|
||||||
|
rows={3}
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Files */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Upload Files</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
onChange={handleFileSelect}
|
||||||
|
className="file-input file-input-bordered"
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
<div className="mt-4 space-y-2">
|
||||||
|
{/* New Files */}
|
||||||
|
{Array.from(selectedFiles.entries()).map(([name, file]) => (
|
||||||
|
<div key={name} className="flex items-center justify-between p-2 bg-base-200 rounded-lg">
|
||||||
|
<span className="truncate">{name}</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="badge badge-primary">New</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-ghost btn-xs text-error"
|
||||||
|
onClick={() => {
|
||||||
|
const updatedFiles = new Map(selectedFiles);
|
||||||
|
updatedFiles.delete(name);
|
||||||
|
setSelectedFiles(updatedFiles);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Current Files */}
|
{/* Current Files */}
|
||||||
{event?.files && event.files.length > 0 && (
|
{event?.files && event.files.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="divider">Current Files</div>
|
<div className="divider">Current Files</div>
|
||||||
{event.files.map((filename) => (
|
{event.files.map((filename) => (
|
||||||
<div key={filename} className={`flex items-center justify-between p-2 bg-base-200 rounded-lg${filesToDelete.has(filename) ? " opacity-50" : ""}`}>
|
<div key={filename} className={`flex items-center justify-between p-2 bg-base-200 rounded-lg${filesToDelete.has(filename) ? " opacity-50" : ""}`}>
|
||||||
<span className="truncate">{filename}</span>
|
<span className="truncate">{filename}</span>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-ghost btn-xs"
|
className="btn btn-ghost btn-xs"
|
||||||
onClick={() => handlePreviewFile(fileManager.getFileUrl("events", event.id, filename), filename)}
|
onClick={() => {
|
||||||
>
|
if (event?.id) {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
handlePreviewFile(
|
||||||
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
fileManager.getFileUrl("events", event.id, filename),
|
||||||
<path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
|
filename
|
||||||
</svg>
|
);
|
||||||
</button>
|
}
|
||||||
<div className="text-error">
|
}}
|
||||||
{filesToDelete.has(filename) ? (
|
>
|
||||||
<button
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
type="button"
|
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
|
||||||
className="btn btn-ghost btn-xs"
|
<path fillRule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clipRule="evenodd" />
|
||||||
onClick={() => handleUndoFileDelete(filename)}
|
</svg>
|
||||||
>
|
</button>
|
||||||
Undo
|
<div className="text-error">
|
||||||
</button>
|
{filesToDelete.has(filename) ? (
|
||||||
) : (
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
className="btn btn-ghost btn-xs"
|
||||||
className="btn btn-ghost btn-xs text-error"
|
onClick={() => handleUndoFileDelete(filename)}
|
||||||
onClick={() => handleFileDelete(filename)}
|
>
|
||||||
>
|
Undo
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
</button>
|
||||||
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
) : (
|
||||||
</svg>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
)}
|
className="btn btn-ghost btn-xs text-error"
|
||||||
|
onClick={() => handleFileDelete(filename)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Published */}
|
{/* Published */}
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label cursor-pointer justify-start gap-4">
|
<label className="label cursor-pointer justify-start gap-4">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="editEventPublished"
|
name="editEventPublished"
|
||||||
className="toggle"
|
className="toggle"
|
||||||
checked={event?.published || false}
|
checked={event?.published || false}
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, published: e.target.checked } : null)}
|
onChange={(e) => setEvent(prev => prev ? { ...prev, published: e.target.checked } : null)}
|
||||||
/>
|
/>
|
||||||
<span className="label-text">Publish Event</span>
|
<span className="label-text">Publish Event</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text-alt text-info">
|
<span className="label-text-alt text-info">
|
||||||
This has to be clicked if you want to make this event available
|
This has to be clicked if you want to make this event available
|
||||||
to the public
|
to the public
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Has Food */}
|
{/* Has Food */}
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label cursor-pointer justify-start gap-4">
|
<label className="label cursor-pointer justify-start gap-4">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="editEventHasFood"
|
name="editEventHasFood"
|
||||||
className="toggle"
|
className="toggle"
|
||||||
checked={event?.has_food || false}
|
checked={event?.has_food || false}
|
||||||
onChange={(e) => setEvent(prev => prev ? { ...prev, has_food: e.target.checked } : null)}
|
onChange={(e) => setEvent(prev => prev ? { ...prev, has_food: e.target.checked } : null)}
|
||||||
/>
|
/>
|
||||||
<span className="label-text">Has Food</span>
|
<span className="label-text">Has Food</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text-alt text-info">
|
<span className="label-text-alt text-info">
|
||||||
Check this if food will be provided at the event
|
Check this if food will be provided at the event
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-action">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className={`btn btn-primary ${isSubmitting ? 'loading' : ''}`}
|
||||||
|
form="editEventForm"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
const modal = document.getElementById("editEventModal") as HTMLDialogElement;
|
||||||
|
if (modal) modal.close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="modal-action">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className={`btn btn-primary ${isSubmitting ? 'loading' : ''}`}
|
|
||||||
form="editEventForm"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
{isSubmitting ? 'Saving...' : 'Save Changes'}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn"
|
|
||||||
onClick={() => {
|
|
||||||
const modal = document.getElementById("editEventModal") as HTMLDialogElement;
|
|
||||||
if (modal) modal.close();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form method="dialog" className="modal-backdrop">
|
<form method="dialog" className="modal-backdrop">
|
||||||
<button>close</button>
|
<button>close</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
171
src/components/dashboard/Officer_EventManagement/FilePreview.tsx
Normal file
171
src/components/dashboard/Officer_EventManagement/FilePreview.tsx
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
interface FilePreviewProps {
|
||||||
|
url: string;
|
||||||
|
filename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilePreview: React.FC<FilePreviewProps> = ({ url, filename }) => {
|
||||||
|
const [content, setContent] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [fileType, setFileType] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!url || !filename) {
|
||||||
|
setContent(null);
|
||||||
|
setError(null);
|
||||||
|
setFileType(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadContent = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
setFileType(contentType);
|
||||||
|
|
||||||
|
if (contentType?.startsWith('image/')) {
|
||||||
|
setContent('image');
|
||||||
|
} else if (contentType?.startsWith('video/')) {
|
||||||
|
setContent('video');
|
||||||
|
} else if (contentType?.startsWith('application/pdf')) {
|
||||||
|
setContent('pdf');
|
||||||
|
} else if (contentType?.startsWith('text/')) {
|
||||||
|
const text = await response.text();
|
||||||
|
// Truncate text if it's too long (e.g., more than 100KB)
|
||||||
|
if (text.length > 100000) {
|
||||||
|
setContent(text.substring(0, 100000) + '\n\n... Content truncated. Please download the file to view the complete content.');
|
||||||
|
} else {
|
||||||
|
setContent(text);
|
||||||
|
}
|
||||||
|
} else if (filename.toLowerCase().endsWith('.mp4')) {
|
||||||
|
// Fallback for video files when content-type might not be correct
|
||||||
|
setContent('video');
|
||||||
|
} else {
|
||||||
|
setError(`This file type (${contentType || 'unknown'}) is not supported for preview. Please download the file to view it.`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to load file');
|
||||||
|
console.error('Error loading file:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadContent();
|
||||||
|
}, [url, filename]);
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error downloading file:', err);
|
||||||
|
alert('Failed to download file. Please try again.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header with filename and download button */}
|
||||||
|
<div className="flex justify-between items-center bg-base-200 p-3 rounded-lg">
|
||||||
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||||
|
<span className="truncate font-medium">{filename}</span>
|
||||||
|
{fileType && (
|
||||||
|
<span className="badge badge-sm">{fileType.split('/')[1]}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDownload}
|
||||||
|
className="btn btn-sm btn-ghost gap-2"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preview content */}
|
||||||
|
<div className="preview-content">
|
||||||
|
{loading && (
|
||||||
|
<div className="flex justify-center items-center p-8">
|
||||||
|
<span className="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="flex flex-col items-center justify-center p-8 bg-base-200 rounded-lg text-center space-y-4">
|
||||||
|
<div className="bg-warning/20 p-4 rounded-full">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-semibold">Preview Unavailable</h3>
|
||||||
|
<p className="text-base-content/70 max-w-md">{error}</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleDownload}
|
||||||
|
className="btn btn-warning btn-sm gap-2 mt-4"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
Download File Instead
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && content === 'image' && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<img src={url} alt={filename} className="max-w-full h-auto rounded-lg" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && content === 'video' && (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<video
|
||||||
|
controls
|
||||||
|
className="max-w-full rounded-lg"
|
||||||
|
style={{ maxHeight: '600px' }}
|
||||||
|
>
|
||||||
|
<source src={url} type="video/mp4" />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && content === 'pdf' && (
|
||||||
|
<div className="w-full h-[600px]">
|
||||||
|
<iframe
|
||||||
|
src={url}
|
||||||
|
className="w-full h-full rounded-lg"
|
||||||
|
title={filename}
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && content && !['image', 'video', 'pdf'].includes(content) && (
|
||||||
|
<div className="mockup-code bg-base-200 text-base-content overflow-x-auto max-h-[600px]">
|
||||||
|
<pre className="p-4"><code>{content}</code></pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilePreview;
|
Loading…
Reference in a new issue