Update Document Upload module
This commit is contained in:
parent
cab7f25f91
commit
4633e16094
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue