Files
tickte-system/src/hooks/useWorksheets.js
2025-12-30 20:29:59 +01:00

479 lines
14 KiB
JavaScript

import { useState, useEffect, useCallback } from 'react'
import { databases, DATABASE_ID, COLLECTIONS, Query, ID } from '../lib/appwrite'
const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID
// Demo data für Testing - Vollständiges Dummy-Ticket 10001 mit allen Worksheets
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000)
const twoDaysAgo = new Date(Date.now() - 48 * 60 * 60 * 1000)
const threeDaysAgo = new Date(Date.now() - 72 * 60 * 60 * 1000)
const DEMO_WORKSHEETS = [
{
$id: 'ws-10001-001',
wsid: '100001',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-max-id',
employeeName: 'Max Mustermann',
employeeShort: 'MM',
serviceType: 'Remote',
oldStatus: 'Open',
newStatus: 'Occupied',
oldResponseLevel: '',
newResponseLevel: '24/7',
totalTime: 30,
startDate: '23.12.2025',
startTime: '0800',
endDate: '23.12.2025',
endTime: '0830',
details: 'Erste Analyse durchgeführt. Server komplett offline. Keine Remote-Verbindung möglich. Vor-Ort-Einsatz erforderlich.',
isComment: false,
$createdAt: threeDaysAgo.toISOString()
},
{
$id: 'ws-10001-002',
wsid: '100002',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-lisa-id',
employeeName: 'Lisa Schneider',
employeeShort: 'LS',
serviceType: 'On Site',
oldStatus: 'Occupied',
newStatus: 'Assigned',
oldResponseLevel: '24/7',
newResponseLevel: '24/7',
totalTime: 120,
startDate: '23.12.2025',
startTime: '1000',
endDate: '23.12.2025',
endTime: '1200',
details: 'Vor-Ort-Einsatz: Hardware-Check durchgeführt. Netzteil des Hauptservers defekt. Ersatzteil bestellt. Notfall-Backup-Server gestartet.',
isComment: false,
$createdAt: threeDaysAgo.toISOString()
},
{
$id: 'ws-10001-003',
wsid: '100003',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-tom-id',
employeeName: 'Tom Klein',
employeeShort: 'TK',
serviceType: 'On Site',
oldStatus: 'Assigned',
newStatus: 'Assigned',
oldResponseLevel: '24/7',
newResponseLevel: '24/7',
totalTime: 0,
startDate: '24.12.2025',
startTime: '1400',
endDate: '24.12.2025',
endTime: '1400',
details: 'Warte auf Ersatzteil-Lieferung. Kunde informiert. Backup-System läuft stabil.',
isComment: true,
$createdAt: twoDaysAgo.toISOString()
},
{
$id: 'ws-10001-004',
wsid: '100004',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-max-id',
employeeName: 'Max Mustermann',
employeeShort: 'MM',
serviceType: 'On Site',
oldStatus: 'Assigned',
newStatus: 'In Test',
oldResponseLevel: '24/7',
newResponseLevel: '24/7',
totalTime: 180,
startDate: '25.12.2025',
startTime: '0900',
endDate: '25.12.2025',
endTime: '1200',
details: 'Ersatzteil eingebaut. Server gestartet. Alle Dienste wiederhergestellt. System-Tests durchgeführt. Datenbank-Verbindungen geprüft.',
isComment: false,
$createdAt: twoDaysAgo.toISOString()
},
{
$id: 'ws-10001-005',
wsid: '100005',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-lisa-id',
employeeName: 'Lisa Schneider',
employeeShort: 'LS',
serviceType: 'Remote',
oldStatus: 'In Test',
newStatus: 'Awaiting',
oldResponseLevel: '24/7',
newResponseLevel: 'Support',
totalTime: 45,
startDate: '26.12.2025',
startTime: '1000',
endDate: '26.12.2025',
endTime: '1045',
details: 'Remote-Monitoring eingerichtet. Warte auf Kunden-Feedback nach 24h Testphase. Alle Systeme laufen stabil.',
isComment: false,
$createdAt: yesterday.toISOString()
},
{
$id: 'ws-10001-006',
wsid: '100006',
woid: '10001',
workorderId: 'dummy-10001',
employeeId: 'user-tom-id',
employeeName: 'Tom Klein',
employeeShort: 'TK',
serviceType: 'COMMENT',
oldStatus: 'Awaiting',
newStatus: 'Closed',
oldResponseLevel: 'Support',
newResponseLevel: 'Backoffice',
totalTime: 0,
startDate: '30.12.2025',
startTime: '0900',
endDate: '30.12.2025',
endTime: '0900',
details: 'Kunde bestätigt: Alle Systeme funktionieren einwandfrei. Problem vollständig behoben. Ticket kann geschlossen werden.',
isComment: true,
$createdAt: new Date().toISOString()
}
]
export function useWorksheets(woid = null) {
const [worksheets, setWorksheets] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const fetchWorksheets = useCallback(async () => {
setLoading(true)
if (DEMO_MODE) {
// Filter demo data by WOID if provided
const filtered = woid
? DEMO_WORKSHEETS.filter(ws => ws.woid === woid)
: DEMO_WORKSHEETS
setWorksheets(filtered)
setLoading(false)
return
}
try {
const queries = [Query.orderDesc('$createdAt')]
// Filter by WOID if provided
if (woid) {
queries.push(Query.equal('woid', woid))
}
if (import.meta.env.DEV) {
console.log('📋 Fetching worksheets:')
console.log(' Database ID:', DATABASE_ID)
console.log(' Collection ID:', COLLECTIONS.WORKSHEETS)
console.log(' WOID Filter:', woid || 'none')
}
const response = await databases.listDocuments(
DATABASE_ID,
COLLECTIONS.WORKSHEETS,
queries
)
setWorksheets(response.documents)
setError(null)
} catch (err) {
let errorMessage = err.message || 'Fehler beim Laden der Worksheets'
if (err.code === 401 || errorMessage.includes('not authorized')) {
errorMessage = 'Berechtigung fehlt: Bitte überprüfe die Read-Berechtigungen der Worksheets Collection.'
} else if (errorMessage.includes('Collection') && errorMessage.includes('not found')) {
errorMessage = 'Worksheets Collection nicht gefunden. Bitte erstelle die Collection in Appwrite (siehe WORKSHEETS_COLLECTION_SETUP.md).'
}
setError(errorMessage)
console.error('Error fetching worksheets:', err)
} finally {
setLoading(false)
}
}, [woid])
useEffect(() => {
fetchWorksheets()
}, [fetchWorksheets])
/**
* Generiert eine eindeutige 6-stellige WSID
* Startet bei 100000 und zählt sequentiell hoch
*/
const generateWSID = useCallback(async () => {
if (DEMO_MODE) {
const maxWsid = worksheets.length > 0
? Math.max(...worksheets.map(ws => parseInt(ws.wsid)).filter(w => !isNaN(w)))
: 99999
return (maxWsid + 1).toString()
}
try {
// Hole ALLE Worksheets (nicht gefiltert) um höchste WSID zu finden
const response = await databases.listDocuments(
DATABASE_ID,
COLLECTIONS.WORKSHEETS,
[Query.orderDesc('wsid'), Query.limit(1)]
)
if (response.documents.length === 0) {
return '100000' // Erste WSID
}
const highestWsid = parseInt(response.documents[0].wsid)
if (isNaN(highestWsid)) {
console.warn('Ungültige WSID gefunden, starte bei 100000')
return '100000'
}
return (highestWsid + 1).toString()
} catch (err) {
console.error('Error generating WSID:', err)
// Fallback: Verwende lokale Worksheets
const maxWsid = worksheets.length > 0
? Math.max(...worksheets.map(ws => parseInt(ws.wsid)).filter(w => !isNaN(w)))
: 99999
return (maxWsid + 1).toString()
}
}, [worksheets])
/**
* Berechnet Arbeitszeit aus Start- und Endzeit
* Format: "1000" = 10:00, "1430" = 14:30
* @returns Minuten oder null wenn ungültig
*/
const calculateTime = (startTime, endTime) => {
if (!startTime || !endTime) return null
try {
const startHour = parseInt(startTime.substring(0, 2))
const startMin = parseInt(startTime.substring(2, 4))
const endHour = parseInt(endTime.substring(0, 2))
const endMin = parseInt(endTime.substring(2, 4))
if (isNaN(startHour) || isNaN(startMin) || isNaN(endHour) || isNaN(endMin)) {
return null
}
const startTotal = startHour * 60 + startMin
const endTotal = endHour * 60 + endMin
let diff = endTotal - startTotal
// Handle overnight (z.B. 23:00 - 01:00)
if (diff < 0) {
diff += 24 * 60
}
return diff
} catch (err) {
return null
}
}
/**
* Erstellt ein neues Worksheet
*/
const createWorksheet = async (data, currentUser) => {
if (DEMO_MODE) {
const wsid = await generateWSID()
const newWs = {
...data,
$id: Date.now().toString(),
wsid,
$createdAt: new Date().toISOString()
}
setWorksheets(prev => [newWs, ...prev])
return { success: true, data: newWs }
}
try {
// Validierung
if (!data.woid || data.woid.trim() === '') {
return { success: false, error: 'WOID ist erforderlich' }
}
if (!data.workorderId || data.workorderId.trim() === '') {
return { success: false, error: 'Work Order ID ist erforderlich' }
}
if (!data.details || data.details.trim() === '') {
return { success: false, error: 'Details sind erforderlich' }
}
// WSID generieren
const wsid = await generateWSID()
// Automatische Zeitberechnung (wenn nicht manuell angegeben)
let totalTime = data.totalTime || 0
if (!data.isComment && data.startTime && data.endTime && !data.totalTime) {
const calculatedTime = calculateTime(data.startTime, data.endTime)
if (calculatedTime !== null) {
totalTime = calculatedTime
}
}
// Worksheet-Daten vorbereiten
const worksheetData = {
wsid,
woid: data.woid.trim(),
workorderId: data.workorderId.trim(),
employeeId: currentUser.$id,
employeeName: currentUser.name || currentUser.email,
employeeShort: data.employeeShort || '',
serviceType: data.serviceType || 'Remote',
oldStatus: data.oldStatus || '',
newStatus: data.newStatus || data.oldStatus || '',
oldResponseLevel: data.oldResponseLevel || '',
newResponseLevel: data.newResponseLevel || data.oldResponseLevel || '',
totalTime: parseInt(totalTime) || 0,
startDate: data.startDate || '',
startTime: data.startTime || '',
endDate: data.endDate || data.startDate || '',
endTime: data.endTime || '',
details: data.details.trim(),
isComment: data.isComment || false,
createdAt: new Date().toISOString()
}
console.log('Creating worksheet with data:', worksheetData)
const response = await databases.createDocument(
DATABASE_ID,
COLLECTIONS.WORKSHEETS,
ID.unique(),
worksheetData
)
setWorksheets(prev => [response, ...prev])
return { success: true, data: response }
} catch (err) {
console.error('Error creating worksheet:', err)
return {
success: false,
error: err.message || 'Fehler beim Erstellen des Worksheets'
}
}
}
/**
* Aktualisiert ein Worksheet
*/
const updateWorksheet = async (id, data) => {
if (DEMO_MODE) {
setWorksheets(prev => prev.map(ws => ws.$id === id ? { ...ws, ...data } : ws))
return { success: true }
}
try {
const response = await databases.updateDocument(
DATABASE_ID,
COLLECTIONS.WORKSHEETS,
id,
data
)
setWorksheets(prev => prev.map(ws => ws.$id === id ? response : ws))
return { success: true, data: response }
} catch (err) {
console.error('Error updating worksheet:', err)
return { success: false, error: err.message }
}
}
/**
* Löscht ein Worksheet (sollte normalerweise nicht erlaubt sein - Audit Trail!)
*/
const deleteWorksheet = async (id) => {
if (DEMO_MODE) {
setWorksheets(prev => prev.filter(ws => ws.$id !== id))
return { success: true }
}
try {
await databases.deleteDocument(
DATABASE_ID,
COLLECTIONS.WORKSHEETS,
id
)
setWorksheets(prev => prev.filter(ws => ws.$id !== id))
return { success: true }
} catch (err) {
console.error('Error deleting worksheet:', err)
return { success: false, error: err.message }
}
}
/**
* Berechnet die Gesamtarbeitszeit für alle Worksheets
* @returns Minuten
*/
const getTotalTime = useCallback(() => {
return worksheets
.filter(ws => !ws.isComment)
.reduce((sum, ws) => sum + (ws.totalTime || 0), 0)
}, [worksheets])
/**
* Gruppiert Worksheets nach Mitarbeiter
* @returns Object mit employeeId als Key
*/
const getWorksheetsByEmployee = useCallback(() => {
return worksheets.reduce((acc, ws) => {
const empId = ws.employeeId
if (!acc[empId]) {
acc[empId] = {
employeeName: ws.employeeName,
employeeShort: ws.employeeShort,
worksheets: [],
totalTime: 0
}
}
acc[empId].worksheets.push(ws)
if (!ws.isComment) {
acc[empId].totalTime += ws.totalTime || 0
}
return acc
}, {})
}, [worksheets])
/**
* Gibt die Status-Historie zurück (chronologisch)
*/
const getStatusHistory = useCallback(() => {
return worksheets
.filter(ws => ws.oldStatus && ws.newStatus)
.map(ws => ({
wsid: ws.wsid,
date: ws.startDate,
time: ws.startTime,
employee: ws.employeeName,
from: ws.oldStatus,
to: ws.newStatus,
details: ws.details
}))
.reverse() // Älteste zuerst
}, [worksheets])
return {
worksheets,
loading,
error,
createWorksheet,
updateWorksheet,
deleteWorksheet,
refresh: fetchWorksheets,
getTotalTime,
getWorksheetsByEmployee,
getStatusHistory,
calculateTime
}
}