diff --git a/src/App.jsx b/src/App.jsx index bd42e24..7705546 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,7 +14,6 @@ import CustomersPage from './pages/CustomersPage' import LeadsPage from './pages/LeadsPage' import CustomerDetailPage from './pages/CustomerDetailPage' import FinancePage from './pages/FinancePage' -import DocumentsPage from './pages/DocumentsPage' function ProtectedRoute({ children }) { const { user, loading } = useAuth() @@ -47,7 +46,6 @@ function AppRoutes() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/components/DocumentsPanel.jsx b/src/components/DocumentsPanel.jsx deleted file mode 100644 index 0892781..0000000 --- a/src/components/DocumentsPanel.jsx +++ /dev/null @@ -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 ( -
-
-
- Dokumente (Paperless) -
-
- - -
-
- -
-
- - -
-
- setQuery(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && load()} - /> - -
-
- - {error &&
{error}
} - - {loading ? ( -

Dokumente werden geladen...

- ) : docs.length === 0 ? ( -

- Keine Dokumente gefunden{scope === 'ticket' ? ' fuer dieses Ticket' : correspondent ? ` fuer ${correspondent}` : ''}. -

- ) : ( - <> -

{count} Dokument(e)

-
- {docs.map((d) => ( -
-
- -
-
{d.title}
-
{d.created ? new Date(d.created).toLocaleDateString('de-DE') : ''}
-
-
-
- - -
-
- ))} -
- - )} - - setShowUpload(false)} - workorderId={woid} - ticket={ticket} - onUploadComplete={() => setTimeout(load, 1500)} - /> -
- ) -} diff --git a/src/components/FileUploadModal.jsx b/src/components/FileUploadModal.jsx deleted file mode 100644 index abe25da..0000000 --- a/src/components/FileUploadModal.jsx +++ /dev/null @@ -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 ( -
- - - -
-

UPLOAD AREA

-

Upload Attachments for WOID {workorderId}

- -
{ e.preventDefault(); setDragOver(true) }} - onDragLeave={() => setDragOver(false)} - onClick={() => fileInputRef.current?.click()} - > - {uploading ? ( -
- -

{status || 'Uploading file...'}

-
- ) : ( - <> -

Drop file here (.pdf, .gif, .png, .jpg, .jpeg)

-

or

- - - )} -
- - -
-
- ) -} diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 1561366..4afa7d2 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -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: }], }, - { - label: 'Dokumente', - links: [{ label: 'Dokumente', href: '/documents', icon: }], - }, { label: 'Auswertung', links: [ diff --git a/src/components/TicketCard.jsx b/src/components/TicketCard.jsx index 0d63465..6ccbc9f 100644 --- a/src/components/TicketCard.jsx +++ b/src/components/TicketCard.jsx @@ -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' && } - {activeTab === 'documents' && } )} diff --git a/src/lib/appwrite.js b/src/lib/appwrite.js index e5b0a09..dc4097d 100644 --- a/src/lib/appwrite.js +++ b/src/lib/appwrite.js @@ -1,4 +1,4 @@ -import { Client, Account, Databases, Storage, ID, Query } from 'appwrite' +import { Client, Account, Databases, ID, Query } from 'appwrite' /** Ticket-System Appwrite-Projekt (Fallback wenn keine .env beim Build) */ const DEFAULT_PROJECT_ID = '6a1058610003c5a13a05' @@ -23,7 +23,6 @@ const client = new Client().setEndpoint(endpoint).setProject(projectId) export const account = new Account(client) export const databases = new Databases(client) -export const storage = new Storage(client) export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID || 'woms-database' @@ -43,8 +42,6 @@ export const COLLECTIONS = { export const PROJECT_TICKET_TYPE = 'Project' -export const BUCKET_ID = import.meta.env.VITE_APPWRITE_BUCKET_ID || 'woms-attachments' - /** Prüft ob vermutlich eine Session existiert (cookieFallback oder Same-Origin-Cookie). */ export function hasAppwriteSession() { if (typeof window === 'undefined' || !projectId) return false diff --git a/src/lib/integrationsApi.js b/src/lib/integrationsApi.js index cc143e9..6fad79d 100644 --- a/src/lib/integrationsApi.js +++ b/src/lib/integrationsApi.js @@ -1,7 +1,7 @@ import { account } from './appwrite' /** - * Client fuer das Integrations-Backend (InvoiceNinja + Paperless). + * Client fuer das Integrations-Backend (InvoiceNinja). * Same-Origin unter /api/integrations, authentifiziert per Appwrite-JWT. */ const BASE = '/api/integrations' @@ -64,16 +64,6 @@ export const integrationsApi = { const blob = await callBlob(`/invoices/${encodeURIComponent(id)}/pdf`) await openBlobInNewTab(blob) }, - - // ---- Paperless ---- - listDocuments: (params = {}) => call(`/paperless/documents${qs(params)}`), - ensureCorrespondent: (name) => call('/paperless/ensure-correspondent', { method: 'POST', body: { name } }), - ensureTag: (name) => call('/paperless/ensure-tag', { method: 'POST', body: { name } }), - uploadToPaperless: (payload) => call('/paperless/upload', { method: 'POST', body: payload }), - async openDocument(id, kind = 'preview') { - const blob = await callBlob(`/paperless/documents/${encodeURIComponent(id)}/${kind}`) - await openBlobInNewTab(blob) - }, } // Oeffentliche InvoiceNinja-URL (fuer Deeplinks ins Portal) diff --git a/src/pages/CustomerDetailPage.jsx b/src/pages/CustomerDetailPage.jsx index 14892f0..4ca7743 100644 --- a/src/pages/CustomerDetailPage.jsx +++ b/src/pages/CustomerDetailPage.jsx @@ -5,7 +5,6 @@ import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite' import { PageHeader, Card, Button, Badge, Tabs, EmptyState, Field } from '../components/ui' import { StatusPill, UrgentPill } from '../components/ui/StatusPill' import InvoicePanel from '../components/InvoicePanel' -import DocumentsPanel from '../components/DocumentsPanel' import AssignedProjectCard from '../components/AssignedProjectCard' import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential' import { updateCustomerWithPortalAccess } from '../lib/customerAdminApi' @@ -18,7 +17,6 @@ const TABS = [ { id: 'overview', label: 'Uebersicht' }, { id: 'tickets', label: 'Tickets' }, { id: 'invoices', label: 'Rechnungen' }, - { id: 'documents', label: 'Dokumente' }, { id: 'projects', label: 'Projekte' }, ] @@ -92,7 +90,6 @@ export default function CustomerDetailPage() { ) )} - {tab === 'documents' && } {tab === 'projects' && } ) @@ -114,7 +111,6 @@ function OverviewTab({ customer, stage, onSaved }) { location: customer.location || '', notes: customer.notes || '', importantCustomer: !!customer.importantCustomer, - paperlessCorrespondent: customer.paperlessCorrespondent || '', }) const [busy, setBusy] = useState(false) const [msg, setMsg] = useState(null) @@ -186,9 +182,6 @@ function OverviewTab({ customer, stage, onSaved }) { InvoiceNinja {customer.invoiceNinjaClientId ? verknuepft : nicht verknuepft} - - setForm({ ...form, paperlessCorrespondent: e.target.value })} placeholder={customer.name} /> - diff --git a/src/pages/CustomersPage.jsx b/src/pages/CustomersPage.jsx index 8ce76c2..da1af11 100644 --- a/src/pages/CustomersPage.jsx +++ b/src/pages/CustomersPage.jsx @@ -1,6 +1,6 @@ import { useState, useMemo } from 'react' import { Link } from 'react-router-dom' -import { FaSpinner, FaPlus, FaStar, FaFileInvoiceDollar, FaFileLines } from 'react-icons/fa6' +import { FaSpinner, FaPlus, FaStar, FaFileInvoiceDollar } from 'react-icons/fa6' import { useCustomers } from '../hooks/useCustomers' import { useAuth } from '../context/AuthContext' import { createLead, customerStage, isArchived } from '../lib/leads' @@ -92,7 +92,6 @@ export default function CustomersPage() { {st === 'customer' && Fester Kunde} {st === 'lost' && Abgesagt} {c.invoiceNinjaClientId && Ninja} - {c.paperlessCorrespondent && Docs} ) diff --git a/src/pages/DashboardPage.jsx b/src/pages/DashboardPage.jsx index ce12aa9..2d38316 100644 --- a/src/pages/DashboardPage.jsx +++ b/src/pages/DashboardPage.jsx @@ -1,8 +1,8 @@ import { useState, useEffect } from 'react' import { Link } from 'react-router-dom' import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite' -import { FaTicket, FaClipboardList, FaClock, FaCircleCheck, FaPlus, FaFileInvoiceDollar, FaUpload } from 'react-icons/fa6' -import { integrationsApi, formatMoney } from '../lib/integrationsApi' +import { FaTicket, FaClipboardList, FaClock, FaCircleCheck, FaPlus, FaFileInvoiceDollar } from 'react-icons/fa6' +import { formatMoney } from '../lib/integrationsApi' import { useFinanceSummary } from '../hooks/useFinanceSummary' import { PageHeader, Card, Button, Kpi, EmptyState } from '../components/ui' import { StatusPill, UrgentPill } from '../components/ui/StatusPill' @@ -11,7 +11,6 @@ export default function DashboardPage() { const { summary, loading: finLoading } = useFinanceSummary() const [stats, setStats] = useState({ total: 0, open: 0, awaiting: 0, closed: 0 }) const [recent, setRecent] = useState([]) - const [docCount, setDocCount] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { @@ -33,10 +32,6 @@ export default function DashboardPage() { } finally { if (active) setLoading(false) } - try { - const d = await integrationsApi.listDocuments({ page_size: 1 }) - if (active) setDocCount(d.count) - } catch { /* ignore */ } })() return () => { active = false } }, []) @@ -45,7 +40,7 @@ export default function DashboardPage() {
@@ -65,7 +60,6 @@ export default function DashboardPage() { -
@@ -95,7 +89,6 @@ export default function DashboardPage() {
-
diff --git a/src/pages/DocumentsPage.jsx b/src/pages/DocumentsPage.jsx deleted file mode 100644 index 33d274d..0000000 --- a/src/pages/DocumentsPage.jsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useState, useEffect, useCallback } from 'react' -import { FaFileLines, FaEye, FaDownload, FaSpinner, FaMagnifyingGlass, FaRotate, FaUpload } from 'react-icons/fa6' -import { integrationsApi } from '../lib/integrationsApi' -import { useCustomers } from '../hooks/useCustomers' -import { PageHeader, Card, Button, EmptyState } from '../components/ui' -import FileUploadModal from '../components/FileUploadModal' - -export default function DocumentsPage() { - const { customers } = useCustomers() - const [docs, setDocs] = useState([]) - const [count, setCount] = useState(0) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [query, setQuery] = useState('') - const [correspondent, setCorrespondent] = useState('') - const [busyId, setBusyId] = useState(null) - const [showUpload, setShowUpload] = useState(false) - - const load = useCallback(async () => { - setLoading(true) - setError(null) - try { - const params = { page_size: 50 } - if (query) params.query = query - if (correspondent) params.correspondent = correspondent - const data = await integrationsApi.listDocuments(params) - setDocs(data.documents || []) - setCount(data.count || 0) - } catch (err) { - setError(err.message) - setDocs([]) - } finally { - setLoading(false) - } - }, [query, correspondent]) - - useEffect(() => { load() }, [load]) - - const open = async (id, kind) => { - setBusyId(id + kind) - try { await integrationsApi.openDocument(id, kind) } - catch (err) { alert('Fehler: ' + err.message) } - finally { setBusyId(null) } - } - - return ( -
- - - - - } - /> - - -
- -
- setQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && load()} /> - -
-
-
- {loading ? ( -
- ) : error ? ( -
{error}
- ) : docs.length === 0 ? ( - } title="Keine Dokumente" hint="Keine Dokumente gefunden." /> - ) : ( - <> -
{count} Dokument(e)
-
- {docs.map((d) => ( -
- -
-
{d.title}
-
{d.created ? new Date(d.created).toLocaleDateString('de-DE') : ''}
-
- - -
- ))} -
- - )} -
-
- - setShowUpload(false)} - workorderId="" - ticket={correspondent ? { customerName: correspondent, woid: '' } : { customerName: '', woid: '' }} - onUploadComplete={() => setTimeout(load, 1500)} - /> -
- ) -}