Files
tickte-system/src/components/FileUploadModal.jsx
T
Webklar DeployandCursor 8f6927fbb0 feat: Rechnungen, Dokumente und Preview im Ticket buendeln
- InvoiceNinja-Rechnungen pro Kunde/Ticket anzeigen, verknuepfen und aus
  Ticket heraus erstellen (integrationsApi, useInvoices, InvoicePanel)
- Paperless-ngx: Ticket-Uploads automatisch synchronisieren und Kunden-/
  Ticket-Dokumente durchsuchen/anzeigen (DocumentsPanel, FileUploadModal)
- Projekt-/Preview-Karte mit Status, Provisioning und letztem Commit
- Einheitliches Ticket-Detail mit Tabs (Details/Arbeitsblaetter/Rechnungen/
  Dokumente/Projekt) + zugehoerige Styles

Aufruf des Integrations-Backends same-origin per Appwrite-JWT.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 09:13:35 +00:00

125 lines
3.6 KiB
React

import { useState, useRef } from 'react'
import { FaTimes, FaSpinner } from 'react-icons/fa'
import { storage, BUCKET_ID, ID } from '../lib/appwrite'
import { integrationsApi } from '../lib/integrationsApi'
export default function FileUploadModal({ isOpen, onClose, workorderId, ticket, onUploadComplete }) {
const [uploading, setUploading] = useState(false)
const [dragOver, setDragOver] = useState(false)
const [status, setStatus] = useState('')
const fileInputRef = useRef(null)
const handleDrop = async (e) => {
e.preventDefault()
setDragOver(false)
const file = e.dataTransfer.files[0]
if (file) {
await uploadFile(file)
}
}
const handleFileSelect = async (e) => {
const file = e.target.files[0]
if (file) {
await uploadFile(file)
}
}
const uploadFile = async (file) => {
// Validate file type
const allowedTypes = ['application/pdf', 'image/gif', 'image/png', 'image/jpeg']
if (!allowedTypes.includes(file.type)) {
alert('Only PDF, GIF, PNG, and JPEG files are allowed.')
return
}
setUploading(true)
setStatus('Lade in Ablage hoch...')
try {
const response = await storage.createFile(
BUCKET_ID,
ID.unique(),
file
)
// Automatisch nach Paperless-ngx schieben (wichtige Kundendokumente)
const woid = ticket?.woid || workorderId
const correspondent = ticket?.customerName || undefined
if (correspondent || woid) {
setStatus('Synchronisiere mit Paperless...')
const tags = []
if (correspondent) tags.push(correspondent)
if (woid) tags.push(`WOID-${woid}`)
tags.push('Ticket')
try {
await integrationsApi.uploadToPaperless({
bucketId: BUCKET_ID,
fileId: response.$id,
filename: file.name,
title: `${woid ? `[${woid}] ` : ''}${file.name}`,
correspondent,
tags,
})
} catch (e) {
console.warn('Paperless-Sync fehlgeschlagen:', e.message)
// Upload in Appwrite ist trotzdem erfolgt - nicht hart abbrechen
}
}
onUploadComplete?.(response)
onClose()
} catch (error) {
console.error('Upload error:', error)
alert('Error uploading file: ' + error.message)
} finally {
setUploading(false)
setStatus('')
}
}
if (!isOpen) return null
return (
<div className="overlay">
<span className="overlay-close" onClick={onClose}>
<FaTimes />
</span>
<div className="overlay-content">
<h2 className="text-center mb-2">UPLOAD AREA</h2>
<p className="mb-2">Upload Attachments for WOID {workorderId}</p>
<div
className={`drop-zone ${dragOver ? 'border-green' : ''}`}
onDrop={handleDrop}
onDragOver={(e) => { e.preventDefault(); setDragOver(true) }}
onDragLeave={() => setDragOver(false)}
onClick={() => fileInputRef.current?.click()}
>
{uploading ? (
<div>
<FaSpinner className="spinner" size={32} />
<p className="mt-1">{status || 'Uploading file...'}</p>
</div>
) : (
<>
<p>Drop file here (.pdf, .gif, .png, .jpg, .jpeg)</p>
<p className="mt-1">or</p>
<button className="btn btn-dark mt-1">Select File</button>
</>
)}
</div>
<input
ref={fileInputRef}
type="file"
accept=".pdf,.gif,.png,.jpg,.jpeg"
style={{ display: 'none' }}
onChange={handleFileSelect}
/>
</div>
</div>
)
}