'use client' import * as React from 'react' import Link from 'next/link' import { useActionState } from 'react' import { useFormStatus } from 'react-dom' import { AlertCircle, Camera, CheckCircle2, FileUp, Loader2, Trash2, Upload, } from 'lucide-react' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Select } from '@/components/ui/select' import { Feld } from '@/components/feld' import { ACCEPT_ATTRIBUT, formatBytes } from '@/lib/speicher/dateityp' import { dokumenteHochladen, type Aktionszustand } from '@/server/actions/dokumente' import { ErkennungPanel } from '@/components/dokumente/erkennung-panel' import { cn } from '@/lib/utils' /** * Dokumente aufnehmen: per Drag&Drop, per Dateiauswahl oder direkt mit der * Handykamera. * * Das Ziel dieser Seite in einem Satz: **Foto hochladen, in zwei Klicks * einem Vorgang zuordnen, daraus eine Frist erzeugen.** Alles andere ist * Beiwerk. Deshalb sind Titel, Art und Absender optional - was der Brief * hergibt, schlaegt die Erkennung anschliessend selbst vor. */ export function UploadFormular({ vorgaenge, vorgangVorgewaehlt, kameraZuerst, }: { vorgaenge: { id: string; titel: string }[] vorgangVorgewaehlt?: string | null /** Aufruf aus der Schnellerfassung "Beleg fotografieren". */ kameraZuerst?: boolean }) { const [zustand, aktion] = useActionState(dokumenteHochladen, {}) const [dateien, setDateien] = React.useState([]) const [ueberzogen, setUeberzogen] = React.useState(false) const dateiFeld = React.useRef(null) const kameraFeld = React.useRef(null) /** * Die ausgewaehlten Dateien werden in einem eigenen Zustand gehalten und * vor dem Absenden in das versteckte Feld gespiegelt. Nur so lassen sich * Dateiauswahl, Kamera und Drag&Drop zu einem Stapel zusammenfuehren. */ const uebernehmen = React.useCallback((neue: FileList | null) => { if (!neue || neue.length === 0) return setDateien((bisher) => { const zusammen = [...bisher] for (const datei of Array.from(neue)) { const schonDa = zusammen.some( (d) => d.name === datei.name && d.size === datei.size && d.lastModified === datei.lastModified, ) if (!schonDa) zusammen.push(datei) } return zusammen.slice(0, 30) }) }, []) React.useEffect(() => { if (!dateiFeld.current) return const behaelter = new DataTransfer() for (const datei of dateien) behaelter.items.add(datei) dateiFeld.current.files = behaelter.files }, [dateien]) // Nach einem erfolgreichen Stapel die Auswahl leeren. React.useEffect(() => { if (zustand.ergebnisse && zustand.ergebnisse.some((e) => e.dokumentId)) { setDateien([]) } }, [zustand.ergebnisse]) const gesamt = dateien.reduce((summe, datei) => summe + datei.size, 0) const einzelnHochgeladen = zustand.ergebnisse?.filter((e) => e.dokumentId).length === 1 ? zustand.ergebnisse.find((e) => e.dokumentId)!.dokumentId! : null return (
uebernehmen(ereignis.target.files)} /> {/* Getrenntes Feld fuer die Kamera: capture darf nicht am Sammelfeld haengen, sonst bietet das Handy keine Dateiauswahl mehr an. */} { uebernehmen(ereignis.target.files) ereignis.target.value = '' }} /> {zustand.fehler && ( )}
{ ereignis.preventDefault() setUeberzogen(true) }} onDragLeave={() => setUeberzogen(false)} onDrop={(ereignis) => { ereignis.preventDefault() setUeberzogen(false) uebernehmen(ereignis.dataTransfer.files) }} className={cn( 'rounded-lg border-2 border-dashed p-6 text-center transition-colors sm:p-10', ueberzogen ? 'border-primary bg-primary/5' : 'border-border', )} >
{dateien.length > 0 && (
{dateien.length} {dateien.length === 1 ? 'Datei' : 'Dateien'} · {formatBytes(gesamt)}
    {dateien.map((datei, i) => (
  • {datei.name} {formatBytes(datei.size)}
  • ))}
)}
{zustand.ergebnisse && zustand.ergebnisse.length > 0 && (
e.dokumentId) ? 'erfolg' : 'warnung'}>
    {zustand.ergebnisse.map((ergebnis, i) => (
  • {ergebnis.dateiname} {ergebnis.dokumentId ? ( Öffnen ) : ( {ergebnis.fehler} )}
  • ))}
)} {einzelnHochgeladen && ( )}
) } function Absendeknopf({ anzahl }: { anzahl: number }) { const { pending } = useFormStatus() return ( ) }