100 行
3.5 KiB
TypeScript
100 行
3.5 KiB
TypeScript
import type { FetchMyDelegationsResponse } from "../../api/delegations";
|
|
import { Button } from "../../components/ui/button";
|
|
import { PDF_FILE_ACCEPT, splitPdfFiles } from "../../lib/pdfFiles";
|
|
|
|
export type InvoiceUploadCardProps = {
|
|
selectedFiles: File[];
|
|
setSelectedFiles: (files: File[]) => void;
|
|
isUploading: boolean;
|
|
uploadProgressLabel: string;
|
|
onUpload: () => void;
|
|
showProxyOwnerSelector: boolean;
|
|
proxyDelegationsQuery: { data?: FetchMyDelegationsResponse };
|
|
uploadOwnerEmployeeId: string;
|
|
setUploadOwnerEmployeeId: (id: string) => void;
|
|
};
|
|
|
|
export function InvoiceUploadCard({
|
|
selectedFiles,
|
|
setSelectedFiles,
|
|
isUploading,
|
|
uploadProgressLabel,
|
|
onUpload,
|
|
showProxyOwnerSelector,
|
|
proxyDelegationsQuery,
|
|
uploadOwnerEmployeeId,
|
|
setUploadOwnerEmployeeId,
|
|
}: InvoiceUploadCardProps) {
|
|
const selectedLabel =
|
|
selectedFiles.length === 0
|
|
? "选择文件"
|
|
: selectedFiles.length === 1
|
|
? selectedFiles[0].name
|
|
: `已选择 ${selectedFiles.length} 个文件`;
|
|
|
|
return (
|
|
<div className="card-std p-6 space-y-4">
|
|
<h3 className="card-title">添加发票</h3>
|
|
{showProxyOwnerSelector ? (
|
|
<select
|
|
className="select-std"
|
|
data-testid="invoice-upload-owner"
|
|
onChange={(event) => setUploadOwnerEmployeeId(event.target.value)}
|
|
value={uploadOwnerEmployeeId}
|
|
>
|
|
<option value="">自己</option>
|
|
{(proxyDelegationsQuery.data?.received ?? [])
|
|
.filter((item) => item.status === "active")
|
|
.map((item) => (
|
|
<option key={item.id} value={item.principalEmployeeId}>
|
|
{item.principal?.name ?? item.principalEmployeeId}
|
|
</option>
|
|
))}
|
|
</select>
|
|
) : null}
|
|
<div className="flex items-center gap-3">
|
|
<label
|
|
htmlFor="file-upload"
|
|
className="cursor-pointer inline-flex h-9 items-center justify-center rounded-md border border-dashed border-zinc-300 bg-zinc-50 px-4 py-2 text-sm font-medium text-zinc-600 shadow-sm transition-colors hover:bg-zinc-100 hover:text-zinc-900 focus-within:ring-1 focus-within:ring-zinc-400"
|
|
>
|
|
<span className="material-symbols-outlined mr-2 text-[18px]">cloud_upload</span>
|
|
{selectedLabel}
|
|
</label>
|
|
<input
|
|
id="file-upload"
|
|
className="hidden"
|
|
accept={PDF_FILE_ACCEPT}
|
|
data-testid="invoice-upload-file"
|
|
multiple
|
|
onChange={(event) => {
|
|
setSelectedFiles(splitPdfFiles(event.target.files).accepted);
|
|
event.target.value = "";
|
|
}}
|
|
type="file"
|
|
/>
|
|
|
|
<Button
|
|
data-testid="invoice-upload-submit"
|
|
className={selectedFiles.length ? "" : "opacity-50"}
|
|
disabled={isUploading || selectedFiles.length === 0}
|
|
onClick={onUpload}
|
|
type="button"
|
|
>
|
|
{isUploading ? uploadProgressLabel || "上传中..." : "上传"}
|
|
</Button>
|
|
</div>
|
|
|
|
{selectedFiles.length ? (
|
|
<div className="rounded-xl border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
|
|
{selectedFiles.slice(0, 4).map((file) => (
|
|
<div key={`${file.name}-${file.size}`} className="truncate">
|
|
{file.name} · {Math.ceil(file.size / 1024)} KB
|
|
</div>
|
|
))}
|
|
{selectedFiles.length > 4 ? <div>另有 {selectedFiles.length - 4} 个文件</div> : null}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|