152 lines
5.2 KiB
TypeScript
152 lines
5.2 KiB
TypeScript
"use client";
|
|
import { DOCUMENT_FILE_ACCEPT, MAX_DOCUMENT_FILES } from "@/utils/shared";
|
|
import DocumentItem from "./DocumentItem";
|
|
import useCompanyDocumentsPresenter from "./useCompanyDocumentsPresenter";
|
|
|
|
interface IProps {
|
|
/** Present in edit mode; switches uploads to the company-documents endpoint. */
|
|
companyId?: string;
|
|
value: string[];
|
|
onChange: (ids: string[]) => void;
|
|
onRemovePersist?: (ids: string[]) => Promise<void>;
|
|
}
|
|
|
|
const CompanyDocuments = ({
|
|
companyId,
|
|
value,
|
|
onChange,
|
|
onRemovePersist,
|
|
}: IProps) => {
|
|
const {
|
|
isUploading,
|
|
isBusy,
|
|
removingFileId,
|
|
uploadProgress,
|
|
failed,
|
|
errorMessage,
|
|
onSelectFiles,
|
|
onRemove,
|
|
} = useCompanyDocumentsPresenter({
|
|
companyId,
|
|
value,
|
|
onChange,
|
|
onRemovePersist,
|
|
});
|
|
|
|
const inputId = "company-documents-input";
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="shadow-theme-xs relative overflow-hidden rounded-2xl border border-stroke/70 bg-gradient-to-br from-white via-white to-primary/[0.04] p-4 dark:border-dark-3 dark:from-dark-2 dark:via-dark-2 dark:to-primary/[0.12]">
|
|
<input
|
|
id={inputId}
|
|
type="file"
|
|
multiple
|
|
accept={DOCUMENT_FILE_ACCEPT}
|
|
className="sr-only"
|
|
onChange={onSelectFiles}
|
|
disabled={isBusy}
|
|
/>
|
|
<label
|
|
htmlFor={inputId}
|
|
aria-disabled={isBusy}
|
|
className={`group flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-stroke/80 bg-white/70 px-4 py-9 text-center transition-all duration-300 dark:border-dark-3 dark:bg-dark/20 ${
|
|
isBusy
|
|
? "cursor-not-allowed opacity-60"
|
|
: "cursor-pointer hover:-translate-y-0.5 hover:border-primary hover:bg-primary/[0.03] dark:hover:bg-primary/[0.08]"
|
|
}`}
|
|
>
|
|
<div className="shadow-theme-xs mb-3 rounded-2xl bg-primary/[0.12] p-3 text-primary transition-transform duration-300 group-hover:scale-105">
|
|
<svg
|
|
className="h-6 w-6"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<p className="text-sm font-semibold text-dark dark:text-white">
|
|
Click to upload documents
|
|
</p>
|
|
<p className="mt-1 text-xs text-dark-5">
|
|
PDF, Word, Excel, images, archives — up to 100MB each
|
|
</p>
|
|
<div className="mt-4 inline-flex items-center rounded-full border border-primary/20 bg-primary/[0.08] px-3 py-1 text-[11px] font-medium text-primary dark:border-primary/40 dark:bg-primary/[0.18]">
|
|
Max {MAX_DOCUMENT_FILES} files per upload
|
|
</div>
|
|
</label>
|
|
|
|
{isUploading ? (
|
|
<div className="mt-3">
|
|
<p className="text-xs font-medium text-primary">
|
|
Uploading documents… {companyId ? "" : `${uploadProgress}%`}
|
|
</p>
|
|
<div className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-gray-2 dark:bg-dark-3">
|
|
<div
|
|
className="h-full rounded-full bg-gradient-to-r from-primary to-purple-500 transition-all duration-300"
|
|
style={{ width: companyId ? "100%" : `${uploadProgress}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : removingFileId ? (
|
|
<div className="mt-3">
|
|
<p className="text-xs font-medium text-primary">
|
|
Removing document…
|
|
</p>
|
|
<div className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-gray-2 dark:bg-dark-3">
|
|
<div className="h-full w-full animate-pulse rounded-full bg-gradient-to-r from-primary to-purple-500" />
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
{errorMessage ? (
|
|
<p className="text-sm font-medium text-red-500">{errorMessage}</p>
|
|
) : null}
|
|
|
|
{value.length > 0 ? (
|
|
<div className="flex flex-col gap-2">
|
|
{value.map((fileId) => (
|
|
<DocumentItem
|
|
key={fileId}
|
|
fileId={fileId}
|
|
onRemove={onRemove}
|
|
disabled={isBusy}
|
|
isRemoving={removingFileId === fileId}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-sm text-dark-5">No documents attached yet.</p>
|
|
)}
|
|
|
|
{failed.length > 0 ? (
|
|
<div className="rounded-xl border border-error/35 bg-error/5 p-3">
|
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-error">
|
|
Failed uploads
|
|
</p>
|
|
<ul className="space-y-1">
|
|
{failed.map((item, index) => (
|
|
<li
|
|
key={`${item.filename}-${index}`}
|
|
className="text-sm text-error"
|
|
>
|
|
<span className="font-medium">{item.filename}</span>:{" "}
|
|
{item.error}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CompanyDocuments;
|