From 0c884a57d72c852d9b17ffddd8773fea81cf98e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 09:55:34 +0000 Subject: [PATCH] =?UTF-8?q?Arbeitsbl=C3=A4tter:=20Zeit=20f=C3=BCr=20eigene?= =?UTF-8?q?=20Git-Pushes=20nachtragen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 70 von 78 Arbeitsblättern sind Git-Pushes, die das Kundenportal mit employeeId "gitea" anlegt; in employeeName steht der Gitea-Name des Pushers ("knso", "ANDJ", "JUSN" oder der volle Name). Sie gehören dem Mitarbeiter mit diesem Kürzel oder Namen - der eigene employees-Eintrag kommt jetzt aus dem AuthContext. Bei offenen Pushes heißt der Button "Zeit nachtragen": Beginn ist Pflicht, das Ende ist der Push. Beim Speichern wird isComment false, damit die Zeit in der Arbeitszeit zählt (wie früher im Modal der alten Ticketzeile). Co-Authored-By: Claude Opus 5.5 --- src/components/TicketCard.jsx | 8 +++---- src/components/WorksheetList.jsx | 14 +++++------- src/components/WorksheetTimesModal.jsx | 15 ++++++++----- src/components/ui/DateTimeInput.jsx | 3 ++- src/context/AuthContext.jsx | 17 +++++++++----- src/hooks/useWorksheets.js | 12 ++++++---- src/lib/ticketForm.js | 31 ++++++++++++++++++++++---- 7 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/components/TicketCard.jsx b/src/components/TicketCard.jsx index 4d6d085..a686ffb 100644 --- a/src/components/TicketCard.jsx +++ b/src/components/TicketCard.jsx @@ -22,7 +22,7 @@ import { StatusPill, PriorityPill } from './ui/StatusPill' import { useWorksheets } from '../hooks/useWorksheets' import { useAuth } from '../context/AuthContext' import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite' -import { serviceTypeLabel } from '../lib/ticketForm' +import { serviceTypeLabel, canEditWorksheetTimes } from '../lib/ticketForm' const TABS = [ { id: 'details', label: 'Details' }, @@ -33,7 +33,7 @@ const TABS = [ ] export default function TicketCard({ ticket, onUpdate }) { - const { user } = useAuth() + const { user, employee } = useAuth() const [expanded, setExpanded] = useState(false) const [activeTab, setActiveTab] = useState('details') const [showCreateWorksheet, setShowCreateWorksheet] = useState(false) @@ -148,7 +148,7 @@ export default function TicketCard({ ticket, onUpdate }) { worksheets={worksheets} totalTime={getTotalTime()} loading={worksheetsLoading} - currentUser={user} + canEditTimes={(ws) => canEditWorksheetTimes(ws, user, employee)} onEditTimes={setEditTimesWorksheet} /> @@ -174,7 +174,7 @@ export default function TicketCard({ ticket, onUpdate }) { setEditTimesWorksheet(null)} - onSave={(ws, times) => updateWorksheetTimes(ws, times, user)} + onSave={(ws, times) => updateWorksheetTimes(ws, times, user, employee)} /> )} setShowHistoryModal(false)} worksheets={worksheets} ticket={ticket} /> diff --git a/src/components/WorksheetList.jsx b/src/components/WorksheetList.jsx index 4b71687..0d9e365 100644 --- a/src/components/WorksheetList.jsx +++ b/src/components/WorksheetList.jsx @@ -1,10 +1,6 @@ import { useState } from 'react' import { FaClock, FaUser, FaExchangeAlt, FaComment, FaChevronDown, FaChevronUp, FaPen } from 'react-icons/fa' -import { serviceTypeLabel, canEditWorksheetTimes } from '../lib/ticketForm' - -function needsTimeEntry(ws) { - return ws.serviceType === 'GIT' && (!ws.startTime || !Number(ws.totalTime)) -} +import { serviceTypeLabel, needsTimeEntry } from '../lib/ticketForm' const headerButtonStyle = { background: 'rgba(255,255,255,0.25)', @@ -16,7 +12,7 @@ const headerButtonStyle = { marginRight: '8px', } -export default function WorksheetList({ worksheets, totalTime, loading, onEditTime, onTogglePlan, currentUser, onEditTimes }) { +export default function WorksheetList({ worksheets, totalTime, loading, onEditTime, onTogglePlan, canEditTimes, onEditTimes }) { const [expandedWorksheets, setExpandedWorksheets] = useState({}) if (loading) { return ( @@ -176,7 +172,7 @@ export default function WorksheetList({ worksheets, totalTime, loading, onEditTi Zeit nachtragen )} - {onEditTimes && canEditWorksheetTimes(ws, currentUser) && ( + {onEditTimes && canEditTimes?.(ws) && ( )} {onTogglePlan && !ws.isComment && Number(ws.totalTime) > 0 && ( diff --git a/src/components/WorksheetTimesModal.jsx b/src/components/WorksheetTimesModal.jsx index d122144..fa992dc 100644 --- a/src/components/WorksheetTimesModal.jsx +++ b/src/components/WorksheetTimesModal.jsx @@ -1,11 +1,11 @@ import { useState } from 'react' import { FaTimes } from 'react-icons/fa' -import { isoToGermanDate, timeToWoms, germanDateToIso, womsTimeToInput, minutesBetween } from '../lib/ticketForm' +import { isoToGermanDate, timeToWoms, germanDateToIso, womsTimeToInput, minutesBetween, isGitWorksheet, needsTimeEntry } from '../lib/ticketForm' import { DateTimeInput } from './ui' // Gespeichert ist dd.mm.yyyy / hhmm, die Kalender-Felder brauchen yyyy-mm-dd / hh:mm const formFromWorksheet = (worksheet) => ({ - startDate: germanDateToIso(worksheet.startDate), + startDate: germanDateToIso(worksheet.startDate || worksheet.endDate), startTime: womsTimeToInput(worksheet.startTime), endDate: germanDateToIso(worksheet.endDate || worksheet.startDate), endTime: womsTimeToInput(worksheet.endTime) @@ -13,9 +13,10 @@ const formFromWorksheet = (worksheet) => ({ /** * Beginn, Ende und Arbeitszeit eines Arbeitsblatts nachtraeglich aendern. - * Wer das darf, entscheidet canEditWorksheetTimes (useWorksheets). + * Wer das darf, entscheidet canEditWorksheetTimes (lib/ticketForm). */ 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. @@ -72,8 +73,11 @@ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) {
{/* Kurzer Titel: auf dem Handy laeuft ein langer unter das Schliessen-X */} -

