Arbeitsblätter: Abzeichen „abgerechnet (Rechnungsnummer)“ für alle, Warnung beim Ändern der Zeit
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
65526ca9b3
commit
f89ecec8ff
3 files changed
+48
No files matched your search
@@ -1,6 +1,8 @@
|
|||||||
import { EmptyState } from './ui'
|
import { EmptyState } from './ui'
|
||||||
import { needsTimeEntry, isGitWorksheet } from '../lib/ticketForm'
|
import { needsTimeEntry, isGitWorksheet } from '../lib/ticketForm'
|
||||||
import { parseSupporters } from '../lib/support'
|
import { parseSupporters } from '../lib/support'
|
||||||
|
import { useAbgerechnet } from '../hooks/useAbgerechnet'
|
||||||
|
import { abgerechnetText, ticketIdsOf } from '../lib/projektkontoView'
|
||||||
import {
|
import {
|
||||||
showAboToggle,
|
showAboToggle,
|
||||||
statusAfterLabel,
|
statusAfterLabel,
|
||||||
@@ -23,6 +25,8 @@ export default function WorksheetList({
|
|||||||
onOwnSupport,
|
onOwnSupport,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
}) {
|
}) {
|
||||||
|
// Hooks laufen immer, auch bei den frühen Rückgaben unten; die Abzeichen sehen alle Mitarbeiter und zeigen nie Beträge
|
||||||
|
const abgerechnet = useAbgerechnet(ticketIdsOf(worksheets))
|
||||||
if (loading) return <div className="loading">Wird geladen …</div>
|
if (loading) return <div className="loading">Wird geladen …</div>
|
||||||
|
|
||||||
if (!worksheets || worksheets.length === 0) {
|
if (!worksheets || worksheets.length === 0) {
|
||||||
@@ -35,6 +39,7 @@ export default function WorksheetList({
|
|||||||
const when = worksheetWhen(ws)
|
const when = worksheetWhen(ws)
|
||||||
const kind = worksheetKindLabel(ws)
|
const kind = worksheetKindLabel(ws)
|
||||||
const badge = worksheetBadge(ws)
|
const badge = worksheetBadge(ws)
|
||||||
|
const billed = abgerechnetText(abgerechnet, ws.$id)
|
||||||
const lines = supportLines(ws)
|
const lines = supportLines(ws)
|
||||||
const status = statusAfterLabel(ws)
|
const status = statusAfterLabel(ws)
|
||||||
const time = timeLabel(ws)
|
const time = timeLabel(ws)
|
||||||
@@ -58,6 +63,18 @@ export default function WorksheetList({
|
|||||||
{kind && <span className="muted">{kind}</span>}
|
{kind && <span className="muted">{kind}</span>}
|
||||||
<span className="faint xs num">WSID {ws.wsid}</span>
|
<span className="faint xs num">WSID {ws.wsid}</span>
|
||||||
{badge && <span className={`badge ${badge.tone}`}>{badge.text}</span>}
|
{badge && <span className={`badge ${badge.tone}`}>{badge.text}</span>}
|
||||||
|
{billed && (
|
||||||
|
<span
|
||||||
|
className="badge blue"
|
||||||
|
title={
|
||||||
|
billed === 'abgerechnet (Entwurf)'
|
||||||
|
? 'Dieses Arbeitsblatt steht in einem Rechnungsentwurf.'
|
||||||
|
: 'Dieses Arbeitsblatt ist auf einer Rechnung abgerechnet.'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{billed}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{showAbo ? (
|
{showAbo ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { Button, DateTimeInput, Dialog, DurationSelect, Field } from './ui'
|
|||||||
import { roundUpToStep, isValidStep } from '../lib/timeSteps'
|
import { roundUpToStep, isValidStep } from '../lib/timeSteps'
|
||||||
import { parseSupporters, validateSupporters } from '../lib/support'
|
import { parseSupporters, validateSupporters } from '../lib/support'
|
||||||
import SupportersEditor from './SupportersEditor'
|
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
|
// Gespeichert ist dd.mm.yyyy / hhmm, die Kalender-Felder brauchen yyyy-mm-dd / hh:mm
|
||||||
const formFromWorksheet = (worksheet) => ({
|
const formFromWorksheet = (worksheet) => ({
|
||||||
@@ -23,6 +25,8 @@ const NO_EMPLOYEES_STATE = { employees: [], loading: false, error: null }
|
|||||||
*/
|
*/
|
||||||
export default function WorksheetTimesModal({ worksheet, currentUserId, onClose, onSave, employeesState = NO_EMPLOYEES_STATE }) {
|
export default function WorksheetTimesModal({ worksheet, currentUserId, onClose, onSave, employeesState = NO_EMPLOYEES_STATE }) {
|
||||||
const isGit = isGitWorksheet(worksheet)
|
const isGit = isGitWorksheet(worksheet)
|
||||||
|
const abgerechnet = useAbgerechnet(ticketIdsOf([worksheet]))
|
||||||
|
const warnung = abgerechnetWarnung(abgerechnet, worksheet.$id)
|
||||||
const [formData, setFormData] = useState(() => formFromWorksheet(worksheet))
|
const [formData, setFormData] = useState(() => formFromWorksheet(worksheet))
|
||||||
// null = die Arbeitszeit folgt Beginn und Ende (aufgerundet auf 15 Minuten). Passte sie
|
// 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.
|
// schon vorher nicht dazu, wurde sie von Hand gewaehlt (z.B. ohne Pause) und bleibt stehen.
|
||||||
@@ -117,6 +121,12 @@ export default function WorksheetTimesModal({ worksheet, currentUserId, onClose,
|
|||||||
{isGit && <><br />Das Ende ist der Zeitpunkt des Git-Pushs. Trag ein, wann die Arbeit begonnen hat.</>}
|
{isGit && <><br />Das Ende ist der Zeitpunkt des Git-Pushs. Trag ein, wann die Arbeit begonnen hat.</>}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{warnung && (
|
||||||
|
<p className="msg warn" role="alert">
|
||||||
|
{warnung}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<Field label="Beginn" htmlFor="worksheet-times-start">
|
<Field label="Beginn" htmlFor="worksheet-times-start">
|
||||||
<DateTimeInput
|
<DateTimeInput
|
||||||
id="worksheet-times-start"
|
id="worksheet-times-start"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { getAbgerechnet } from '../lib/teamApi'
|
||||||
|
|
||||||
|
const NICHTS = Object.freeze({})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arbeitsblatt-Kennung -> Rechnungsnummer oder „Entwurf“ für die Tickets in `ticketIds` (Team-Route, alle Mitarbeiter,
|
||||||
|
* ohne Beträge). Ein Fehler verbirgt nur die Abzeichen, das Arbeitsblatt bleibt bedienbar; der Hook wirft nie.
|
||||||
|
*/
|
||||||
|
export function useAbgerechnet(ticketIds) {
|
||||||
|
const key = (ticketIds || []).join(',')
|
||||||
|
const [state, setState] = useState({ key: '', map: NICHTS })
|
||||||
|
useEffect(() => {
|
||||||
|
if (!key) return undefined
|
||||||
|
let cancelled = false
|
||||||
|
Promise.all(key.split(',').map((id) => getAbgerechnet(id).then((r) => r?.abgerechnet || NICHTS).catch(() => NICHTS)))
|
||||||
|
.then((maps) => { if (!cancelled) setState({ key, map: Object.assign({}, ...maps) }) })
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}, [key])
|
||||||
|
return state.key === key ? state.map : NICHTS
|
||||||
|
}
|
||||||
Reference in new issue
Block a user