woms 3.0
This commit is contained in:
152
src/hooks/useAdminConfig.js
Normal file
152
src/hooks/useAdminConfig.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { databases, DATABASE_ID, COLLECTIONS, ID } from '../lib/appwrite'
|
||||
|
||||
const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID
|
||||
|
||||
// Default-Werte für Demo-Modus
|
||||
const DEFAULT_CONFIG = {
|
||||
ticketTypes: [
|
||||
'Home Office', 'Holidays', 'Trip', 'Supportrequest', 'Change Request',
|
||||
'Maintenance', 'Project', 'Controlling', 'Development', 'Documentation',
|
||||
'Meeting/Conference', 'IT Management', 'IT Security', 'Procurement',
|
||||
'Rollout', 'Emergency Call', 'Other Services'
|
||||
],
|
||||
systems: [
|
||||
'Account View', 'Client', 'Cofano', 'Credentials', 'Diamant', 'Docuware',
|
||||
'EDI', 'eMail', 'Employee', 'Invoice', 'LBase', 'Medical Office', 'Network',
|
||||
'O365', 'PDF Viewer', 'Printer', 'Reports', 'Server', 'Time Tracking',
|
||||
'TK', 'TOS', 'Vivendi NG', 'VGM', '(W)LAN', '(W)WAN', 'WOMS', 'n/a'
|
||||
],
|
||||
responseLevels: [
|
||||
'USER', 'KEY USER', 'Helpdesk', 'Support', 'Admin', 'FS/FE', '24/7',
|
||||
'TECH MGMT', 'Backoffice', 'BUSI MGMT', 'n/a'
|
||||
],
|
||||
serviceTypes: ['Remote', 'On Site', 'Off Site'],
|
||||
priorities: [
|
||||
{ value: 0, label: 'None' },
|
||||
{ value: 1, label: 'Low' },
|
||||
{ value: 2, label: 'Medium' },
|
||||
{ value: 3, label: 'High' },
|
||||
{ value: 4, label: 'Critical' }
|
||||
]
|
||||
}
|
||||
|
||||
export function useAdminConfig() {
|
||||
const [config, setConfig] = useState(DEFAULT_CONFIG)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const fetchConfig = useCallback(async () => {
|
||||
if (DEMO_MODE) {
|
||||
setConfig(DEFAULT_CONFIG)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Versuche Config-Dokument zu laden (ID: 'config')
|
||||
try {
|
||||
const doc = await databases.getDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CONFIG || 'config',
|
||||
'config'
|
||||
)
|
||||
setConfig({
|
||||
ticketTypes: doc.ticketTypes || DEFAULT_CONFIG.ticketTypes,
|
||||
systems: doc.systems || DEFAULT_CONFIG.systems,
|
||||
responseLevels: doc.responseLevels || DEFAULT_CONFIG.responseLevels,
|
||||
serviceTypes: doc.serviceTypes || DEFAULT_CONFIG.serviceTypes,
|
||||
priorities: doc.priorities || DEFAULT_CONFIG.priorities
|
||||
})
|
||||
} catch (e) {
|
||||
// Config existiert noch nicht (404) - das ist normal, verwende Defaults
|
||||
if (e.code === 404 || e.message?.includes('not found')) {
|
||||
setConfig(DEFAULT_CONFIG)
|
||||
setError(null) // Kein Fehler, Collection existiert einfach noch nicht
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
console.error('Error fetching config:', err)
|
||||
// Nur echte Fehler als Error setzen, nicht 404
|
||||
if (err.code !== 404 && !err.message?.includes('not found')) {
|
||||
setError(err.message)
|
||||
} else {
|
||||
setError(null)
|
||||
}
|
||||
setConfig(DEFAULT_CONFIG) // Fallback zu Defaults
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchConfig()
|
||||
}, [fetchConfig])
|
||||
|
||||
const updateConfig = async (newConfig) => {
|
||||
if (DEMO_MODE) {
|
||||
setConfig(newConfig)
|
||||
localStorage.setItem('admin_config', JSON.stringify(newConfig))
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
const configData = {
|
||||
ticketTypes: newConfig.ticketTypes,
|
||||
systems: newConfig.systems,
|
||||
responseLevels: newConfig.responseLevels,
|
||||
serviceTypes: newConfig.serviceTypes,
|
||||
priorities: newConfig.priorities
|
||||
}
|
||||
|
||||
try {
|
||||
// Versuche zu aktualisieren
|
||||
await databases.updateDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CONFIG || 'config',
|
||||
'config',
|
||||
configData
|
||||
)
|
||||
} catch (e) {
|
||||
// Dokument existiert nicht (404) oder Collection existiert nicht
|
||||
if (e.code === 404 || e.message?.includes('not found')) {
|
||||
// Versuche zu erstellen
|
||||
try {
|
||||
await databases.createDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CONFIG || 'config',
|
||||
'config',
|
||||
configData
|
||||
)
|
||||
} catch (createErr) {
|
||||
// Collection existiert nicht - zeige hilfreiche Fehlermeldung
|
||||
if (createErr.code === 404 || createErr.message?.includes('Collection')) {
|
||||
throw new Error('Die "config" Collection existiert noch nicht. Bitte erstelle sie zuerst in Appwrite.')
|
||||
}
|
||||
throw createErr
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
setConfig(newConfig)
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
console.error('Error updating config:', err)
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
config,
|
||||
loading,
|
||||
error,
|
||||
updateConfig,
|
||||
refresh: fetchConfig
|
||||
}
|
||||
}
|
||||
|
||||
121
src/hooks/useCustomers.js
Normal file
121
src/hooks/useCustomers.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { databases, DATABASE_ID, COLLECTIONS, ID, Query } from '../lib/appwrite'
|
||||
|
||||
const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID
|
||||
|
||||
// Demo-Kunden für Testing
|
||||
const DEMO_CUSTOMERS = [
|
||||
{ $id: '1', code: 'C001', name: 'Kunde A', location: 'Berlin', email: 'kunde.a@example.com', phone: '030-123456' },
|
||||
{ $id: '2', code: 'C002', name: 'Kunde B', location: 'München', email: 'kunde.b@example.com', phone: '089-654321' }
|
||||
]
|
||||
|
||||
export function useCustomers() {
|
||||
const [customers, setCustomers] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const fetchCustomers = useCallback(async () => {
|
||||
if (DEMO_MODE) {
|
||||
setCustomers(DEMO_CUSTOMERS)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CUSTOMERS,
|
||||
[Query.orderAsc('name')]
|
||||
)
|
||||
setCustomers(response.documents)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
console.error('Error fetching customers:', err)
|
||||
// Wenn Collection nicht existiert, setze leeres Array (kein Fehler)
|
||||
if (err.code === 404 || err.message?.includes('not found')) {
|
||||
setCustomers([])
|
||||
setError(null) // Kein Fehler, Collection existiert einfach noch nicht
|
||||
} else {
|
||||
setError(err.message)
|
||||
setCustomers([])
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchCustomers()
|
||||
}, [fetchCustomers])
|
||||
|
||||
const createCustomer = async (data) => {
|
||||
if (DEMO_MODE) {
|
||||
const newCustomer = { ...data, $id: Date.now().toString() }
|
||||
setCustomers(prev => [...prev, newCustomer])
|
||||
return { success: true, data: newCustomer }
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.createDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CUSTOMERS,
|
||||
ID.unique(),
|
||||
data
|
||||
)
|
||||
setCustomers(prev => [...prev, response])
|
||||
return { success: true, data: response }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const updateCustomer = async (id, data) => {
|
||||
if (DEMO_MODE) {
|
||||
setCustomers(prev => prev.map(c => c.$id === id ? { ...c, ...data } : c))
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.updateDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CUSTOMERS,
|
||||
id,
|
||||
data
|
||||
)
|
||||
setCustomers(prev => prev.map(c => c.$id === id ? response : c))
|
||||
return { success: true, data: response }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const deleteCustomer = async (id) => {
|
||||
if (DEMO_MODE) {
|
||||
setCustomers(prev => prev.filter(c => c.$id !== id))
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
await databases.deleteDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.CUSTOMERS,
|
||||
id
|
||||
)
|
||||
setCustomers(prev => prev.filter(c => c.$id !== id))
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
customers,
|
||||
loading,
|
||||
error,
|
||||
refresh: fetchCustomers,
|
||||
createCustomer,
|
||||
updateCustomer,
|
||||
deleteCustomer
|
||||
}
|
||||
}
|
||||
|
||||
207
src/hooks/useEmployees.js
Normal file
207
src/hooks/useEmployees.js
Normal file
@@ -0,0 +1,207 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { databases, account, DATABASE_ID, COLLECTIONS, ID, Query } from '../lib/appwrite'
|
||||
|
||||
const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID
|
||||
|
||||
// Demo-Mitarbeiter für Testing
|
||||
const DEMO_EMPLOYEES = [
|
||||
{ $id: '1', userId: 'user1', displayName: 'Kenso Grimm', email: 'kenso@example.com', shortcode: 'KNSO' },
|
||||
{ $id: '2', userId: 'user2', displayName: 'Christian Lehmann', email: 'christian@example.com', shortcode: 'CHLE' }
|
||||
]
|
||||
|
||||
export function useEmployees() {
|
||||
const [employees, setEmployees] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [syncing, setSyncing] = useState(false)
|
||||
|
||||
const fetchEmployees = useCallback(async () => {
|
||||
setLoading(true)
|
||||
|
||||
if (DEMO_MODE) {
|
||||
setEmployees(DEMO_EMPLOYEES)
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.listDocuments(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.EMPLOYEES,
|
||||
[Query.orderAsc('displayName')]
|
||||
)
|
||||
setEmployees(response.documents)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
console.error('Error fetching employees:', err)
|
||||
// Wenn Collection nicht existiert, setze leeres Array (kein Fehler)
|
||||
if (err.code === 404 || err.message?.includes('not found')) {
|
||||
setEmployees([])
|
||||
setError(null) // Kein Fehler, Collection existiert einfach noch nicht
|
||||
} else {
|
||||
setError(err.message)
|
||||
setEmployees([])
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchEmployees()
|
||||
}, [fetchEmployees])
|
||||
|
||||
const createEmployee = async (data) => {
|
||||
if (DEMO_MODE) {
|
||||
const newEmployee = { ...data, $id: Date.now().toString() }
|
||||
setEmployees(prev => [...prev, newEmployee])
|
||||
return { success: true, data: newEmployee }
|
||||
}
|
||||
|
||||
try {
|
||||
// Validierung
|
||||
if (!data.userId || !data.displayName) {
|
||||
return { success: false, error: 'userId und displayName sind erforderlich' }
|
||||
}
|
||||
|
||||
const response = await databases.createDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.EMPLOYEES,
|
||||
ID.unique(),
|
||||
{
|
||||
userId: data.userId,
|
||||
displayName: data.displayName,
|
||||
email: data.email || '',
|
||||
shortcode: data.shortcode || ''
|
||||
}
|
||||
)
|
||||
setEmployees(prev => [...prev, response])
|
||||
return { success: true, data: response }
|
||||
} catch (err) {
|
||||
console.error('Error creating employee:', err)
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const updateEmployee = async (id, data) => {
|
||||
if (DEMO_MODE) {
|
||||
setEmployees(prev => prev.map(e => e.$id === id ? { ...e, ...data } : e))
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await databases.updateDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.EMPLOYEES,
|
||||
id,
|
||||
data
|
||||
)
|
||||
setEmployees(prev => prev.map(e => e.$id === id ? response : e))
|
||||
return { success: true, data: response }
|
||||
} catch (err) {
|
||||
console.error('Error updating employee:', err)
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
const deleteEmployee = async (id) => {
|
||||
if (DEMO_MODE) {
|
||||
setEmployees(prev => prev.filter(e => e.$id !== id))
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
try {
|
||||
await databases.deleteDocument(
|
||||
DATABASE_ID,
|
||||
COLLECTIONS.EMPLOYEES,
|
||||
id
|
||||
)
|
||||
setEmployees(prev => prev.filter(e => e.$id !== id))
|
||||
return { success: true }
|
||||
} catch (err) {
|
||||
console.error('Error deleting employee:', err)
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronisiert Appwrite Auth Users mit der employees Collection
|
||||
* Erstellt fehlende Einträge für neue Users
|
||||
*/
|
||||
const syncWithAuthUsers = async () => {
|
||||
if (DEMO_MODE) {
|
||||
return { success: true, message: 'Demo-Modus: Keine Synchronisierung nötig' }
|
||||
}
|
||||
|
||||
setSyncing(true)
|
||||
|
||||
try {
|
||||
// 1. Lade alle Appwrite Auth Users
|
||||
// Hinweis: In Appwrite 1.5.7 gibt es möglicherweise keine direkte List-Users API
|
||||
// für normale User. Diese Funktion benötigt Server-Side Code oder Admin-API-Key.
|
||||
// Für jetzt implementieren wir einen Workaround: Wir bieten ein manuelles Add-Interface.
|
||||
|
||||
// Alternative: Wenn der User Appwrite Admin ist, können wir versuchen:
|
||||
// const users = await account.listUsers() // Funktioniert nur mit Admin-Rechten
|
||||
|
||||
// Da das nicht direkt möglich ist, geben wir eine Info zurück
|
||||
return {
|
||||
success: false,
|
||||
error: 'Automatische Synchronisierung erfordert Admin-API-Zugriff. Bitte füge Mitarbeiter manuell hinzu oder verwende die Appwrite Server API.'
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error syncing auth users:', err)
|
||||
return { success: false, error: err.message }
|
||||
} finally {
|
||||
setSyncing(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt einen Employee-Eintrag für den aktuell eingeloggten User
|
||||
* Nützlich für Self-Service
|
||||
*/
|
||||
const createSelfEmployee = async (shortcode = '') => {
|
||||
if (DEMO_MODE) {
|
||||
return { success: true, message: 'Demo-Modus' }
|
||||
}
|
||||
|
||||
try {
|
||||
// Hole aktuellen User
|
||||
const currentUser = await account.get()
|
||||
|
||||
// Prüfe, ob Employee bereits existiert
|
||||
const existing = employees.find(e => e.userId === currentUser.$id)
|
||||
if (existing) {
|
||||
return { success: false, error: 'Mitarbeiter-Eintrag existiert bereits' }
|
||||
}
|
||||
|
||||
// Erstelle Employee-Eintrag
|
||||
const result = await createEmployee({
|
||||
userId: currentUser.$id,
|
||||
displayName: currentUser.name || currentUser.email,
|
||||
email: currentUser.email,
|
||||
shortcode: shortcode
|
||||
})
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Error creating self employee:', err)
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
employees,
|
||||
loading,
|
||||
error,
|
||||
syncing,
|
||||
refresh: fetchEmployees,
|
||||
createEmployee,
|
||||
updateEmployee,
|
||||
deleteEmployee,
|
||||
syncWithAuthUsers,
|
||||
createSelfEmployee
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ const DEMO_MODE = !import.meta.env.VITE_APPWRITE_PROJECT_ID
|
||||
|
||||
// Demo data for testing without Appwrite
|
||||
const DEMO_WORKORDERS = [
|
||||
{ $id: '1', title: 'Server Wartung', description: 'Monatliche Wartung', status: 'Open', priority: 2, type: 'Maintenance', customerName: 'Kunde A', assignedName: 'Max M.', response: 'Onsite', $createdAt: new Date().toISOString() },
|
||||
{ $id: '2', 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', 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', title: 'Drucker defekt', description: 'Papierstau', status: 'Awaiting', priority: 2, type: 'Hardware', customerName: 'Kunde D', assignedName: '', response: 'Pickup', $createdAt: new Date().toISOString() },
|
||||
{ $id: '5', title: 'Kritischer Serverausfall', description: 'Produktionsserver down', status: 'Open', priority: 4, type: 'Emergency', customerName: 'Kunde E', assignedName: 'Max M.', response: 'Onsite', $createdAt: new Date().toISOString() },
|
||||
{ $id: '1', woid: '10001', title: 'Server Wartung', description: 'Monatliche Wartung', status: 'Open', priority: 2, type: 'Maintenance', customerName: 'Kunde A', assignedName: 'Max M.', response: 'Onsite', $createdAt: new Date().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 = {}) {
|
||||
@@ -44,16 +44,27 @@ export function useWorkorders(filters = {}) {
|
||||
queries.push(Query.limit(filters.limit))
|
||||
}
|
||||
|
||||
// Für Arrays: In Appwrite 1.5.7 gibt es kein Query.or()
|
||||
// Wir filtern clientseitig für mehrere Werte
|
||||
if (filters.status && filters.status.length > 0) {
|
||||
queries.push(Query.equal('status', filters.status))
|
||||
if (filters.status.length === 1) {
|
||||
queries.push(Query.equal('status', filters.status[0]))
|
||||
}
|
||||
// Für mehrere Werte: Clientseitig filtern (siehe unten)
|
||||
}
|
||||
|
||||
if (filters.type && filters.type.length > 0) {
|
||||
queries.push(Query.equal('type', filters.type))
|
||||
if (filters.type.length === 1) {
|
||||
queries.push(Query.equal('type', filters.type[0]))
|
||||
}
|
||||
// Für mehrere Werte: Clientseitig filtern
|
||||
}
|
||||
|
||||
if (filters.priority && filters.priority.length > 0) {
|
||||
queries.push(Query.equal('priority', filters.priority))
|
||||
if (filters.priority.length === 1) {
|
||||
queries.push(Query.equal('priority', filters.priority[0]))
|
||||
}
|
||||
// Für mehrere Werte: Clientseitig filtern
|
||||
}
|
||||
|
||||
if (filters.customerId) {
|
||||
@@ -64,16 +75,48 @@ export function useWorkorders(filters = {}) {
|
||||
queries.push(Query.equal('assignedTo', filters.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)
|
||||
// Clientseitige Filterung für Arrays (da Query.or() nicht verfügbar ist)
|
||||
let filteredDocs = response.documents
|
||||
|
||||
if (filters.status && filters.status.length > 1) {
|
||||
filteredDocs = filteredDocs.filter(doc => filters.status.includes(doc.status))
|
||||
}
|
||||
|
||||
if (filters.type && filters.type.length > 1) {
|
||||
filteredDocs = filteredDocs.filter(doc => filters.type.includes(doc.type))
|
||||
}
|
||||
|
||||
if (filters.priority && filters.priority.length > 1) {
|
||||
filteredDocs = filteredDocs.filter(doc => filters.priority.includes(doc.priority))
|
||||
}
|
||||
|
||||
setWorkorders(filteredDocs)
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
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)
|
||||
@@ -86,26 +129,107 @@ export function useWorkorders(filters = {}) {
|
||||
|
||||
const createWorkorder = async (data) => {
|
||||
if (DEMO_MODE) {
|
||||
const newWo = { ...data, $id: Date.now().toString(), status: 'Open', $createdAt: new Date().toISOString() }
|
||||
// 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.' }
|
||||
}
|
||||
|
||||
// Status-Automatik: Wenn Mitarbeiter zugewiesen → Status = "Assigned", sonst "Open"
|
||||
const autoStatus = (data.assignedTo && data.assignedTo !== '') ? 'Assigned' : 'Open'
|
||||
|
||||
// Generiere sequentielle 5-stellige WOID (wie im Original-System)
|
||||
const generateWOID = () => {
|
||||
// Finde die höchste bestehende WOID
|
||||
if (workorders.length === 0) {
|
||||
return '10000'; // Starte bei 10000 wenn keine Tickets existieren
|
||||
}
|
||||
|
||||
const maxWoid = Math.max(
|
||||
...workorders
|
||||
.map(wo => parseInt(wo.woid))
|
||||
.filter(woid => !isNaN(woid) && woid > 0)
|
||||
);
|
||||
|
||||
// Wenn keine gültige WOID gefunden wurde, starte bei 10000
|
||||
if (maxWoid === -Infinity || isNaN(maxWoid)) {
|
||||
return '10000';
|
||||
}
|
||||
|
||||
// Gib die nächste Nummer zurück (sequentiell)
|
||||
return (maxWoid + 1).toString();
|
||||
}
|
||||
|
||||
// Bereite Daten für Appwrite vor
|
||||
const workorderData = {
|
||||
// Required fields
|
||||
topic: data.topic.trim(),
|
||||
status: data.status || autoStatus, // Verwende übergebenen Status oder automatischen Status
|
||||
priority: typeof data.priority === 'number' ? data.priority : parseInt(data.priority) || 1,
|
||||
woid: generateWOID(), // 5-stellige Zahl
|
||||
|
||||
// Optional fields - nur senden wenn vorhanden
|
||||
type: data.type || '',
|
||||
systemType: data.systemType || '',
|
||||
responseLevel: data.responseLevel || '',
|
||||
serviceType: data.serviceType || 'Remote',
|
||||
customerId: data.customerId || '',
|
||||
assignedTo: data.assignedTo || '', // Zugewiesener Mitarbeiter
|
||||
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(),
|
||||
{
|
||||
...data,
|
||||
status: 'Open',
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
workorderData
|
||||
)
|
||||
setWorkorders(prev => [response, ...prev])
|
||||
return { success: true, data: response }
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message }
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +240,27 @@ export function useWorkorders(filters = {}) {
|
||||
}
|
||||
|
||||
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,
|
||||
data
|
||||
updateData
|
||||
)
|
||||
setWorkorders(prev =>
|
||||
prev.map(wo => wo.$id === id ? response : wo)
|
||||
|
||||
362
src/hooks/useWorksheets.js
Normal file
362
src/hooks/useWorksheets.js
Normal file
@@ -0,0 +1,362 @@
|
||||
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
|
||||
const DEMO_WORKSHEETS = [
|
||||
{
|
||||
$id: '1',
|
||||
wsid: '100001',
|
||||
woid: '10001',
|
||||
workorderId: '1',
|
||||
employeeId: 'emp1',
|
||||
employeeName: 'Max Müller',
|
||||
employeeShort: 'MAMU',
|
||||
serviceType: 'Remote',
|
||||
oldStatus: 'Open',
|
||||
newStatus: 'Occupied',
|
||||
totalTime: 30,
|
||||
startDate: '29.12.2025',
|
||||
startTime: '1000',
|
||||
endDate: '29.12.2025',
|
||||
endTime: '1030',
|
||||
details: 'Router neu gestartet',
|
||||
isComment: false,
|
||||
$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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user