Zeiten bearbeiten

-

WSID {worksheet.wsid}

+

{needsTimeEntry(worksheet) ? 'Zeit nachtragen' : 'Zeiten bearbeiten'}

+

+ WSID {worksheet.wsid} + {isGit && <>
Das Ende ist der Zeitpunkt des Git-Pushs. Trag ein, wann die Arbeit begonnen hat.} +

{error && (
@@ -88,6 +92,7 @@ export default function WorksheetTimesModal({ worksheet, onClose, onSave }) { id="worksheet-times-start" timeLabel="Uhrzeit Beginn" required + timeRequired={isGit} date={formData.startDate} time={formData.startTime} onDateChange={handleStartDateChange} diff --git a/src/components/ui/DateTimeInput.jsx b/src/components/ui/DateTimeInput.jsx index 56c0a15..39c62c7 100644 --- a/src/components/ui/DateTimeInput.jsx +++ b/src/components/ui/DateTimeInput.jsx @@ -9,7 +9,7 @@ const openCalendar = (e) => { /* Datum mit Kalender und Uhrzeit nebeneinander. Werte wie die Browser-Felder: Datum "yyyy-mm-dd", Uhrzeit "hh:mm" - umgerechnet wird erst beim Speichern. */ -export default function DateTimeInput({ id, date, time, onDateChange, onTimeChange, min, required, timeLabel }) { +export default function DateTimeInput({ id, date, time, onDateChange, onTimeChange, min, required, timeRequired, timeLabel }) { return (
onTimeChange(e.target.value)} /> diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 6084ebd..2cad599 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -70,9 +70,10 @@ async function createSessionWithCleanup(email, password) { } } -// Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu +// Hilfsfunktion: Fügt User automatisch zur employees Collection hinzu und liefert +// den Mitarbeiter-Eintrag (Kürzel = Gitea-Name, siehe isWorksheetCreator) oder null async function ensureEmployeeExists(user) { - if (!user || DEMO_MODE) return + if (!user || DEMO_MODE) return null try { const response = await databases.listDocuments( @@ -82,7 +83,7 @@ async function ensureEmployeeExists(user) { ) if (response.documents.length === 0) { - await databases.createDocument( + const employee = await databases.createDocument( DATABASE_ID, COLLECTIONS.EMPLOYEES, ID.unique(), @@ -94,16 +95,20 @@ async function ensureEmployeeExists(user) { } ) console.log('✅ User automatisch zur Mitarbeiter-Liste hinzugefügt') + return employee } + return response.documents[0] } catch (error) { if (error.code !== 404) { console.warn('Could not add user to employees collection:', error.message) } + return null } } export function AuthProvider({ children }) { const [user, setUser] = useState(null) + const [employee, setEmployee] = useState(null) const [loading, setLoading] = useState(true) useEffect(() => { @@ -129,7 +134,7 @@ export function AuthProvider({ children }) { try { const session = await account.get() setUser(session) - await ensureEmployeeExists(session) + setEmployee(await ensureEmployeeExists(session)) } catch (error) { if (error.code !== 401 && error.code !== 404) { console.error('Unexpected error checking user:', error) @@ -160,7 +165,7 @@ export function AuthProvider({ children }) { const session = await account.get() setUser(session) - await ensureEmployeeExists(session) + setEmployee(await ensureEmployeeExists(session)) return { success: true } } catch (error) { @@ -202,6 +207,7 @@ export function AuthProvider({ children }) { } finally { clearAllLocalAppwriteSessions() setUser(null) + setEmployee(null) } } @@ -209,6 +215,7 @@ export function AuthProvider({ children }) { const value = { user, + employee, loading, login, logout, diff --git a/src/hooks/useWorksheets.js b/src/hooks/useWorksheets.js index ec81baa..4f08761 100644 --- a/src/hooks/useWorksheets.js +++ b/src/hooks/useWorksheets.js @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react' import { databases, DATABASE_ID, COLLECTIONS, Query, ID, isDemoMode } from '../lib/appwrite' -import { canEditWorksheetTimes } from '../lib/ticketForm' +import { canEditWorksheetTimes, isGitWorksheet } from '../lib/ticketForm' const DEMO_MODE = isDemoMode @@ -391,13 +391,17 @@ export function useWorksheets(woid = null) { /** * Aendert nachtraeglich Beginn, Ende und Arbeitszeit - nur fuer den Ersteller + * (employee = eigener Eintrag aus "employees", fuer Git-Pushes) */ - const updateWorksheetTimes = async (worksheet, times, currentUser) => { - if (!canEditWorksheetTimes(worksheet, currentUser)) { + const updateWorksheetTimes = async (worksheet, times, currentUser, employee) => { + if (!canEditWorksheetTimes(worksheet, currentUser, employee)) { return { success: false, error: 'Nur wer das Arbeitsblatt erstellt hat, kann die Zeiten ändern.' } } const { startDate, startTime, endDate, endTime, totalTime } = times - return updateWorksheet(worksheet.$id, { startDate, startTime, endDate, endTime, totalTime }) + 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 + return updateWorksheet(worksheet.$id, data) } /** diff --git a/src/lib/ticketForm.js b/src/lib/ticketForm.js index 52ac57e..54f7eea 100644 --- a/src/lib/ticketForm.js +++ b/src/lib/ticketForm.js @@ -23,10 +23,33 @@ export function serviceTypeLabel(value) { return SERVICE_TYPE_SHORT[String(value || '').trim().toLowerCase()] || value } -// Zeiten eines Arbeitsblatts darf nur aendern, wer es angelegt hat. -// Reine Kommentare haben keine Arbeitszeit und bleiben aussen vor. -export function canEditWorksheetTimes(worksheet, user) { - return Boolean(user?.$id) && worksheet?.employeeId === user.$id && !worksheet.isComment +// Git-Pushes legt das Kundenportal als Arbeitsblatt an (employeeId "gitea"): Ende = Push, +// der Beginn wird im Ticketsystem nachgetragen +export function isGitWorksheet(worksheet) { + return worksheet?.serviceType === 'GIT' +} + +export function needsTimeEntry(worksheet) { + return isGitWorksheet(worksheet) && (!worksheet.startTime || !Number(worksheet.totalTime)) +} + +const sameName = (a, b) => String(a || '').trim().toLowerCase() === String(b || '').trim().toLowerCase() + +/* Wer hat das Arbeitsblatt erstellt? Normale Eintraege tragen die Appwrite-ID des + Mitarbeiters. Bei Git-Pushes steht in employeeName der Gitea-Name des Pushers + (Benutzername wie "knso" oder voller Name); er gehoert dem Mitarbeiter mit diesem + Kuerzel oder Namen. employee ist der eigene Eintrag aus "employees". */ +export function isWorksheetCreator(worksheet, user, employee) { + if (!user?.$id || !worksheet) return false + if (!isGitWorksheet(worksheet)) return worksheet.employeeId === user.$id + const pusher = String(worksheet.employeeName || '').trim() + return Boolean(pusher) && [employee?.shortcode, employee?.displayName, user.name].some((name) => sameName(name, pusher)) +} + +// Zeiten darf nur aendern, wer das Arbeitsblatt erstellt hat. Reine Kommentare haben +// keine Arbeitszeit; Git-Pushes sind Kommentare, bis ihre Zeit nachgetragen ist. +export function canEditWorksheetTimes(worksheet, user, employee) { + return isWorksheetCreator(worksheet, user, employee) && (isGitWorksheet(worksheet) || !worksheet.isComment) } const pad = (n) => String(n).padStart(2, '0')