Update Document Upload module

This commit is contained in:
Tanuja Pansare 2026-07-10 12:02:38 +05:30
parent cab7f25f91
commit 4633e16094
1 changed files with 321 additions and 0 deletions

View File

@ -0,0 +1,321 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { AlertCircle, CalendarDays, Download, FileText, Mail, Phone, Upload } from "lucide-react";
import { PanelShell } from "../panels/PanelShell";
import { listMedia, uploadMediaFile } from "../../../services/api";
import type { LibraryContent } from "../../../services/api/types";
const SUPPORTED_MIME_TYPES = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/bmp",
];
const SUPPORTED_EXTENSIONS = [
".pdf",
".doc",
".docx",
".xls",
".xlsx",
".ppt",
".pptx",
".jpg",
".jpeg",
".png",
".gif",
".webp",
".bmp",
];
function isSupportedFile(file: File) {
const name = file.name.toLowerCase();
const hasSupportedExtension = SUPPORTED_EXTENSIONS.some((ext) => name.endsWith(ext));
const hasSupportedMime = SUPPORTED_MIME_TYPES.includes(file.type);
return hasSupportedExtension || hasSupportedMime;
}
function formatDateTime(value?: string) {
if (!value) return "—";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "—";
return new Intl.DateTimeFormat("en", {
dateStyle: "medium",
timeStyle: "short",
}).format(date);
}
function getUploaderName(item: LibraryContent) {
if (typeof item.uploadedBy === "object" && item.uploadedBy) {
const parts = [item.uploadedBy.firstName, item.uploadedBy.lastName].filter(Boolean);
if (parts.length) return parts.join(" ");
return item.uploadedBy.email || "Unknown uploader";
}
return "";
}
function getUploaderEmail(item: LibraryContent) {
if (typeof item.uploadedBy === "object" && item.uploadedBy) return item.uploadedBy.email || "—";
return "—";
}
function getUploaderPhone(item: LibraryContent) {
if (typeof item.uploadedBy === "object" && item.uploadedBy) return item.uploadedBy.phone || "—";
return "—";
}
export function EventDocumentsPanel({
eventId,
eventName,
onClose,
}: {
eventId: string;
eventName?: string;
onClose: () => void;
}) {
const fileInputRef = useRef<HTMLInputElement>(null);
const [documents, setDocuments] = useState<LibraryContent[]>([]);
const [loading, setLoading] = useState(false);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const loadDocuments = useCallback(async () => {
if (!eventId) return;
setLoading(true);
setError("");
try {
const res = await listMedia({ eventId, limit: 100 });
setDocuments(res.media ?? []);
} catch (err) {
setError(err instanceof Error ? err.message : "Could not load documents.");
setDocuments([]);
} finally {
setLoading(false);
}
}, [eventId]);
useEffect(() => {
void loadDocuments();
}, [loadDocuments]);
const acceptAttribute = useMemo(() => SUPPORTED_MIME_TYPES.join(","), []);
const handleUpload = async (file: File | null) => {
if (!file) return;
if (!isSupportedFile(file)) {
setError("Only PDF, Word, Excel, PowerPoint, and image files are supported.");
return;
}
setUploading(true);
setError("");
setSuccess("");
try {
await uploadMediaFile(file, { eventId, module: "event-documents", visibility: "private" });
setSuccess("Document uploaded successfully.");
await loadDocuments();
} catch (err) {
setError(err instanceof Error ? err.message : "Upload failed.");
} finally {
setUploading(false);
if (fileInputRef.current) fileInputRef.current.value = "";
}
};
return (
<PanelShell
onClose={onClose}
title="Upload Documents"
breadcrumb={eventName ? `Event · ${eventName}` : "Event documents"}
description="Upload and manage event documents for this event."
completedCount={1}
totalSections={1}
ctaLabel="Close"
hidePrimaryCta
onCtaClick={onClose}
panelWidth="min(1100px, 100vw)"
showProgress={false}
>
<div style={{ display: "grid", gridTemplateColumns: "minmax(0, 1.15fr) minmax(320px, 0.85fr)", gap: "20px", alignItems: "start" }}>
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
<div style={{ display: "flex", paddingLeft: "19px", alignItems: "center", justifyContent: "space-between", gap: "10px" }}>
<div>
<p style={{ margin: "4px 0 0", fontSize: "var(--text-base)", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", color: "var(--card-foreground)" }}>
Uploaded documents
</p>
<p style={{ margin: "4px 0 0", fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)" }}>
Files uploaded by you for this event appear here.
</p>
</div>
</div>
{error ? (
<div style={{ display: "flex", alignItems: "center", gap: "8px", padding: "10px 12px", paddingLeft: "19px", borderRadius: "var(--radius-button)", background: "color-mix(in srgb, var(--destructive) 10%, transparent)", color: "var(--destructive)", fontSize: "var(--text-sm)" }}>
<AlertCircle size={16} />
<span>{error}</span>
</div>
) : null}
{success ? (
<div style={{ display: "flex", alignItems: "center", gap: "8px", padding: "10px 12px", paddingLeft: "19px", borderRadius: "var(--radius-button)", background: "color-mix(in srgb, var(--primary) 12%, transparent)", color: "var(--primary)", fontSize: "var(--text-sm)" }}>
<FileText size={16} />
<span>{success}</span>
</div>
) : null}
{loading ? (
<p style={{ margin: 0, color: "var(--muted)", fontSize: "var(--text-sm)" }}>Loading documents…</p>
) : documents.length === 0 ? (
<div style={{ border: "1px dashed var(--border)", borderRadius: "var(--radius-card)", padding: "24px", paddingLeft: "12px", background: "var(--secondary)", textAlign: "center" }}>
<p style={{ margin: 0, paddingLeft: "12px", fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)" }}>
No documents uploaded yet for this event.
</p>
</div>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: "10px", padding: "14px 14px 8px", }}>
{documents.map((item) => (
<div key={item._id} style={{ border: "1px solid var(--border)", borderRadius: "var(--radius-card)", padding: "14px", background: "var(--card)" }}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "10px" }}>
{/* <div style={{ display: "flex", alignItems: "flex-start", gap: "10px", minWidth: 0 }}>
<div style={{ width: "36px", height: "36px", borderRadius: "50%", background: "color-mix(in srgb, var(--primary) 12%, transparent)", color: "var(--primary)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}>
<FileText size={18} />
</div>
<div style={{ minWidth: 0 }}>
<p style={{ margin: 0, fontSize: "var(--text-sm)", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", color: "var(--card-foreground)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{item.name || "Untitled document"}
</p>
<span style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", whiteSpace: "nowrap", flexShrink: 0,}}> • {getUploaderName(item) || "Uploaded by you"}
</span>
<p style={{ margin: "4px 0 0", fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)" }}>
{getUploaderName(item) || "Uploaded by you"}
</p>
</div>
</div> */}
<div
style={{
display: "flex",
alignItems: "flex-start",
gap: "10px",
minWidth: 0,
flex: 1,
}}
>
<div
style={{
minWidth: 0,
display: "flex",
flexDirection: "column",
flex: 1,
}}
>
{/* File Name */}
<p
style={{
margin: 0,
fontSize: "var(--text-sm)",
fontFamily: "var(--font-inter)",
fontWeight: "var(--font-weight-medium)",
color: "var(--card-foreground)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{item.name || "Untitled document"}
</p>
{/* Submitted by + Date */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
marginTop: "4px",
fontSize: "var(--text-xs)",
fontFamily: "var(--font-noto-sans)",
color: "var(--muted)",
flexWrap: "wrap",
}}
>
<span>
Submitted by {getUploaderName(item) || "You"}
</span>
<span>{formatDateTime(item.createdAt)}</span>
</div>
</div>
</div>
{item.url ? (
<button
type="button"
onClick={() => window.open(item.url, "_blank", "noopener,noreferrer")}
style={{ border: "1px solid var(--border)", background: "var(--card)", color: "var(--card-foreground)", borderRadius: "var(--radius-button)", padding: "6px 10px", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: "6px", flexShrink: 0, marginBottom: 0,}}
>
<Download size={14} />
Download
</button>
) : null}
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))", gap: "8px", marginTop: "10px" }}>
{
// <div style={{ padding: "10px 10px", borderRadius: "var(--radius-button)", background: "var(--secondary)" }}>
// <p style={{ margin: 0, fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--muted)" }}>Uploaded by</p>
// <p style={{ margin: "4px 0 0", fontSize: "var(--text-sm)", color: "var(--card-foreground)" }}>{getUploaderEmail(item)}</p>
// </div>
// <div style={{ padding: "10px 10px", borderRadius: "var(--radius-button)", background: "var(--secondary)" }}>
// <p style={{ margin: 0, fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--muted)" }}>Submitted</p>
// <p style={{ margin: "4px 0 0", fontSize: "var(--text-sm)", color: "var(--card-foreground)" }}>{formatDateTime(item.createdAt)}</p>
// </div>
// <div style={{ padding: "10px 10px", borderRadius: "var(--radius-button)", background: "var(--secondary)" }}>
// <p style={{ margin: 0, fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--muted)" }}>Phone</p>
// <p style={{ margin: "4px 0 0", fontSize: "var(--text-sm)", color: "var(--card-foreground)" }}>{getUploaderPhone(item)}</p>
// </div>
}
</div>
</div>
))}
</div>
)}
</div>
<div style={{ border: "1px solid var(--border)", borderRadius: "var(--radius-card)", padding: "16px", marginTop: "8px", background: "var(--card)", display: "flex", flexDirection: "column", gap: "12px", width: "calc(100% - 20px)" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "36px", height: "36px", borderRadius: "50%", background: "color-mix(in srgb, var(--primary) 12%, transparent)", color: "var(--primary)", display: "flex", alignItems: "center", justifyContent: "center" }}>
<Upload size={18} />
</div>
<div>
<p style={{ margin: 0, fontSize: "var(--text-sm)", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", color: "var(--card-foreground)" }}>Upload a document</p>
<p style={{ margin: "4px 0 0", fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)" }}>Allowed: PDF, Word, Excel, PowerPoint, and images.</p>
</div>
</div>
<input ref={fileInputRef} type="file" accept={acceptAttribute} onChange={(e) => { void handleUpload(e.target.files?.[0] ?? null); }} style={{ display: "none" }} />
<button
type="button"
onClick={() => fileInputRef.current?.click()}
disabled={uploading}
style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", gap: "8px", border: "none", borderRadius: "var(--radius-button)", background: "var(--primary)", color: "var(--primary-foreground)", padding: "10px 12px", cursor: uploading ? "not-allowed" : "pointer", opacity: uploading ? 0.7 : 1 }}
>
{uploading ? "Uploading…" : "Upload file"}
</button>
<div style={{ display: "grid", gap: "8px", fontSize: "var(--text-xs)", color: "var(--muted)", fontFamily: "var(--font-noto-sans)" }}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}><FileText size={14} /> PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX</div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}><CalendarDays size={14} /> JPG, PNG, GIF, BMP, WEBP images</div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}><Mail size={14} /> The uploader name, email, phone, and upload date are shown in the list.</div>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}><Phone size={14} /> Files are stored for this event and available for download.</div>
</div>
</div>
</div>
</PanelShell>
);
}