479 lines
14 KiB
JavaScript
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
|
|
}
|
|
}
|
|
|