import { useState, useEffect, useCallback, useMemo } from 'react' import { databases, DATABASE_ID, COLLECTIONS, Query, ID, isDemoMode } from '../lib/appwrite' const DEMO_MODE = isDemoMode function buildFiltersKey(filters = {}) { return JSON.stringify({ limit: filters.limit ?? null, customerId: filters.customerId ?? null, assignedTo: filters.assignedTo ?? null, status: filters.status ?? null, type: filters.type ?? null, priority: filters.priority ?? null, }) } // Demo data for testing without Appwrite const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) const DEMO_WORKORDERS = [ { $id: 'dummy-10001', woid: '10001', topic: 'Kompletter Systemausfall - Server & Netzwerk', status: 'Assigned', priority: 4, type: 'Emergency Call', systemType: 'Server', responseLevel: 'Backoffice', serviceType: 'On Site', customerName: 'Kunde A', customerLocation: 'Hauptstraße 123, 12345 Musterstadt', assignedTo: 'user-max-id', assignedName: 'Max Mustermann', requestedBy: 'Dr. Anna Schmidt', requestedFor: 'IT-Abteilung Kunde A', startDate: '30.12.2025', startTime: '0800', deadline: '31.12.2025', endTime: '1800', estimate: '480', mailCopyTo: 'admin@kunde-a.de, it@kunde-a.de', sendNotification: true, details: `KRITISCHER SYSTEMAUSFALL - SOFORTIGE BEARBEITUNG ERFORDERLICH Problembeschreibung: - Kompletter Serverausfall im Rechenzentrum - Alle Server sind offline (keine Verbindung möglich) - Netzwerk-Infrastruktur betroffen - Keine Backup-Systeme verfügbar Betroffene Systeme: - Hauptserver (Windows Server 2022) - Datenbankserver (SQL Server 2019) - Fileserver - Exchange Server - Netzwerk-Switches Auswirkungen: - Keine E-Mail-Kommunikation möglich - Alle Anwendungen offline - Kein Zugriff auf Datenbanken - Produktion steht still Dringlichkeit: KRITISCH - Produktionsausfall Erwartete Bearbeitungszeit: 8 Stunden Benötigte Ressourcen: 2 Techniker, Hardware-Ersatzteile`, approvalStatus: 'approved', $createdAt: lastWeek.toISOString(), createdAt: lastWeek.toISOString() }, { $id: '2', woid: '10002', title: 'Netzwerk Problem', description: 'WLAN funktioniert nicht', status: 'Occupied', priority: 3, type: 'Support', customerName: 'Kunde B', assignedName: 'Lisa S.', response: 'Remote', $createdAt: new Date().toISOString() }, { $id: '3', woid: '10003', title: 'Software Installation', description: 'Office 365 Setup', status: 'Assigned', priority: 1, type: 'Installation', customerName: 'Kunde C', assignedName: 'Tom K.', response: 'Onsite', $createdAt: new Date().toISOString() }, { $id: '4', woid: '10004', title: 'Drucker defekt', description: 'Papierstau', status: 'Awaiting', priority: 2, type: 'Hardware', customerName: 'Kunde D', assignedName: '', response: 'Pickup', $createdAt: new Date().toISOString() }, { $id: '5', woid: '10005', title: 'Kritischer Serverausfall', description: 'Produktionsserver down', status: 'Open', priority: 4, type: 'Emergency', customerName: 'Kunde E', assignedName: 'Max M.', response: 'Onsite', $createdAt: new Date().toISOString() }, ] export function useWorkorders(filters = {}) { const [workorders, setWorkorders] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const filtersKey = useMemo(() => buildFiltersKey(filters), [ filters.limit, filters.customerId, filters.assignedTo, filters.status, filters.type, filters.priority, ]) const fetchWorkorders = useCallback(async () => { const activeFilters = JSON.parse(filtersKey) setLoading(true) if (DEMO_MODE) { // Filter demo data let filtered = [...DEMO_WORKORDERS] if (activeFilters.status?.length > 0) { filtered = filtered.filter(wo => activeFilters.status.includes(wo.status)) } if (activeFilters.priority?.length > 0) { filtered = filtered.filter(wo => activeFilters.priority.includes(wo.priority)) } if (activeFilters.limit) { filtered = filtered.slice(0, activeFilters.limit) } setWorkorders(filtered) setLoading(false) return } try { const queries = [Query.orderDesc('$createdAt')] if (activeFilters.limit) { queries.push(Query.limit(activeFilters.limit)) } // Appwrite Query.equal mit Array = OR-Match. Serverseitig filtern, damit // das Limit erst NACH dem Statusfilter greift (sonst wuerden archivierte // Tickets die neuesten Slots belegen und aktive verdraengen). if (activeFilters.status && activeFilters.status.length > 0) { queries.push(Query.equal('status', activeFilters.status)) } if (activeFilters.type && activeFilters.type.length > 0) { queries.push(Query.equal('type', activeFilters.type)) } if (activeFilters.priority && activeFilters.priority.length > 0) { queries.push(Query.equal('priority', activeFilters.priority)) } if (activeFilters.customerId) { queries.push(Query.equal('customerId', activeFilters.customerId)) } if (activeFilters.assignedTo) { queries.push(Query.equal('assignedTo', activeFilters.assignedTo)) } // Debug: Zeige Collection ID if (import.meta.env.DEV) { console.log('📋 Fetching workorders:') console.log(' Database ID:', DATABASE_ID) console.log(' Collection ID:', COLLECTIONS.WORKORDERS) console.log(' Queries:', queries.length) } const response = await databases.listDocuments( DATABASE_ID, COLLECTIONS.WORKORDERS, queries ) setWorkorders(response.documents) setError(null) } catch (err) { let errorMessage = err.message || 'Fehler beim Laden der Tickets' // Bessere Fehlermeldungen if (err.code === 401 || errorMessage.includes('not authorized') || errorMessage.includes('Unauthorized')) { errorMessage = 'Berechtigung fehlt: Bitte überprüfe die Read-Berechtigungen der Collection in Appwrite. Die Collection muss "Users" oder "Any" als Read-Berechtigung haben.' } else if (errorMessage.includes('Collection') && errorMessage.includes('not found')) { errorMessage = 'Collection nicht gefunden: Bitte überprüfe die Collection ID in der Konfiguration.' } setError(errorMessage) console.error('Error fetching workorders:', err) } finally { setLoading(false) } }, [filtersKey]) useEffect(() => { fetchWorkorders() }, [fetchWorkorders]) const resolveAutomaticTicketFields = async (requestedAssignee = '') => { const [allTicketsResponse, employeesResponse] = await Promise.all([ databases.listDocuments( DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.limit(5000)] ), databases.listDocuments( DATABASE_ID, COLLECTIONS.EMPLOYEES, [Query.orderAsc('displayName'), Query.limit(500)] ), ]) const allTickets = allTicketsResponse.documents || [] const employees = employeesResponse.documents || [] const maxWoid = allTickets.reduce((highest, ticket) => { const numericId = Number.parseInt(ticket.woid, 10) return Number.isFinite(numericId) ? Math.max(highest, numericId) : highest }, 9999) let assignee = requestedAssignee ? employees.find((employee) => employee.userId === requestedAssignee) : null if (!assignee && employees.length > 0) { const finishedStatuses = new Set(['Closed', 'Completed', 'Cancelled', 'Done']) const activeCounts = new Map(employees.map((employee) => [employee.userId, 0])) for (const ticket of allTickets) { if (!ticket.assignedTo || finishedStatuses.has(ticket.status)) continue if (activeCounts.has(ticket.assignedTo)) { activeCounts.set(ticket.assignedTo, activeCounts.get(ticket.assignedTo) + 1) } } assignee = [...employees].sort((left, right) => { const loadDifference = activeCounts.get(left.userId) - activeCounts.get(right.userId) if (loadDifference !== 0) return loadDifference return (left.displayName || '').localeCompare(right.displayName || '', 'de') })[0] } return { woid: String(maxWoid + 1), assignedTo: assignee?.userId || '', assignedName: assignee?.displayName || '', } } const createWorkorder = async (data) => { if (DEMO_MODE) { // Finde höchste WOID und +1 const maxWoid = workorders.length > 0 ? Math.max(...workorders.map(wo => parseInt(wo.woid)).filter(w => !isNaN(w))) : 9999; const nextWoid = maxWoid + 1; const newWo = { ...data, $id: Date.now().toString(), woid: nextWoid.toString(), status: 'Open', $createdAt: new Date().toISOString() } setWorkorders(prev => [newWo, ...prev]) return { success: true, data: newWo } } try { // Validierung: Prüfe required fields if (!data.topic || data.topic.trim() === '') { return { success: false, error: 'Das Feld "Topic" ist erforderlich.' } } const automatic = await resolveAutomaticTicketFields(data.assignedTo) const autoStatus = automatic.assignedTo ? 'Assigned' : 'Open' // Bereite Daten für Appwrite vor const workorderData = { // Required fields topic: data.topic.trim(), status: autoStatus, priority: typeof data.priority === 'number' ? data.priority : parseInt(data.priority) || 1, woid: automatic.woid, // Optional fields - nur senden wenn vorhanden type: data.type || '', systemType: data.systemType || '', responseLevel: data.responseLevel || '', serviceType: data.serviceType || 'Remote', customerId: data.customerId || '', assignedTo: automatic.assignedTo, assignedName: automatic.assignedName, requestedBy: data.requestedBy || '', requestedFor: data.requestedFor || '', startDate: data.startDate || '', startTime: data.startTime || '', deadline: data.deadline || '', endTime: data.endTime || '', estimate: data.estimate || '', mailCopyTo: data.mailCopyTo || '', sendNotification: data.sendNotification || false, details: data.details || '', // Datetime field createdAt: new Date().toISOString() } // Entferne leere Strings (außer für required fields) Object.keys(workorderData).forEach(key => { if (workorderData[key] === '' && key !== 'topic' && key !== 'status') { delete workorderData[key] } }) console.log('Creating workorder with data:', workorderData) const response = await databases.createDocument( DATABASE_ID, COLLECTIONS.WORKORDERS, ID.unique(), workorderData ) setWorkorders(prev => [response, ...prev]) return { success: true, data: response } } catch (err) { console.error('Error creating workorder:', err) let errorMessage = err.message || 'Fehler beim Erstellen des Tickets' // Bessere Fehlermeldungen if (err.code === 400 || errorMessage.includes('Bad Request')) { errorMessage = 'Ungültige Daten: Bitte überprüfe, ob alle Pflichtfelder ausgefüllt sind und die Daten korrekt sind. Details: ' + (err.message || 'Unbekannter Fehler') } else if (errorMessage.includes('required') || errorMessage.includes('missing')) { errorMessage = 'Pflichtfelder fehlen: Bitte fülle alle erforderlichen Felder aus.' } return { success: false, error: errorMessage } } } const updateWorkorder = async (id, data) => { if (DEMO_MODE) { setWorkorders(prev => prev.map(wo => wo.$id === id ? { ...wo, ...data } : wo)) return { success: true } } try { // Status-Automatik beim Update: // Wenn assignedTo gesetzt wird → Status = "Assigned" // Wenn assignedTo entfernt wird UND Status = "Assigned" → Status = "Open" const updateData = { ...data } if ('assignedTo' in updateData) { if (updateData.assignedTo && updateData.assignedTo !== '') { // Mitarbeiter zugewiesen → Status auf "Assigned" setzen if (!updateData.status) { updateData.status = 'Assigned' } } else if (!updateData.status) { // Keine Zuweisung mehr → Status auf "Open" setzen (nur wenn nicht explizit anders gesetzt) updateData.status = 'Open' } } const response = await databases.updateDocument( DATABASE_ID, COLLECTIONS.WORKORDERS, id, updateData ) setWorkorders(prev => prev.map(wo => wo.$id === id ? response : wo) ) return { success: true, data: response } } catch (err) { return { success: false, error: err.message } } } const deleteWorkorder = async (id) => { if (DEMO_MODE) { setWorkorders(prev => prev.filter(wo => wo.$id !== id)) return { success: true } } try { await databases.deleteDocument( DATABASE_ID, COLLECTIONS.WORKORDERS, id ) setWorkorders(prev => prev.filter(wo => wo.$id !== id)) return { success: true } } catch (err) { return { success: false, error: err.message } } } return { workorders, loading, error, refresh: fetchWorkorders, createWorkorder, updateWorkorder, deleteWorkorder } }