From ff536b048ffbcabd9b7a8ef6ae7c3d2e4907392f Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Sat, 11 Jul 2026 18:10:10 +0000 Subject: [PATCH] feat: Zeit nachtragen fuer Git-Push-Worksheets Git-Pushes erzeugen Worksheets mit automatischer Endzeit (Push-Zeitpunkt), aber ohne Startzeit. Neu: - WorksheetList: Pille 'Zeit offen' + Button 'Zeit nachtragen' bei GIT-Worksheets ohne Startzeit/Arbeitszeit - EditWorksheetTimeModal: Startdatum/-zeit erfassen, Arbeitszeit wird live berechnet (inkl. Uebernacht-Logik), speichert via updateWorksheet und setzt isComment=false, damit der Eintrag in der Arbeitszeit-Statistik zaehlt Co-Authored-By: Claude Fable 5 --- src/components/EditWorksheetTimeModal.jsx | 142 ++++++++++++++++++++++ src/components/TicketRow.jsx | 15 ++- src/components/WorksheetList.jsx | 45 ++++++- 3 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 src/components/EditWorksheetTimeModal.jsx diff --git a/src/components/EditWorksheetTimeModal.jsx b/src/components/EditWorksheetTimeModal.jsx new file mode 100644 index 0000000..a4cd04f --- /dev/null +++ b/src/components/EditWorksheetTimeModal.jsx @@ -0,0 +1,142 @@ +import { useEffect, useMemo, useState } from 'react' +import { FaClock } from 'react-icons/fa' + +/** Minuten zwischen hhmm-Zeiten, Übernacht (+24h) wie useWorksheets.calculateTime. */ +function calculateMinutes(startTime, endTime) { + if (!startTime || !endTime) return null + const sh = parseInt(startTime.substring(0, 2), 10) + const sm = parseInt(startTime.substring(2, 4), 10) + const eh = parseInt(endTime.substring(0, 2), 10) + const em = parseInt(endTime.substring(2, 4), 10) + if ([sh, sm, eh, em].some(Number.isNaN)) return null + let diff = eh * 60 + em - (sh * 60 + sm) + if (diff < 0) diff += 24 * 60 + return diff +} + +function formatMinutes(minutes) { + if (minutes === null || minutes === undefined) return '–' + const h = Math.floor(minutes / 60) + const m = minutes % 60 + return h > 0 ? `${h}h ${m}min` : `${m}min` +} + +/** + * Startzeit fuer Git-Push-Worksheets nachtragen: Die Endzeit wurde beim Push + * automatisch gesetzt, hier wird der Arbeitsbeginn erfasst. Beim Speichern + * wird totalTime berechnet und isComment auf false gesetzt, damit der Eintrag + * in der Arbeitszeit-Statistik zaehlt. + */ +export default function EditWorksheetTimeModal({ isOpen, onClose, worksheet, onSave }) { + const [startDate, setStartDate] = useState('') + const [startTime, setStartTime] = useState('') + const [saving, setSaving] = useState(false) + const [error, setError] = useState('') + + useEffect(() => { + if (isOpen && worksheet) { + setStartDate(worksheet.startDate || worksheet.endDate || '') + setStartTime(worksheet.startTime || '') + setError('') + } + }, [isOpen, worksheet]) + + const totalTime = useMemo( + () => calculateMinutes(startTime, worksheet?.endTime), + [startTime, worksheet] + ) + + if (!isOpen || !worksheet) return null + + const handleSubmit = async (e) => { + e.preventDefault() + if (!/^\d{2}\.\d{2}\.\d{4}$/.test(startDate)) { + setError('Startdatum bitte als TT.MM.JJJJ angeben.') + return + } + if (!/^\d{4}$/.test(startTime)) { + setError('Startzeit bitte als HHMM angeben (z.B. 0930).') + return + } + if (totalTime === null) { + setError('Zeit konnte nicht berechnet werden.') + return + } + setSaving(true) + setError('') + const result = await onSave(worksheet, { + startDate, + startTime, + totalTime, + isComment: false, + }) + setSaving(false) + if (result?.success) { + onClose() + } else { + setError(result?.error || 'Speichern fehlgeschlagen') + } + } + + return ( +
+ + × + +
+

+ + Zeit nachtragen – WSID {worksheet.wsid} +

+

+ Die Endzeit wurde beim Git-Push automatisch gesetzt + ({worksheet.endDate} {worksheet.endTime ? `${worksheet.endTime.substring(0, 2)}:${worksheet.endTime.substring(2, 4)}` : ''}). + Trage hier ein, wann die Arbeit begonnen hat. +

+ + {error && ( +
+ {error} +
+ )} + +
+
+ + setStartDate(e.target.value)} + placeholder="11.07.2026" + required + /> +
+
+ + setStartTime(e.target.value.replace(/[^0-9]/g, '').slice(0, 4))} + placeholder="0930" + maxLength={4} + required + /> +
+
+ +
+ {formatMinutes(totalTime)} +
+
+
+ +
+
+
+
+ ) +} diff --git a/src/components/TicketRow.jsx b/src/components/TicketRow.jsx index b9ba8e3..cb8ddd7 100644 --- a/src/components/TicketRow.jsx +++ b/src/components/TicketRow.jsx @@ -7,6 +7,7 @@ import PriorityDropdown from './PriorityDropdown' import EditorDropdown from './EditorDropdown' import ResponseDropdown from './ResponseDropdown' import CreateWorksheetModal from './CreateWorksheetModal' +import EditWorksheetTimeModal from './EditWorksheetTimeModal' import StatusHistoryModal from './StatusHistoryModal' import WorksheetList from './WorksheetList' import WorksheetStats from './WorksheetStats' @@ -52,6 +53,7 @@ export default function TicketRow({ ticket, onUpdate, onExpand }) { const [locked, setLocked] = useState(true) const [activeTab, setActiveTab] = useState('details') const [showCreateWorksheet, setShowCreateWorksheet] = useState(false) + const [editTimeWorksheet, setEditTimeWorksheet] = useState(null) const [showHistoryModal, setShowHistoryModal] = useState(false) const [showProjectsModal, setShowProjectsModal] = useState(false) const [projectsRefreshKey, setProjectsRefreshKey] = useState(0) @@ -60,9 +62,13 @@ export default function TicketRow({ ticket, onUpdate, onExpand }) { worksheets, loading: worksheetsLoading, createWorksheet, + updateWorksheet, getTotalTime } = useWorksheets(expanded ? ticket.woid : null) + // Startzeit fuer Git-Push-Worksheets nachtragen (Endzeit kam vom Push) + const handleSaveWorksheetTime = async (ws, data) => updateWorksheet(ws.$id, data) + const createdAt = new Date(ticket.$createdAt || ticket.createdAt) const elapsed = formatDistanceToNow(createdAt, { locale: de }) @@ -237,7 +243,7 @@ export default function TicketRow({ ticket, onUpdate, onExpand }) { )} - + )} @@ -272,6 +278,13 @@ export default function TicketRow({ ticket, onUpdate, onExpand }) { onCreate={handleCreateWorksheet} /> + setEditTimeWorksheet(null)} + worksheet={editTimeWorksheet} + onSave={handleSaveWorksheetTime} + /> + setShowHistoryModal(false)} diff --git a/src/components/WorksheetList.jsx b/src/components/WorksheetList.jsx index e40a403..c7f6770 100644 --- a/src/components/WorksheetList.jsx +++ b/src/components/WorksheetList.jsx @@ -1,7 +1,11 @@ import { useState } from 'react' import { FaClock, FaUser, FaExchangeAlt, FaComment, FaChevronDown, FaChevronUp } from 'react-icons/fa' -export default function WorksheetList({ worksheets, totalTime, loading }) { +function needsTimeEntry(ws) { + return ws.serviceType === 'GIT' && (!ws.startTime || !Number(ws.totalTime)) +} + +export default function WorksheetList({ worksheets, totalTime, loading, onEditTime }) { const [expandedWorksheets, setExpandedWorksheets] = useState({}) if (loading) { return ( @@ -105,13 +109,20 @@ export default function WorksheetList({ worksheets, totalTime, loading }) { )}
WSID {ws.wsid} - {ws.isComment && ( - Kommentar )} + {needsTimeEntry(ws) && ( + + Zeit offen + + )}
{/* Collapsed: Mitarbeiter & Zeit im Header */} {!isExpanded && ( @@ -133,9 +144,31 @@ export default function WorksheetList({ worksheets, totalTime, loading }) { )} - - {formatDateTime(ws.startDate, ws.startTime)} - +
+ {needsTimeEntry(ws) && onEditTime && ( + + )} + + {formatDateTime(ws.startDate, ws.startTime) || formatDateTime(ws.endDate, ws.endTime)} + +
{/* Body - Nur wenn expanded */}