From d86f9c07cd0ac6c43f510d2d6fbd34897ffd2181 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 19:30:29 +0000 Subject: [PATCH] =?UTF-8?q?Neues=20Ticket:=20Formular=20entschlackt,=20Kal?= =?UTF-8?q?ender=20und=20Autovervollst=C3=A4ndigung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Work Order Type nur noch Webpage und neu Migration (Kunde kommt von anderem Anbieter) - Affected System, Response Level und Benachrichtigungsfelder entfernt - Service Type: Fernwartung (remote) oder Vor Ort beim Kunden, Standard Fernwartung; gespeichert werden weiter "Remote" / "On Site" - Start und Ende mit Kalender und Uhrzeitfeld; gespeichert weiter als dd.mm.yyyy / hhmm - Requested by/for schlagen Kunden, Mitarbeiter und Namen aus den letzten Tickets vor, die des gewählten Kunden zuerst - Neue Tickets speichern Kundenname und Ort (die Karte zeigte sonst "Unbekannt") - Ticketfilter kennt Webpage und Migration Co-Authored-By: Claude Opus 5.5 --- src/components/CreateTicketModal.jsx | 287 ++++++++++-------------- src/components/ui/AutocompleteInput.jsx | 87 +++++++ src/components/ui/index.js | 1 + src/hooks/useWorkorders.js | 2 + src/lib/ticketForm.js | 94 ++++++++ src/pages/TicketsPage.jsx | 1 + src/styles/global.css | 47 ++++ 7 files changed, 356 insertions(+), 163 deletions(-) create mode 100644 src/components/ui/AutocompleteInput.jsx create mode 100644 src/lib/ticketForm.js diff --git a/src/components/CreateTicketModal.jsx b/src/components/CreateTicketModal.jsx index 11d3a31..573e268 100644 --- a/src/components/CreateTicketModal.jsx +++ b/src/components/CreateTicketModal.jsx @@ -1,30 +1,30 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { FaTimes } from 'react-icons/fa' import { useAdminConfig } from '../hooks/useAdminConfig' import { useEmployees } from '../hooks/useEmployees' +import { databases, DATABASE_ID, COLLECTIONS, Query, isDemoMode } from '../lib/appwrite' +import { + todayIso, + isoToGermanDate, + timeToWoms, + buildRequesterSuggestions, + rankSuggestions, +} from '../lib/ticketForm' +import { AutocompleteInput } from './ui' -// Fallback-Werte falls Config nicht geladen werden kann -const DEFAULT_TICKET_TYPES = [ - 'Akquise', 'Home Office', 'Holidays', 'Trip', 'Supportrequest', 'Change Request', - 'Maintenance', 'Project', 'Webpage', 'Controlling', 'Development', 'Documentation', - 'Meeting/Conference', 'IT Management', 'IT Security', 'Procurement', - 'Rollout', 'Emergency Call', 'Other Services' +// Neue Tickets gibt es aktuell nur fuer diese Arten; alte Tickets behalten ihre Art. +// Migration = ein Kunde, den wir von einem anderen Anbieter uebernehmen. +const TICKET_TYPES = [ + { value: 'Webpage', label: 'Webpage' }, + { value: 'Migration', label: 'Migration (Übernahme von anderem Anbieter)' }, ] -const DEFAULT_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' +// Gespeichert werden die bisherigen Werte, angezeigt wird Deutsch +const SERVICE_TYPES = [ + { value: 'Remote', label: 'Fernwartung (remote)' }, + { value: 'On Site', label: 'Vor Ort beim Kunden' }, ] -const DEFAULT_RESPONSE_LEVELS = [ - 'USER', 'KEY USER', 'Helpdesk', 'Support', 'Admin', 'FS/FE', '24/7', - 'TECH MGMT', 'Backoffice', 'BUSI MGMT', 'n/a' -] - -const DEFAULT_SERVICE_TYPES = ['Remote', 'On Site', 'Off Site'] - const DEFAULT_PRIORITIES = [ { value: 0, label: 'None' }, { value: 1, label: 'Low' }, @@ -33,56 +33,68 @@ const DEFAULT_PRIORITIES = [ { value: 4, label: 'Critical' } ] -const today = new Date().toLocaleDateString('de-DE') +// Datum als "yyyy-mm-dd", Uhrzeit als "hh:mm" (Werte der Kalender-Felder) +const emptyForm = () => ({ + customerId: '', + type: TICKET_TYPES[0].value, + serviceType: SERVICE_TYPES[0].value, + priority: 1, + topic: '', + requestedBy: '', + requestedFor: '', + startDate: todayIso(), + startTime: '', + deadline: todayIso(), + endTime: '', + estimate: '30', + details: '' +}) + +// Klick irgendwo ins Datumsfeld oeffnet den Kalender, nicht nur das kleine Symbol +const openCalendar = (e) => { + try { + e.currentTarget.showPicker?.() + } catch { + // aeltere Browser: Kalender bleibt ueber das Symbol erreichbar + } +} export default function CreateTicketModal({ isOpen, onClose, onCreate, customers = [] }) { const { config } = useAdminConfig() const { employees } = useEmployees() - - // Verwende Config-Werte oder Fallbacks - const TICKET_TYPES = config?.ticketTypes || DEFAULT_TICKET_TYPES - const SYSTEMS = config?.systems || DEFAULT_SYSTEMS - const RESPONSE_LEVELS = config?.responseLevels || DEFAULT_RESPONSE_LEVELS - const SERVICE_TYPES = config?.serviceTypes || DEFAULT_SERVICE_TYPES + const PRIORITIES = config?.priorities || DEFAULT_PRIORITIES - const [formData, setFormData] = useState({ - customerId: '', - type: '', - systemType: '', - responseLevel: '', - serviceType: '', - priority: 1, - topic: '', - requestedBy: '', - requestedFor: '', - startDate: today, - startTime: '', - deadline: today, - endTime: '', - estimate: '30', - mailCopyTo: '', - sendNotification: false, - details: '' - }) + const [formData, setFormData] = useState(emptyForm) + const [recentTickets, setRecentTickets] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState('') - // Setze Default-Werte wenn Config geladen ist oder Modal geöffnet wird + // Reset error when modal opens useEffect(() => { - if (isOpen && (TICKET_TYPES.length > 0 || SERVICE_TYPES.length > 0)) { - setFormData(prev => ({ - ...prev, - type: prev.type || TICKET_TYPES[0] || 'Supportrequest', - serviceType: prev.serviceType || SERVICE_TYPES[0] || 'Remote', - priority: prev.priority || (PRIORITIES.find(p => p.value === 1)?.value || 1) - })) + if (isOpen) setError('') + }, [isOpen]) + + // Namen aus den letzten Tickets fuer die Autovervollstaendigung + useEffect(() => { + if (!isOpen || isDemoMode) return + let cancelled = false + databases + .listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.orderDesc('$createdAt'), Query.limit(200)]) + .then((response) => { + if (!cancelled) setRecentTickets(response.documents || []) + }) + .catch((err) => console.warn('Vorschläge aus früheren Tickets nicht geladen:', err)) + return () => { + cancelled = true } - // Reset error when modal opens - if (isOpen) { - setError('') - } - }, [isOpen, TICKET_TYPES, SERVICE_TYPES, PRIORITIES]) + }, [isOpen]) + + const suggestions = useMemo( + () => buildRequesterSuggestions({ customers, employees, tickets: recentTickets }), + [customers, employees, recentTickets] + ) + const getSuggestions = (text) => rankSuggestions(suggestions, text, { customerId: formData.customerId }) const handleChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })) @@ -90,34 +102,35 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers if (error) setError('') } + // Das Ende darf nicht vor dem Start liegen, deshalb rueckt es mit + const handleStartDateChange = (value) => { + setFormData(prev => ({ + ...prev, + startDate: value, + deadline: value && prev.deadline && prev.deadline < value ? value : prev.deadline + })) + if (error) setError('') + } + const handleSubmit = async (e) => { e.preventDefault() setLoading(true) setError('') - + + const customer = customers.find(c => c.$id === formData.customerId) try { - const result = await onCreate(formData) + const result = await onCreate({ + ...formData, + customerName: customer?.name || '', + customerLocation: customer?.location || '', + startDate: isoToGermanDate(formData.startDate), + startTime: timeToWoms(formData.startTime), + deadline: isoToGermanDate(formData.deadline), + endTime: timeToWoms(formData.endTime) + }) if (result.success) { onClose() - setFormData({ - customerId: '', - type: TICKET_TYPES[0] || 'Supportrequest', - systemType: '', - responseLevel: '', - serviceType: SERVICE_TYPES[0] || 'Remote', - priority: PRIORITIES.find(p => p.value === 1)?.value || 1, - topic: '', - requestedBy: '', - requestedFor: '', - startDate: today, - startTime: '', - deadline: today, - endTime: '', - estimate: '30', - mailCopyTo: '', - sendNotification: false, - details: '' - }) + setFormData(emptyForm()) } else { setError(result.error || 'Fehler beim Erstellen des Tickets') } @@ -138,13 +151,13 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers

