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:
1 parent
062d992b63
commit
34c4db0228
11 files changed
+6
-437
No files matched your search
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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: [
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user