Arbeitsblatt-Formular erneuert: Kalender, deutsche Service-Art
- Start und Ende mit Kalender und Uhrzeitfeld (gemeinsame Komponente DateTimeInput, auch im Ticketformular); gespeichert weiter als dd.mm.yyyy / hhmm, das Ende rückt mit, wenn der Start später liegt - Service Type nur noch "Fernwartung (remote)" oder "Vor Ort beim Kunden", vorbelegt vom Ticket; COMMENT entfällt (dafür gibt es "Nur Kommentar") - Minuten werden aus den Uhrzeiten berechnet (über Mitternacht weiter) - Minutenfeld nimmt ganze Minuten: mit step 15 ließ der Browser berechnete Zeiten wie 70 Minuten nicht absenden - Arbeitsblatt-Liste und -Statistik zeigen die Service-Art auf Deutsch (Remote und altes Off Site zusammen als Fernwartung) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8cb0bb8146
commit
e678899141
7 files changed
+159
-173
No files matched your search
@@ -1,8 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { FaTimes } from 'react-icons/fa'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
|
||||
const SERVICE_TYPES = ['Remote', 'On Site', 'Off Site', 'COMMENT']
|
||||
import { SERVICE_TYPES, todayIso, isoToGermanDate, timeToWoms, minutesBetween } from '../lib/ticketForm'
|
||||
import { DateTimeInput } from './ui'
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
'Open',
|
||||
@@ -20,30 +20,25 @@ const STATUS_OPTIONS = [
|
||||
// Akquise-Tickets: Ergebnis des Kundenkontakts (statt technischer Status)
|
||||
const ACQUISITION_STATUS = ['In Kontakt', 'Zugesagt', 'Abgesagt']
|
||||
|
||||
// Datum als "yyyy-mm-dd", Uhrzeit als "hh:mm" (Werte der Kalender-Felder).
|
||||
// Die Service-Art kommt vom Ticket; das alte "Off Site" zaehlt als Fernwartung.
|
||||
const initialForm = (workorder, newStatus) => ({
|
||||
serviceType: SERVICE_TYPES.some(t => t.value === workorder?.serviceType)
|
||||
? workorder.serviceType
|
||||
: SERVICE_TYPES[0].value,
|
||||
newStatus,
|
||||
totalTime: 0,
|
||||
startDate: todayIso(),
|
||||
startTime: '',
|
||||
endDate: todayIso(),
|
||||
endTime: '',
|
||||
details: '',
|
||||
isComment: false,
|
||||
countsToPlan: false
|
||||
})
|
||||
|
||||
export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCreate }) {
|
||||
const { user } = useAuth()
|
||||
const today = new Date().toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
})
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
serviceType: 'Remote',
|
||||
newStatus: workorder?.status || 'Open',
|
||||
totalTime: 0,
|
||||
startDate: today,
|
||||
startTime: '',
|
||||
endDate: today,
|
||||
endTime: '',
|
||||
details: '',
|
||||
isComment: false,
|
||||
countsToPlan: false
|
||||
})
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [autoCalculate, setAutoCalculate] = useState(true)
|
||||
|
||||
const isAcquisition = workorder?.type === 'Akquise'
|
||||
const statusOptions = isAcquisition ? ACQUISITION_STATUS : STATUS_OPTIONS
|
||||
@@ -51,66 +46,53 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
? (ACQUISITION_STATUS.includes(workorder?.status) ? workorder.status : 'In Kontakt')
|
||||
: (workorder?.status || 'Open')
|
||||
|
||||
const [formData, setFormData] = useState(() => initialForm(workorder, defaultStatus))
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [autoCalculate, setAutoCalculate] = useState(true)
|
||||
|
||||
// Reset form wenn Modal geöffnet wird
|
||||
useEffect(() => {
|
||||
if (isOpen && workorder) {
|
||||
setFormData({
|
||||
serviceType: 'Remote',
|
||||
newStatus: defaultStatus,
|
||||
totalTime: 0,
|
||||
startDate: today,
|
||||
startTime: '',
|
||||
endDate: today,
|
||||
endTime: '',
|
||||
details: '',
|
||||
isComment: false,
|
||||
countsToPlan: false
|
||||
})
|
||||
setFormData(initialForm(workorder, defaultStatus))
|
||||
setError('')
|
||||
setAutoCalculate(true)
|
||||
}
|
||||
}, [isOpen, workorder, today])
|
||||
}, [isOpen, workorder, defaultStatus])
|
||||
|
||||
// Automatische Zeitberechnung
|
||||
useEffect(() => {
|
||||
if (autoCalculate && formData.startTime && formData.endTime && !formData.isComment) {
|
||||
try {
|
||||
const startHour = parseInt(formData.startTime.substring(0, 2))
|
||||
const startMin = parseInt(formData.startTime.substring(2, 4))
|
||||
const endHour = parseInt(formData.endTime.substring(0, 2))
|
||||
const endMin = parseInt(formData.endTime.substring(2, 4))
|
||||
|
||||
if (!isNaN(startHour) && !isNaN(startMin) && !isNaN(endHour) && !isNaN(endMin)) {
|
||||
const startTotal = startHour * 60 + startMin
|
||||
const endTotal = endHour * 60 + endMin
|
||||
|
||||
let diff = endTotal - startTotal
|
||||
if (diff < 0) {
|
||||
diff += 24 * 60 // Overnight
|
||||
}
|
||||
|
||||
setFormData(prev => ({ ...prev, totalTime: diff }))
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignoriere Fehler
|
||||
}
|
||||
if (!autoCalculate || formData.isComment) return
|
||||
const minutes = minutesBetween(formData.startTime, formData.endTime)
|
||||
if (minutes !== null) {
|
||||
setFormData(prev => ({ ...prev, totalTime: minutes }))
|
||||
}
|
||||
}, [formData.startTime, formData.endTime, formData.isComment, autoCalculate])
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
setFormData(prev => ({ ...prev, [field]: value }))
|
||||
|
||||
|
||||
// Wenn totalTime manuell geändert wird, deaktiviere Auto-Berechnung
|
||||
if (field === 'totalTime') {
|
||||
setAutoCalculate(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Das Ende darf nicht vor dem Start liegen, deshalb rueckt es mit
|
||||
const handleStartDateChange = (value) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
startDate: value,
|
||||
endDate: value && prev.endDate && prev.endDate < value ? value : prev.endDate
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
|
||||
try {
|
||||
if (!formData.details.trim()) {
|
||||
setError('Bitte Details eingeben')
|
||||
@@ -125,10 +107,10 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
oldStatus: workorder.status,
|
||||
newStatus: formData.newStatus,
|
||||
totalTime: formData.isComment ? 0 : parseInt(formData.totalTime) || 0,
|
||||
startDate: formData.startDate,
|
||||
startTime: formData.startTime,
|
||||
endDate: formData.endDate,
|
||||
endTime: formData.endTime,
|
||||
startDate: isoToGermanDate(formData.startDate),
|
||||
startTime: timeToWoms(formData.startTime),
|
||||
endDate: isoToGermanDate(formData.endDate),
|
||||
endTime: timeToWoms(formData.endTime),
|
||||
details: formData.details,
|
||||
isComment: formData.isComment,
|
||||
countsToPlan: !formData.isComment && formData.countsToPlan,
|
||||
@@ -136,7 +118,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
}
|
||||
|
||||
const result = await onCreate(worksheetData, user)
|
||||
|
||||
|
||||
if (result.success) {
|
||||
onClose()
|
||||
} else {
|
||||
@@ -159,13 +141,13 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
</span>
|
||||
<div className="overlay-content">
|
||||
<h2 className="mb-2">Create New Worksheet - WOID {workorder.woid}</h2>
|
||||
|
||||
|
||||
{error && (
|
||||
<div className="bg-red text-white p-2 mb-2" style={{ borderRadius: '4px' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col col-6">
|
||||
@@ -178,7 +160,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
required
|
||||
>
|
||||
{SERVICE_TYPES.map(type => (
|
||||
<option key={type} value={type}>{type}</option>
|
||||
<option key={type.value} value={type.value}>{type.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -227,72 +209,52 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
</div>
|
||||
|
||||
<div className="col col-6">
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="worksheet-start-date">Start Date & Time</label>
|
||||
<DateTimeInput
|
||||
id="worksheet-start-date"
|
||||
timeLabel="Start time"
|
||||
required
|
||||
date={formData.startDate}
|
||||
time={formData.startTime}
|
||||
onDateChange={handleStartDateChange}
|
||||
onTimeChange={(value) => handleChange('startTime', value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="worksheet-end-date">End Date & Time</label>
|
||||
<DateTimeInput
|
||||
id="worksheet-end-date"
|
||||
timeLabel="End time"
|
||||
required
|
||||
min={formData.startDate}
|
||||
date={formData.endDate}
|
||||
time={formData.endTime}
|
||||
onDateChange={(value) => handleChange('endDate', value)}
|
||||
onTimeChange={(value) => handleChange('endTime', value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">Total Time (Minuten)</label>
|
||||
{/* Ganze Minuten: mit step 15 blockierte der Browser berechnete Zeiten wie 70 */}
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
min="0"
|
||||
step="15"
|
||||
step="1"
|
||||
value={formData.totalTime}
|
||||
onChange={(e) => handleChange('totalTime', e.target.value)}
|
||||
disabled={formData.isComment}
|
||||
placeholder="0"
|
||||
/>
|
||||
<small style={{ color: '#a0aec0', fontSize: '12px' }}>
|
||||
{autoCalculate && formData.startTime && formData.endTime
|
||||
? '✓ Automatisch berechnet'
|
||||
{autoCalculate && formData.startTime && formData.endTime
|
||||
? '✓ Automatisch berechnet'
|
||||
: 'Manuell eingeben'}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">Start Date</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="dd.mm.yyyy"
|
||||
value={formData.startDate}
|
||||
onChange={(e) => handleChange('startDate', e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">End Date</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="dd.mm.yyyy"
|
||||
value={formData.endDate}
|
||||
onChange={(e) => handleChange('endDate', e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">Start Time</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="hhmm"
|
||||
value={formData.startTime}
|
||||
onChange={(e) => handleChange('startTime', e.target.value)}
|
||||
maxLength="4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">End Time</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="hhmm"
|
||||
value={formData.endTime}
|
||||
onChange={(e) => handleChange('endTime', e.target.value)}
|
||||
maxLength="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -309,8 +271,8 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-2">
|
||||
<button
|
||||
type="submit"
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-dark"
|
||||
disabled={loading}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user