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