180 lines
6.4 KiB
React
180 lines
6.4 KiB
React
import { useRef, useState } from 'react'
|
|
import { useScrollIntoView } from '../hooks/useScrollIntoView'
|
|
import { isoToGermanDate, timeToWoms, germanDateToIso, womsTimeToInput, minutesBetween, isGitWorksheet, needsTimeEntry } from '../lib/ticketForm'
|
|
import { Button, DateTimeInput, Dialog, DurationSelect, Field } from './ui'
|
|
import { roundUpToStep, isValidStep } from '../lib/timeSteps'
|
|
import { parseSupporters, validateSupporters } from '../lib/support'
|
|
import SupportersEditor from './SupportersEditor'
|
|
import { useAbgerechnet } from '../hooks/useAbgerechnet'
|
|
import { abgerechnetWarnung, ticketIdsOf } from '../lib/projektkontoView'
|
|
|
|
// Gespeichert ist dd.mm.yyyy / hhmm, die Kalender-Felder brauchen yyyy-mm-dd / hh:mm
|
|
const formFromWorksheet = (worksheet) => ({
|
|
startDate: germanDateToIso(worksheet.startDate || worksheet.endDate),
|
|
startTime: womsTimeToInput(worksheet.startTime),
|
|
endDate: germanDateToIso(worksheet.endDate || worksheet.startDate),
|
|
endTime: womsTimeToInput(worksheet.endTime)
|
|
})
|
|
|
|
// Mitarbeiterliste der Seite (useEmployees laeuft dort einmal); der Editor ruft den Hook nicht selbst auf
|
|
const NO_EMPLOYEES_STATE = { employees: [], loading: false, error: null }
|
|
|
|
/**
|
|
* Beginn, Ende und Arbeitszeit eines Arbeitsblatts nachtraeglich aendern.
|
|
* Wer das darf, entscheidet canEditWorksheetTimes (lib/ticketForm).
|
|
*/
|
|
export default function WorksheetTimesModal({ worksheet, currentUserId, onClose, onSave, employeesState = NO_EMPLOYEES_STATE }) {
|
|
const isGit = isGitWorksheet(worksheet)
|
|
const abgerechnet = useAbgerechnet(ticketIdsOf([worksheet]))
|
|
const warnung = abgerechnetWarnung(abgerechnet, worksheet.$id)
|
|
const [formData, setFormData] = useState(() => formFromWorksheet(worksheet))
|
|
// 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
|
|
const calculated = minutesBetween(formData.startTime, formData.endTime)
|
|
if (!stored || (calculated !== null && stored === roundUpToStep(calculated))) return null
|
|
return isValidStep(stored) ? stored : roundUpToStep(stored)
|
|
})
|
|
const [supporters, setSupporters] = useState(() => parseSupporters(worksheet.supporters))
|
|
const [saving, setSaving] = useState(false)
|
|
const [error, setError] = useState('')
|
|
const errorRef = useRef(null)
|
|
useScrollIntoView(errorRef, error)
|
|
|
|
const calculatedMinutes = minutesBetween(formData.startTime, formData.endTime)
|
|
const calculatedTotal = calculatedMinutes === null ? null : roundUpToStep(calculatedMinutes)
|
|
const totalTime = manualTotal ?? calculatedTotal ?? 0
|
|
|
|
const handleChange = (field, value) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }))
|
|
}
|
|
|
|
// 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()
|
|
if (!isValidStep(totalTime)) {
|
|
setError('Bitte die Arbeitszeit auswählen.')
|
|
return
|
|
}
|
|
const supportError = validateSupporters(supporters, currentUserId)
|
|
if (supportError) {
|
|
setError(supportError)
|
|
return
|
|
}
|
|
setSaving(true)
|
|
setError('')
|
|
try {
|
|
const result = await onSave(worksheet, {
|
|
startDate: isoToGermanDate(formData.startDate),
|
|
startTime: timeToWoms(formData.startTime),
|
|
endDate: isoToGermanDate(formData.endDate),
|
|
endTime: timeToWoms(formData.endTime),
|
|
totalTime: Number(totalTime),
|
|
supporters,
|
|
supportersBefore: worksheet.supporters
|
|
})
|
|
if (result?.success) {
|
|
onClose()
|
|
return
|
|
}
|
|
setError(result?.error || 'Speichern fehlgeschlagen')
|
|
} catch (err) {
|
|
setError(err.message || 'Speichern fehlgeschlagen')
|
|
}
|
|
setSaving(false)
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
as="form"
|
|
title={needsTimeEntry(worksheet) ? 'Zeit nachtragen' : 'Zeiten bearbeiten'}
|
|
busy={saving}
|
|
onClose={onClose}
|
|
onSubmit={handleSubmit}
|
|
footer={
|
|
<>
|
|
<span className="grow" />
|
|
<Button variant="primary" type="submit" disabled={saving}>
|
|
{saving ? 'Speichert …' : 'Speichern'}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
{error && (
|
|
<div className="msg err" role="alert" ref={errorRef}>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<p className="muted">
|
|
WSID {worksheet.wsid}
|
|
{isGit && <><br />Das Ende ist der Zeitpunkt des Git-Pushs. Trag ein, wann die Arbeit begonnen hat.</>}
|
|
</p>
|
|
|
|
{warnung && (
|
|
<p className="msg warn" role="alert">
|
|
{warnung}
|
|
</p>
|
|
)}
|
|
|
|
<Field label="Beginn" htmlFor="worksheet-times-start">
|
|
<DateTimeInput
|
|
id="worksheet-times-start"
|
|
timeLabel="Uhrzeit Beginn"
|
|
required
|
|
timeRequired={isGit}
|
|
date={formData.startDate}
|
|
time={formData.startTime}
|
|
onDateChange={handleStartDateChange}
|
|
onTimeChange={(value) => handleChange('startTime', value)}
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="Ende" htmlFor="worksheet-times-end">
|
|
<DateTimeInput
|
|
id="worksheet-times-end"
|
|
timeLabel="Uhrzeit Ende"
|
|
required
|
|
min={formData.startDate}
|
|
date={formData.endDate}
|
|
time={formData.endTime}
|
|
onDateChange={(value) => handleChange('endDate', value)}
|
|
onTimeChange={(value) => handleChange('endTime', value)}
|
|
/>
|
|
</Field>
|
|
|
|
<Field
|
|
label="Arbeitszeit"
|
|
htmlFor="worksheet-times-total"
|
|
hint={manualTotal === null && calculatedTotal !== null
|
|
? 'Aus Beginn und Ende berechnet, aufgerundet auf 15 Minuten'
|
|
: 'Von Hand gewählt'}
|
|
>
|
|
<DurationSelect
|
|
id="worksheet-times-total"
|
|
value={totalTime}
|
|
onChange={(value) => setManualTotal(value)}
|
|
/>
|
|
</Field>
|
|
|
|
<SupportersEditor
|
|
value={supporters}
|
|
onChange={setSupporters}
|
|
excludeUserId={currentUserId}
|
|
minutes={totalTime}
|
|
employeesState={employeesState}
|
|
/>
|
|
</Dialog>
|
|
)
|
|
}
|