diff --git a/src/components/CreateWorksheetModal.jsx b/src/components/CreateWorksheetModal.jsx index 325849a..31eeef9 100644 --- a/src/components/CreateWorksheetModal.jsx +++ b/src/components/CreateWorksheetModal.jsx @@ -3,7 +3,8 @@ import { FaTimes } from 'react-icons/fa' import { useAuth } from '../context/AuthContext' import { SERVICE_TYPES, todayIso, isoToGermanDate, timeToWoms, minutesBetween } from '../lib/ticketForm' import { TICKET_STATUSES, statusLabel } from '../lib/ticketStatus' -import { DateTimeInput } from './ui' +import { DateTimeInput, DurationSelect } from './ui' +import { roundUpToStep, isValidStep } from '../lib/timeSteps' // Akquise-Tickets: Ergebnis des Kundenkontakts (statt technischer Status) const ACQUISITION_STATUS = ['In Kontakt', 'Zugesagt', 'Abgesagt'] @@ -57,12 +58,12 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre } }, [isOpen, workorder, defaultStatus]) - // Automatische Zeitberechnung + // Automatische Zeitberechnung, aufgerundet auf 15 Minuten useEffect(() => { if (!autoCalculate || formData.isComment) return const minutes = minutesBetween(formData.startTime, formData.endTime) if (minutes !== null) { - setFormData(prev => ({ ...prev, totalTime: minutes })) + setFormData(prev => ({ ...prev, totalTime: roundUpToStep(minutes) })) } }, [formData.startTime, formData.endTime, formData.isComment, autoCalculate]) @@ -95,6 +96,11 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre setLoading(false) return } + if (!formData.isComment && !isValidStep(formData.totalTime)) { + setError('Bitte die Arbeitszeit auswählen.') + setLoading(false) + return + } const worksheetData = { woid: workorder.woid, @@ -102,7 +108,7 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre serviceType: formData.serviceType, oldStatus: workorder.status, newStatus: formData.newStatus, - totalTime: formData.isComment ? 0 : parseInt(formData.totalTime) || 0, + totalTime: formData.isComment ? 0 : Number(formData.totalTime), startDate: isoToGermanDate(formData.startDate), startTime: timeToWoms(formData.startTime), endDate: isoToGermanDate(formData.endDate), @@ -235,22 +241,17 @@ export default function CreateWorksheetModal({ isOpen, onClose, workorder, onCre
- - {/* Ganze Minuten: mit step 15 blockierte der Browser berechnete Zeiten wie 70 */} - handleChange('totalTime', e.target.value)} + + handleChange('totalTime', value)} disabled={formData.isComment} - placeholder="0" /> {autoCalculate && formData.startTime && formData.endTime - ? '✓ Automatisch berechnet' - : 'Manuell eingeben'} + ? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten' + : autoCalculate ? 'Beginn und Ende eintragen oder Zeit wählen' : 'Von Hand gewählt'}
diff --git a/src/components/WorksheetTimesModal.jsx b/src/components/WorksheetTimesModal.jsx index fa992dc..63a1312 100644 --- a/src/components/WorksheetTimesModal.jsx +++ b/src/components/WorksheetTimesModal.jsx @@ -1,7 +1,8 @@ import { useState } from 'react' import { FaTimes } from 'react-icons/fa' import { isoToGermanDate, timeToWoms, germanDateToIso, womsTimeToInput, minutesBetween, isGitWorksheet, needsTimeEntry } from '../lib/ticketForm' -import { DateTimeInput } from './ui' +import { DateTimeInput, DurationSelect } from './ui' +import { roundUpToStep, isValidStep } from '../lib/timeSteps' // Gespeichert ist dd.mm.yyyy / hhmm, die Kalender-Felder brauchen yyyy-mm-dd / hh:mm const formFromWorksheet = (worksheet) => ({ @@ -18,16 +19,20 @@ const formFromWorksheet = (worksheet) => ({ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) { const isGit = isGitWorksheet(worksheet) const [formData, setFormData] = useState(() => formFromWorksheet(worksheet)) - // null = die Arbeitszeit folgt Beginn und Ende. Passte sie schon vorher nicht dazu, - // wurde sie von Hand eingetragen (z.B. ohne Pause) und bleibt stehen. + // null = die Arbeitszeit folgt Beginn und Ende (aufgerundet auf 15 Minuten). Passte sie + // schon vorher nicht dazu, wurde sie von Hand gewaehlt (z.B. ohne Pause) und bleibt stehen. + // Alte Zeiten ausserhalb des Rasters (z.B. 70) werden auf den naechsten Schritt aufgerundet. const [manualTotal, setManualTotal] = useState(() => { const stored = Number(worksheet.totalTime) || 0 - return !stored || stored === minutesBetween(formData.startTime, formData.endTime) ? null : stored + const calculated = minutesBetween(formData.startTime, formData.endTime) + if (!stored || (calculated !== null && stored === roundUpToStep(calculated))) return null + return isValidStep(stored) ? stored : roundUpToStep(stored) }) const [saving, setSaving] = useState(false) const [error, setError] = useState('') - const calculatedTotal = minutesBetween(formData.startTime, formData.endTime) + const calculatedMinutes = minutesBetween(formData.startTime, formData.endTime) + const calculatedTotal = calculatedMinutes === null ? null : roundUpToStep(calculatedMinutes) const totalTime = manualTotal ?? calculatedTotal ?? 0 const handleChange = (field, value) => { @@ -45,6 +50,10 @@ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) { const handleSubmit = async (e) => { e.preventDefault() + if (!isValidStep(totalTime)) { + setError('Bitte die Arbeitszeit auswählen.') + return + } setSaving(true) setError('') try { @@ -53,7 +62,7 @@ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) { startTime: timeToWoms(formData.startTime), endDate: isoToGermanDate(formData.endDate), endTime: timeToWoms(formData.endTime), - totalTime: parseInt(totalTime, 10) || 0 + totalTime: Number(totalTime) }) if (result?.success) { onClose() @@ -115,21 +124,16 @@ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) {
- - Arbeitszeit + setManualTotal(e.target.value)} - placeholder="0" + onChange={(value) => setManualTotal(value)} /> {manualTotal === null && calculatedTotal !== null - ? '✓ Automatisch berechnet' - : 'Manuell eingeben'} + ? '✓ Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten' + : 'Von Hand gewählt'}
diff --git a/src/components/ui/DurationSelect.jsx b/src/components/ui/DurationSelect.jsx new file mode 100644 index 0000000..e440921 --- /dev/null +++ b/src/components/ui/DurationSelect.jsx @@ -0,0 +1,20 @@ +import { stepOptions } from '../../lib/timeSteps' + +/* Arbeitszeit nur in 15-Minuten-Schritten (15 min bis 12 h), siehe lib/timeSteps */ +export default function DurationSelect({ id, value, onChange, disabled }) { + return ( + + ) +} diff --git a/src/components/ui/index.js b/src/components/ui/index.js index c4bb700..450ce02 100644 --- a/src/components/ui/index.js +++ b/src/components/ui/index.js @@ -9,3 +9,4 @@ export { default as Field } from './Field' export { default as Kpi } from './Kpi' export { default as AutocompleteInput } from './AutocompleteInput' export { default as DateTimeInput } from './DateTimeInput' +export { default as DurationSelect } from './DurationSelect' diff --git a/src/hooks/useWorksheets.js b/src/hooks/useWorksheets.js index b9969fd..ff153fe 100644 --- a/src/hooks/useWorksheets.js +++ b/src/hooks/useWorksheets.js @@ -1,6 +1,9 @@ import { useState, useEffect, useCallback } from 'react' import { databases, DATABASE_ID, COLLECTIONS, Query, ID } from '../lib/appwrite' import { canEditWorksheetTimes, isGitWorksheet } from '../lib/ticketForm' +import { roundUpToStep, isValidStep } from '../lib/timeSteps' + +const STEP_ERROR = 'Die Arbeitszeit muss in 15-Minuten-Schritten angegeben werden.' export function useWorksheets(woid = null) { const [worksheets, setWorksheets] = useState([]) @@ -143,14 +146,17 @@ export function useWorksheets(woid = null) { // 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) { + // Automatische Zeitberechnung (wenn nicht manuell angegeben), aufgerundet auf 15 Minuten + let totalTime = data.isComment ? 0 : Number(data.totalTime) || 0 + if (!data.isComment && data.startTime && data.endTime && !totalTime) { const calculatedTime = calculateTime(data.startTime, data.endTime) if (calculatedTime !== null) { - totalTime = calculatedTime + totalTime = roundUpToStep(calculatedTime) } } + if (!data.isComment && !isValidStep(totalTime)) { + return { success: false, error: STEP_ERROR } + } // Worksheet-Daten vorbereiten const worksheetData = { @@ -165,7 +171,7 @@ export function useWorksheets(woid = null) { newStatus: data.newStatus || data.oldStatus || '', oldResponseLevel: data.oldResponseLevel || '', newResponseLevel: data.newResponseLevel || data.oldResponseLevel || '', - totalTime: parseInt(totalTime) || 0, + totalTime, startDate: data.startDate || '', startTime: data.startTime || '', endDate: data.endDate || data.startDate || '', @@ -224,6 +230,7 @@ export function useWorksheets(woid = null) { return { success: false, error: 'Nur wer das Arbeitsblatt erstellt hat, kann die Zeiten ändern.' } } const { startDate, startTime, endDate, endTime, totalTime } = times + if (!isValidStep(totalTime)) return { success: false, error: STEP_ERROR } const data = { startDate, startTime, endDate, endTime, totalTime } // Mit nachgetragener Zeit zaehlt ein Git-Push als Arbeitszeit, nicht mehr als Kommentar if (isGitWorksheet(worksheet)) data.isComment = false