Create New Ticket

- + {error && (
{error}
)} - +
@@ -171,36 +184,7 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers onChange={(e) => handleChange('type', e.target.value)} > {TICKET_TYPES.map(type => ( - - ))} - -
- -
- - -
- -
- -
@@ -213,7 +197,7 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers onChange={(e) => handleChange('serviceType', e.target.value)} > {SERVICE_TYPES.map(type => ( - + ))}
@@ -257,68 +241,67 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
- - Requested by + handleChange('requestedBy', e.target.value)} + onChange={(value) => handleChange('requestedBy', value)} + getSuggestions={getSuggestions} required />
- - Requested for + handleChange('requestedFor', e.target.value)} + onChange={(value) => handleChange('requestedFor', value)} + getSuggestions={getSuggestions} />
- -
+ +
handleChange('startDate', e.target.value)} - style={{ flex: 1 }} + onChange={(e) => handleStartDateChange(e.target.value)} + onClick={openCalendar} /> handleChange('startTime', e.target.value)} - style={{ flex: 1 }} />
- -
+ +
handleChange('deadline', e.target.value)} - style={{ flex: 1 }} + onClick={openCalendar} /> handleChange('endTime', e.target.value)} - style={{ flex: 1 }} />
@@ -333,28 +316,6 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers onChange={(e) => handleChange('estimate', e.target.value)} />
- -
- - handleChange('mailCopyTo', e.target.value)} - /> -
- -
- -
@@ -370,8 +331,8 @@ export default function CreateTicketModal({ isOpen, onClose, onCreate, customers
-