Dokumente und Paperless aus WOMS entfernt (Paket C)

Paperless enthält 0 Dokumente, der Upload wurde nie benutzt. Weg sind:
die Seite Dokumente (alte Links führen zu den Tickets), der Menüpunkt,
der Reiter Dokumente in Ticketkarte und Kundenseite, der Upload-Dialog
(legte Dateien zusätzlich ungelesen im Appwrite-Bucket ab), das Feld
"Paperless-Korrespondent" und das Abzeichen "Docs", auf dem Dashboard
die Kachel Dokumente und "Dokument hochladen", im API-Client die
Paperless-Aufrufe. Gespeicherte Daten bleiben unverändert; die
Paperless-Routen im Backend ticket-integrations sind nicht angefasst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-27 15:52:46 +00:00
1 parent 062d992b63
commit 34c4db0228
11 files changed
+6 -437

No files matched your search

-158
View File
@@ -1,158 +0,0 @@
import { useState, useEffect, useCallback } from 'react'
import { FaFileLines, FaDownload, FaEye, FaSpinner, FaRotate, FaUpload, FaMagnifyingGlass } from 'react-icons/fa6'
import { integrationsApi } from '../lib/integrationsApi'
import FileUploadModal from './FileUploadModal'
const card = {
background: 'rgba(45, 55, 72, 0.5)',
borderRadius: '12px',
padding: '16px',
border: '1px solid rgba(16, 185, 129, 0.2)',
}
export default function DocumentsPanel({ ticket, initialScope = 'ticket' }) {
const [docs, setDocs] = useState([])
const [count, setCount] = useState(0)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [query, setQuery] = useState('')
const [scope, setScope] = useState(initialScope) // 'ticket' | 'customer'
const [showUpload, setShowUpload] = useState(false)
const [busyId, setBusyId] = useState(null)
const correspondent = ticket.customerName || ''
const woid = ticket.woid || ''
const load = useCallback(async () => {
setLoading(true)
setError(null)
try {
const params = { page_size: 30 }
if (query) params.query = query
if (scope === 'customer' && correspondent) params.correspondent = correspondent
if (scope === 'ticket' && woid) params.tag = `WOID-${woid}`
const data = await integrationsApi.listDocuments(params)
setDocs(data.documents || [])
setCount(data.count || 0)
} catch (err) {
setError(err.message)
setDocs([])
} finally {
setLoading(false)
}
}, [query, scope, correspondent, woid])
useEffect(() => {
load()
}, [load])
const open = async (id, kind) => {
setBusyId(id + kind)
try {
await integrationsApi.openDocument(id, kind)
} catch (err) {
alert('Dokument konnte nicht geoeffnet werden: ' + err.message)
} finally {
setBusyId(null)
}
}
return (
<div style={card}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px', flexWrap: 'wrap', gap: '8px' }}>
<h5 style={{ color: 'var(--dark-text)', fontWeight: 'bold', margin: 0, fontSize: '16px' }}>
Dokumente (Paperless)
</h5>
<div style={{ display: 'flex', gap: '8px' }}>
<button className="btn btn-green" style={{ margin: 0 }} onClick={() => setShowUpload(true)}>
<FaUpload /> Hochladen
</button>
<button className="btn" onClick={load} title="Aktualisieren"
style={{ background: '#374151', color: 'white', border: 'none', padding: '6px 10px', borderRadius: '6px', cursor: 'pointer' }}>
<FaRotate />
</button>
</div>
</div>
<div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: '4px' }}>
<button
className={`btn ${scope === 'ticket' ? 'btn-green' : ''}`}
style={{ margin: 0, background: scope === 'ticket' ? undefined : '#374151', color: 'white' }}
onClick={() => setScope('ticket')}
>
Dieses Ticket
</button>
<button
className={`btn ${scope === 'customer' ? 'btn-green' : ''}`}
style={{ margin: 0, background: scope === 'customer' ? undefined : '#374151', color: 'white' }}
onClick={() => setScope('customer')}
disabled={!correspondent}
>
Ganzer Kunde
</button>
</div>
<div style={{ display: 'flex', gap: '6px', flex: 1, minWidth: '180px' }}>
<input
className="form-control"
style={{ margin: 0 }}
placeholder="Volltextsuche..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && load()}
/>
<button className="btn btn-green" style={{ margin: 0 }} onClick={load}>
<FaMagnifyingGlass />
</button>
</div>
</div>
{error && <div className="text-red" style={{ marginBottom: 8 }}>{error}</div>}
{loading ? (
<p style={{ color: '#a0aec0' }}><FaSpinner className="spinner" /> Dokumente werden geladen...</p>
) : docs.length === 0 ? (
<p style={{ color: '#a0aec0', fontSize: '13px' }}>
Keine Dokumente gefunden{scope === 'ticket' ? ' fuer dieses Ticket' : correspondent ? ` fuer ${correspondent}` : ''}.
</p>
) : (
<>
<p style={{ color: '#718096', fontSize: '12px', marginTop: 0 }}>{count} Dokument(e)</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{docs.map((d) => (
<div key={d.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px', background: 'rgba(0,0,0,0.15)', borderRadius: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', minWidth: 0 }}>
<FaFileLines style={{ color: '#10b981', flexShrink: 0 }} />
<div style={{ minWidth: 0 }}>
<div style={{ color: 'var(--dark-text)', fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.title}</div>
<div style={{ color: '#718096', fontSize: '12px' }}>{d.created ? new Date(d.created).toLocaleDateString('de-DE') : ''}</div>
</div>
</div>
<div style={{ display: 'flex', gap: '6px', flexShrink: 0 }}>
<button className="btn" title="Vorschau" disabled={busyId === d.id + 'preview'}
style={{ background: '#3b82f6', color: 'white', border: 'none', padding: '6px 10px', borderRadius: '6px', cursor: 'pointer' }}
onClick={() => open(d.id, 'preview')}>
{busyId === d.id + 'preview' ? <FaSpinner className="spinner" /> : <FaEye />}
</button>
<button className="btn" title="Download" disabled={busyId === d.id + 'download'}
style={{ background: '#374151', color: 'white', border: 'none', padding: '6px 10px', borderRadius: '6px', cursor: 'pointer' }}
onClick={() => open(d.id, 'download')}>
{busyId === d.id + 'download' ? <FaSpinner className="spinner" /> : <FaDownload />}
</button>
</div>
</div>
))}
</div>
</>
)}
<FileUploadModal
isOpen={showUpload}
onClose={() => setShowUpload(false)}
workorderId={woid}
ticket={ticket}
onUploadComplete={() => setTimeout(load, 1500)}
/>
</div>
)
}
-124
View File
@@ -1,124 +0,0 @@
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>
)
}
-5
View File
@@ -6,7 +6,6 @@ import {
IconUsers,
IconTargetArrow,
IconReportMoney,
IconFiles,
IconFolders,
IconCalendar,
IconRoute,
@@ -49,10 +48,6 @@ export default function Sidebar() {
label: 'Finanzen',
links: [{ label: 'Finanzen', href: '/finance', icon: <IconReportMoney /> }],
},
{
label: 'Dokumente',
links: [{ label: 'Dokumente', href: '/documents', icon: <IconFiles /> }],
},
{
label: 'Auswertung',
links: [
-3
View File
@@ -14,7 +14,6 @@ import TicketProjectsModal from './TicketProjectsModal'
import TicketRoadmapPanel from './TicketRoadmapPanel'
import ProjectContextPanel from './ProjectContextPanel'
import InvoicePanel from './InvoicePanel'
import DocumentsPanel from './DocumentsPanel'
import Tabs from './ui/Tabs'
import Badge from './ui/Badge'
import { StatusPill, UrgentPill } from './ui/StatusPill'
@@ -29,7 +28,6 @@ const TABS = [
{ id: 'details', label: 'Details' },
{ id: 'worksheets', label: 'Arbeitsblätter' },
{ id: 'invoices', label: 'Rechnungen' },
{ id: 'documents', label: 'Dokumente' },
]
export default function TicketCard({ ticket, onUpdate }) {
@@ -154,7 +152,6 @@ export default function TicketCard({ ticket, onUpdate }) {
)}
{activeTab === 'invoices' && <InvoicePanel ticket={ticket} />}
{activeTab === 'documents' && <DocumentsPanel ticket={ticket} />}
</div>
</div>
)}