fix default profile view
This commit is contained in:
parent
e57f76bd4c
commit
6b9d793590
5 changed files with 533 additions and 148 deletions
156
src/components/pocketbase/FileManager.ts
Normal file
156
src/components/pocketbase/FileManager.ts
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import { Authentication } from "./Authentication";
|
||||||
|
|
||||||
|
export class FileManager {
|
||||||
|
private auth: Authentication;
|
||||||
|
private static instance: FileManager;
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.auth = Authentication.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance of FileManager
|
||||||
|
*/
|
||||||
|
public static getInstance(): FileManager {
|
||||||
|
if (!FileManager.instance) {
|
||||||
|
FileManager.instance = new FileManager();
|
||||||
|
}
|
||||||
|
return FileManager.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a single file to a record
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record to attach the file to
|
||||||
|
* @param field The field name for the file
|
||||||
|
* @param file The file to upload
|
||||||
|
* @returns The updated record
|
||||||
|
*/
|
||||||
|
public async uploadFile<T = any>(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
field: string,
|
||||||
|
file: File
|
||||||
|
): Promise<T> {
|
||||||
|
if (!this.auth.isAuthenticated()) {
|
||||||
|
throw new Error("User must be authenticated to upload files");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pb = this.auth.getPocketBase();
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append(field, file);
|
||||||
|
|
||||||
|
return await pb.collection(collectionName).update<T>(recordId, formData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to upload file to ${collectionName}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload multiple files to a record
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record to attach the files to
|
||||||
|
* @param field The field name for the files
|
||||||
|
* @param files Array of files to upload
|
||||||
|
* @returns The updated record
|
||||||
|
*/
|
||||||
|
public async uploadFiles<T = any>(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
field: string,
|
||||||
|
files: File[]
|
||||||
|
): Promise<T> {
|
||||||
|
if (!this.auth.isAuthenticated()) {
|
||||||
|
throw new Error("User must be authenticated to upload files");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pb = this.auth.getPocketBase();
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
files.forEach(file => {
|
||||||
|
formData.append(field, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await pb.collection(collectionName).update<T>(recordId, formData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to upload files to ${collectionName}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL for a file
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record containing the file
|
||||||
|
* @param filename The name of the file
|
||||||
|
* @returns The URL to access the file
|
||||||
|
*/
|
||||||
|
public getFileUrl(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
filename: string
|
||||||
|
): string {
|
||||||
|
const pb = this.auth.getPocketBase();
|
||||||
|
return `${pb.baseUrl}/api/files/${collectionName}/${recordId}/${filename}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a file from a record
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record containing the file
|
||||||
|
* @param field The field name of the file to delete
|
||||||
|
* @returns The updated record
|
||||||
|
*/
|
||||||
|
public async deleteFile<T = any>(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
field: string
|
||||||
|
): Promise<T> {
|
||||||
|
if (!this.auth.isAuthenticated()) {
|
||||||
|
throw new Error("User must be authenticated to delete files");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pb = this.auth.getPocketBase();
|
||||||
|
const data = { [field]: null };
|
||||||
|
return await pb.collection(collectionName).update<T>(recordId, data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to delete file from ${collectionName}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download a file
|
||||||
|
* @param collectionName The name of the collection
|
||||||
|
* @param recordId The ID of the record containing the file
|
||||||
|
* @param filename The name of the file
|
||||||
|
* @returns The file blob
|
||||||
|
*/
|
||||||
|
public async downloadFile(
|
||||||
|
collectionName: string,
|
||||||
|
recordId: string,
|
||||||
|
filename: string
|
||||||
|
): Promise<Blob> {
|
||||||
|
if (!this.auth.isAuthenticated()) {
|
||||||
|
throw new Error("User must be authenticated to download files");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = this.getFileUrl(collectionName, recordId, filename);
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.blob();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to download file from ${collectionName}:`, err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -184,64 +184,68 @@
|
||||||
import { Authentication } from "../pocketbase/Authentication";
|
import { Authentication } from "../pocketbase/Authentication";
|
||||||
import { Get } from "../pocketbase/Get";
|
import { Get } from "../pocketbase/Get";
|
||||||
import { SendLog } from "../pocketbase/SendLog";
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
|
|
||||||
const auth = Authentication.getInstance();
|
const auth = Authentication.getInstance();
|
||||||
const get = Get.getInstance();
|
const get = Get.getInstance();
|
||||||
const logger = SendLog.getInstance();
|
const logger = SendLog.getInstance();
|
||||||
|
const fileManager = FileManager.getInstance();
|
||||||
|
|
||||||
// Initialize event check-in
|
// Track if we're currently fetching events
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
let isFetchingEvents = false;
|
||||||
|
let lastFetchPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
// Define interfaces
|
||||||
|
interface BaseRecord {
|
||||||
|
id: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Event extends BaseRecord {
|
||||||
|
id: string;
|
||||||
|
event_id: string;
|
||||||
|
event_name: string;
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
location: string;
|
||||||
|
files?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add debounce function
|
||||||
|
function debounce<T extends (...args: any[]) => any>(
|
||||||
|
func: T,
|
||||||
|
wait: number
|
||||||
|
): (...args: Parameters<T>) => void {
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
return (...args: Parameters<T>) => {
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func(...args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create debounced version of renderEvents
|
||||||
|
const debouncedRenderEvents = debounce(async () => {
|
||||||
try {
|
try {
|
||||||
// Get current user's events
|
await renderEvents();
|
||||||
if (auth.isAuthenticated()) {
|
|
||||||
const user = auth.getCurrentUser();
|
|
||||||
if (user) {
|
|
||||||
// Get user's events
|
|
||||||
const events = await get.getMany(
|
|
||||||
"events",
|
|
||||||
user.events_attended || []
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update UI with events data
|
|
||||||
// ... rest of the code ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to initialize profile:", error);
|
console.error("Failed to render events:", error);
|
||||||
await logger.send(
|
|
||||||
"error",
|
|
||||||
"profile view",
|
|
||||||
`Failed to initialize profile: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
}, 300);
|
||||||
|
|
||||||
// Handle loading states
|
|
||||||
const eventCheckInSkeleton = document.getElementById(
|
|
||||||
"eventCheckInSkeleton"
|
|
||||||
);
|
|
||||||
const eventCheckInContent = document.getElementById("eventCheckInContent");
|
|
||||||
const pastEventsCount = document.getElementById("pastEventsCount");
|
|
||||||
|
|
||||||
// Function to show content and hide skeleton
|
// Function to show content and hide skeleton
|
||||||
function showEventCheckIn() {
|
function showEventCheckIn() {
|
||||||
|
const eventCheckInSkeleton = document.getElementById(
|
||||||
|
"eventCheckInSkeleton"
|
||||||
|
);
|
||||||
|
const eventCheckInContent = document.getElementById(
|
||||||
|
"eventCheckInContent"
|
||||||
|
);
|
||||||
if (eventCheckInSkeleton && eventCheckInContent) {
|
if (eventCheckInSkeleton && eventCheckInContent) {
|
||||||
eventCheckInSkeleton.classList.add("hidden");
|
eventCheckInSkeleton.classList.add("hidden");
|
||||||
eventCheckInContent.classList.remove("hidden");
|
eventCheckInContent.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show content when auth state changes
|
|
||||||
auth.onAuthStateChange(() => {
|
|
||||||
showEventCheckIn();
|
|
||||||
renderEvents();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show content on initial load if already authenticated
|
|
||||||
if (auth.isAuthenticated()) {
|
|
||||||
showEventCheckIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to format date
|
// Function to format date
|
||||||
function formatDate(dateStr: string): string {
|
function formatDate(dateStr: string): string {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
|
@ -321,14 +325,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Event extends RecordModel {
|
|
||||||
event_id: string;
|
|
||||||
event_name: string;
|
|
||||||
start_date: string;
|
|
||||||
end_date: string;
|
|
||||||
location: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to render event card
|
// Function to render event card
|
||||||
function renderEventCard(event: Event, attendedEvents: string[]): string {
|
function renderEventCard(event: Event, attendedEvents: string[]): string {
|
||||||
const isAttended =
|
const isAttended =
|
||||||
|
@ -350,7 +346,7 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
${event.files.length} File${event.files.length > 1 ? "s" : ""}
|
${event.files?.length || 0} File${(event.files?.length || 0) > 1 ? "s" : ""}
|
||||||
</button>
|
</button>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
|
@ -389,115 +385,181 @@
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to render events
|
// Function to render events with request cancellation handling
|
||||||
async function renderEvents() {
|
async function renderEvents() {
|
||||||
const eventsList = document.getElementById("eventsList");
|
const eventsList = document.getElementById("eventsList");
|
||||||
const pastEventsList = document.getElementById("pastEventsList");
|
const pastEventsList = document.getElementById("pastEventsList");
|
||||||
if (!eventsList || !pastEventsList) return;
|
if (!eventsList || !pastEventsList) return;
|
||||||
|
|
||||||
try {
|
// If we're already fetching, wait for the current fetch to complete
|
||||||
// Get current user's attended events with safe parsing
|
if (isFetchingEvents && lastFetchPromise) {
|
||||||
const user = auth.getCurrentUser();
|
try {
|
||||||
let attendedEvents: string[] = [];
|
await lastFetchPromise;
|
||||||
|
return;
|
||||||
if (user?.events_attended) {
|
} catch (err) {
|
||||||
try {
|
console.warn("Previous fetch failed:", err);
|
||||||
attendedEvents =
|
|
||||||
typeof user.events_attended === "string"
|
|
||||||
? JSON.parse(user.events_attended)
|
|
||||||
: Array.isArray(user.events_attended)
|
|
||||||
? user.events_attended
|
|
||||||
: [];
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("Failed to parse events_attended:", e);
|
|
||||||
attendedEvents = [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch all events
|
// Set up new fetch
|
||||||
const events = await get.getMany(
|
isFetchingEvents = true;
|
||||||
"events",
|
const fetchPromise = (async () => {
|
||||||
user.events_attended || []
|
try {
|
||||||
);
|
// Get current user's attended events with safe parsing
|
||||||
|
const user = auth.getCurrentUser();
|
||||||
|
let attendedEvents: string[] = [];
|
||||||
|
|
||||||
// Clear loading skeletons
|
if (user?.events_attended) {
|
||||||
eventsList.innerHTML = "";
|
try {
|
||||||
pastEventsList.innerHTML = "";
|
attendedEvents =
|
||||||
|
typeof user.events_attended === "string"
|
||||||
// Categorize events
|
? JSON.parse(user.events_attended)
|
||||||
const now = new Date();
|
: Array.isArray(user.events_attended)
|
||||||
const currentEvents: Event[] = [];
|
? user.events_attended
|
||||||
const upcomingEvents: Event[] = [];
|
: [];
|
||||||
const pastEvents: Event[] = [];
|
console.log("Attended events:", attendedEvents);
|
||||||
|
} catch (e) {
|
||||||
events.forEach((event) => {
|
console.warn("Failed to parse events_attended:", e);
|
||||||
const typedEvent = event as Event;
|
attendedEvents = [];
|
||||||
const startDate = new Date(typedEvent.start_date);
|
}
|
||||||
const endDate = new Date(typedEvent.end_date);
|
|
||||||
|
|
||||||
if (startDate > now) {
|
|
||||||
upcomingEvents.push(typedEvent);
|
|
||||||
} else if (endDate >= now && startDate <= now) {
|
|
||||||
currentEvents.push(typedEvent);
|
|
||||||
} else {
|
|
||||||
pastEvents.push(typedEvent);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Sort upcoming events by start date
|
// Fetch all events
|
||||||
const sortedUpcomingEvents = upcomingEvents.sort(
|
console.log("Fetching all events");
|
||||||
(a, b) =>
|
const events = await get.getAll<Event>(
|
||||||
new Date(a.start_date).getTime() -
|
"events",
|
||||||
new Date(b.start_date).getTime()
|
undefined,
|
||||||
);
|
"-start_date"
|
||||||
|
);
|
||||||
|
console.log("Fetched events:", events);
|
||||||
|
|
||||||
// Sort past events by date descending (most recent first)
|
if (!Array.isArray(events)) {
|
||||||
const sortedPastEvents = pastEvents.sort(
|
throw new Error(
|
||||||
(a, b) =>
|
"Failed to fetch events: Invalid response format"
|
||||||
new Date(b.end_date).getTime() -
|
);
|
||||||
new Date(a.end_date).getTime()
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// Update past events count
|
// Clear loading skeletons
|
||||||
if (pastEventsCount) {
|
eventsList.innerHTML = "";
|
||||||
pastEventsCount.textContent =
|
pastEventsList.innerHTML = "";
|
||||||
sortedPastEvents.length.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to render section
|
// Categorize events
|
||||||
function renderSection(events: Event[]): string {
|
const now = new Date();
|
||||||
if (events.length === 0) {
|
const currentEvents: Event[] = [];
|
||||||
return `
|
const upcomingEvents: Event[] = [];
|
||||||
<div class="text-center py-8 opacity-70">
|
const pastEvents: Event[] = [];
|
||||||
<p>No events found</p>
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
if (!event.start_date || !event.end_date) {
|
||||||
|
console.warn("Event missing dates:", event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = new Date(event.start_date);
|
||||||
|
const endDate = new Date(event.end_date);
|
||||||
|
|
||||||
|
if (startDate > now) {
|
||||||
|
upcomingEvents.push(event);
|
||||||
|
} else if (endDate >= now && startDate <= now) {
|
||||||
|
currentEvents.push(event);
|
||||||
|
} else {
|
||||||
|
pastEvents.push(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort upcoming events by start date
|
||||||
|
const sortedUpcomingEvents = upcomingEvents.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(a.start_date).getTime() -
|
||||||
|
new Date(b.start_date).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort past events by date descending (most recent first)
|
||||||
|
const sortedPastEvents = pastEvents.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.end_date).getTime() -
|
||||||
|
new Date(a.end_date).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update past events count
|
||||||
|
const pastEventsCountElement =
|
||||||
|
document.getElementById("pastEventsCount");
|
||||||
|
if (pastEventsCountElement) {
|
||||||
|
pastEventsCountElement.textContent =
|
||||||
|
sortedPastEvents.length.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to render section
|
||||||
|
function renderSection(events: Event[]): string {
|
||||||
|
if (events.length === 0) {
|
||||||
|
return `
|
||||||
|
<div class="text-center py-8 opacity-70">
|
||||||
|
<p>No events found</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
.map((event) => renderEventCard(event, attendedEvents))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update main events list (current & upcoming)
|
||||||
|
eventsList.innerHTML = renderSection([
|
||||||
|
...currentEvents,
|
||||||
|
...sortedUpcomingEvents,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update past events list
|
||||||
|
pastEventsList.innerHTML = renderSection(sortedPastEvents);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to render events:", err);
|
||||||
|
await logger.send(
|
||||||
|
"error",
|
||||||
|
"render events",
|
||||||
|
`Failed to render events: ${err instanceof Error ? err.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
const errorMessage = `
|
||||||
|
<div class="text-center py-8 text-error">
|
||||||
|
<p>Failed to load events. Please try again later.</p>
|
||||||
|
<p class="text-sm mt-2 opacity-70">${err instanceof Error ? err.message : "Unknown error"}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
eventsList.innerHTML = errorMessage;
|
||||||
return events
|
pastEventsList.innerHTML = errorMessage;
|
||||||
.map((event) => renderEventCard(event, attendedEvents))
|
throw err; // Re-throw to handle in the outer catch
|
||||||
.join("");
|
} finally {
|
||||||
|
isFetchingEvents = false;
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
// Update main events list (current & upcoming)
|
lastFetchPromise = fetchPromise;
|
||||||
eventsList.innerHTML = renderSection([
|
|
||||||
...currentEvents,
|
|
||||||
...sortedUpcomingEvents,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update past events list
|
try {
|
||||||
pastEventsList.innerHTML = renderSection(sortedPastEvents);
|
await fetchPromise;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to render events:", err);
|
// Error already handled above
|
||||||
const errorMessage = `
|
console.debug("Fetch completed with error");
|
||||||
<div class="text-center py-8 text-error">
|
|
||||||
<p>Failed to load events. Please try again later.</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
eventsList.innerHTML = errorMessage;
|
|
||||||
pastEventsList.innerHTML = errorMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize event check-in
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
showEventCheckIn();
|
||||||
|
// Only render events once at startup
|
||||||
|
renderEvents().catch(console.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show content when auth state changes
|
||||||
|
let lastAuthState = auth.isAuthenticated();
|
||||||
|
auth.onAuthStateChange((isValid) => {
|
||||||
|
showEventCheckIn();
|
||||||
|
// Only re-render if auth state actually changed
|
||||||
|
if (lastAuthState !== isValid) {
|
||||||
|
lastAuthState = isValid;
|
||||||
|
renderEvents().catch(console.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add event listener for viewing files
|
// Add event listener for viewing files
|
||||||
interface ViewEventFilesEvent extends CustomEvent {
|
interface ViewEventFilesEvent extends CustomEvent {
|
||||||
detail: {
|
detail: {
|
||||||
|
@ -509,7 +571,10 @@
|
||||||
if (e instanceof CustomEvent && "eventId" in e.detail) {
|
if (e instanceof CustomEvent && "eventId" in e.detail) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const event = await get.getOne("events", e.detail.eventId);
|
const event = await get.getOne<Event>(
|
||||||
|
"events",
|
||||||
|
e.detail.eventId
|
||||||
|
);
|
||||||
const fileViewerContent =
|
const fileViewerContent =
|
||||||
document.getElementById("fileViewerContent");
|
document.getElementById("fileViewerContent");
|
||||||
const fileViewerTitle =
|
const fileViewerTitle =
|
||||||
|
@ -533,7 +598,11 @@
|
||||||
|
|
||||||
fileViewerContent.innerHTML = event.files
|
fileViewerContent.innerHTML = event.files
|
||||||
.map((file) => {
|
.map((file) => {
|
||||||
const fileUrl = get.getFileURL(event, file);
|
const fileUrl = fileManager.getFileUrl(
|
||||||
|
"events",
|
||||||
|
event.id,
|
||||||
|
file
|
||||||
|
);
|
||||||
const fileName =
|
const fileName =
|
||||||
file.split("/").pop() || "File";
|
file.split("/").pop() || "File";
|
||||||
const fileExt =
|
const fileExt =
|
||||||
|
@ -599,7 +668,4 @@
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}) as unknown as EventListener);
|
}) as unknown as EventListener);
|
||||||
|
|
||||||
// Initial render
|
|
||||||
renderEvents();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -285,3 +285,162 @@ const { editor_title, form } = text.ui.tables.events;
|
||||||
<button>close</button>
|
<button>close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Authentication } from "../pocketbase/Authentication";
|
||||||
|
import { Get } from "../pocketbase/Get";
|
||||||
|
import { Update } from "../pocketbase/Update";
|
||||||
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
|
|
||||||
|
const auth = Authentication.getInstance();
|
||||||
|
const get = Get.getInstance();
|
||||||
|
const update = Update.getInstance();
|
||||||
|
const logger = SendLog.getInstance();
|
||||||
|
const fileManager = FileManager.getInstance();
|
||||||
|
|
||||||
|
// Handle file uploads
|
||||||
|
if (editorFiles) {
|
||||||
|
editorFiles.addEventListener("change", async (e) => {
|
||||||
|
const files = Array.from(
|
||||||
|
(e.target as HTMLInputElement).files || []
|
||||||
|
);
|
||||||
|
if (files.length > 0) {
|
||||||
|
const uploadProgress =
|
||||||
|
document.getElementById("uploadProgress");
|
||||||
|
const uploadProgressBar = document.getElementById(
|
||||||
|
"uploadProgressBar"
|
||||||
|
) as HTMLProgressElement;
|
||||||
|
const uploadProgressText =
|
||||||
|
document.getElementById("uploadProgressText");
|
||||||
|
|
||||||
|
if (uploadProgress) uploadProgress.classList.remove("hidden");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = auth.getCurrentUser();
|
||||||
|
if (!user) throw new Error("User not authenticated");
|
||||||
|
|
||||||
|
// Upload files
|
||||||
|
await fileManager.uploadFiles(
|
||||||
|
"events",
|
||||||
|
eventId,
|
||||||
|
"files",
|
||||||
|
files
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
if (uploadProgressBar) uploadProgressBar.value = 100;
|
||||||
|
if (uploadProgressText)
|
||||||
|
uploadProgressText.textContent = "100%";
|
||||||
|
|
||||||
|
// Log successful upload
|
||||||
|
await logger.send(
|
||||||
|
"update",
|
||||||
|
"event files",
|
||||||
|
`Successfully uploaded ${files.length} files to event ${eventId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh file list
|
||||||
|
await loadCurrentFiles();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("File upload error:", err);
|
||||||
|
|
||||||
|
// Log upload error
|
||||||
|
await logger.send(
|
||||||
|
"error",
|
||||||
|
"event files",
|
||||||
|
`Failed to upload files to event ${eventId}. Error: ${err instanceof Error ? err.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (uploadProgress) {
|
||||||
|
setTimeout(() => {
|
||||||
|
uploadProgress.classList.add("hidden");
|
||||||
|
if (uploadProgressBar) uploadProgressBar.value = 0;
|
||||||
|
if (uploadProgressText)
|
||||||
|
uploadProgressText.textContent = "0%";
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to load current files
|
||||||
|
async function loadCurrentFiles() {
|
||||||
|
const currentFiles = document.getElementById("currentFiles");
|
||||||
|
if (!currentFiles || !eventId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const event = await get.getOne("events", eventId);
|
||||||
|
if (!event.files || !Array.isArray(event.files)) return;
|
||||||
|
|
||||||
|
currentFiles.innerHTML = event.files
|
||||||
|
.map((filename) => {
|
||||||
|
const fileUrl = fileManager.getFileUrl(
|
||||||
|
"events",
|
||||||
|
eventId,
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
return `
|
||||||
|
<div class="flex justify-between items-center p-2 bg-base-200 rounded-lg">
|
||||||
|
<span class="truncate flex-1">${filename}</span>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a href="${fileUrl}" target="_blank" class="btn btn-sm btn-ghost">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-sm btn-ghost text-error" onclick="deleteFile('${filename}')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load current files:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to delete a file
|
||||||
|
async function deleteFile(filename: string) {
|
||||||
|
if (!eventId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const event = await get.getOne("events", eventId);
|
||||||
|
if (!event.files || !Array.isArray(event.files)) return;
|
||||||
|
|
||||||
|
// Remove the file from the array
|
||||||
|
const updatedFiles = event.files.filter((f) => f !== filename);
|
||||||
|
await update.updateField("events", eventId, "files", updatedFiles);
|
||||||
|
|
||||||
|
// Log successful deletion
|
||||||
|
await logger.send(
|
||||||
|
"delete",
|
||||||
|
"event files",
|
||||||
|
`Successfully deleted file ${filename} from event ${eventId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Refresh file list
|
||||||
|
await loadCurrentFiles();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to delete file:", err);
|
||||||
|
|
||||||
|
// Log deletion error
|
||||||
|
await logger.send(
|
||||||
|
"error",
|
||||||
|
"event files",
|
||||||
|
`Failed to delete file ${filename} from event ${eventId}. Error: ${err instanceof Error ? err.message : "Unknown error"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make deleteFile available globally
|
||||||
|
window.deleteFile = deleteFile;
|
||||||
|
|
||||||
|
// Load current files on page load
|
||||||
|
loadCurrentFiles();
|
||||||
|
</script>
|
||||||
|
|
|
@ -274,10 +274,12 @@ const majorsList: string[] = allMajors
|
||||||
import { Authentication } from "../pocketbase/Authentication";
|
import { Authentication } from "../pocketbase/Authentication";
|
||||||
import { Update } from "../pocketbase/Update";
|
import { Update } from "../pocketbase/Update";
|
||||||
import { SendLog } from "../pocketbase/SendLog";
|
import { SendLog } from "../pocketbase/SendLog";
|
||||||
|
import { FileManager } from "../pocketbase/FileManager";
|
||||||
|
|
||||||
const auth = Authentication.getInstance();
|
const auth = Authentication.getInstance();
|
||||||
const update = Update.getInstance();
|
const update = Update.getInstance();
|
||||||
const logger = SendLog.getInstance();
|
const logger = SendLog.getInstance();
|
||||||
|
const fileManager = FileManager.getInstance();
|
||||||
|
|
||||||
// Get form elements
|
// Get form elements
|
||||||
const memberIdInput = document.getElementById(
|
const memberIdInput = document.getElementById(
|
||||||
|
@ -355,10 +357,12 @@ const majorsList: string[] = allMajors
|
||||||
const user = auth.getCurrentUser();
|
const user = auth.getCurrentUser();
|
||||||
if (!user) throw new Error("User not authenticated");
|
if (!user) throw new Error("User not authenticated");
|
||||||
|
|
||||||
const formData = new FormData();
|
await fileManager.uploadFile(
|
||||||
formData.append("resume", file);
|
"users",
|
||||||
|
user.id,
|
||||||
await update.updateFields("users", user.id, formData);
|
"resume",
|
||||||
|
file
|
||||||
|
);
|
||||||
uploadStatus.textContent = "Resume uploaded successfully";
|
uploadStatus.textContent = "Resume uploaded successfully";
|
||||||
if (currentResume) currentResume.textContent = file.name;
|
if (currentResume) currentResume.textContent = file.name;
|
||||||
|
|
||||||
|
|
|
@ -201,10 +201,10 @@ const text = yaml.load(textConfig) as any;
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content Areas -->
|
<!-- Content Areas -->
|
||||||
<!-- <div id="defaultView">
|
<div id="defaultView">
|
||||||
<DefaultProfileView />
|
<DefaultProfileView />
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsView" class="hidden">
|
<!-- <div id="settingsView" class="hidden">
|
||||||
<UserSettings />
|
<UserSettings />
|
||||||
</div>
|
</div>
|
||||||
<div id="officerView" class="hidden">
|
<div id="officerView" class="hidden">
|
||||||
|
|
Loading…
Reference in a new issue