This commit is contained in:
michael committed 2026-09-15 14:18:21 +00:00
1 parent 8a4cf3fc9b
commit d33b2c487d
1 file changed
+25 -5
+25 -5
View File
@@ -305,6 +305,18 @@ function projectConsumptionPercent(consumptionMinutes: number, budgetMinutes?: n
return Math.round((consumptionMinutes / budgetMinutes) * 100);
}
function projectRemainingLabel(remainingMinutes: number | null) {
if (remainingMinutes === null) {
return "Rest offen";
}
if (remainingMinutes < 0) {
return `Überzogen ${formatMinutes(Math.abs(remainingMinutes))}`;
}
return `Rest ${formatMinutes(remainingMinutes)}`;
}
function isPartialHoursInput(value: string) {
return /^\d*(?:[,.]\d*)?$/.test(value.trim());
}
@@ -562,6 +574,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
const projectConsumptionMinutes = project?.consumption_minutes ?? 0;
const projectBudgetMinutes = project?.hour_budget_minutes ?? null;
const projectConsumptionUsagePercent = project ? projectConsumptionPercent(projectConsumptionMinutes, projectBudgetMinutes) : null;
const projectRemainingMinutes = projectBudgetMinutes && projectBudgetMinutes > 0 ? projectBudgetMinutes - projectConsumptionMinutes : null;
const canEditProject = project?.can_edit !== false;
const canEditTicketData = !project || canEditProject;
const isProject = Boolean(project);
@@ -1396,16 +1409,24 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
<Badge variant="secondary">Zeitraum {project.starts_on || "offen"} - {project.ends_on || "offen"}</Badge>
<Badge variant="secondary">Kontingent {projectBudgetMinutes ? formatMinutes(projectBudgetMinutes) : "offen"}</Badge>
<Badge variant="secondary">Verbrauch {formatMinutes(projectConsumptionMinutes)}</Badge>
<Badge variant={projectRemainingMinutes !== null && projectRemainingMinutes < 0 ? "destructive" : "secondary"}>{projectRemainingLabel(projectRemainingMinutes)}</Badge>
{showBudgetedProjectEntries ? <Badge variant="outline">{budgetedProjectEntries.length} Budgets</Badge> : null}
</div>
{showBudgetedProjectEntries ? (
<div className="space-y-2">
<div>
<div className="mb-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
<span>Gesamtverbrauch</span>
<span>{projectConsumptionUsagePercent !== null ? `${projectConsumptionUsagePercent}%` : "ohne Gesamtbudget"}</span>
<span>Budgetverbrauch</span>
<span className="shrink-0 text-right">
{projectBudgetMinutes && projectBudgetMinutes > 0
? `${formatMinutes(projectConsumptionMinutes)} / ${formatMinutes(projectBudgetMinutes)} · ${projectRemainingLabel(projectRemainingMinutes)}`
: `${formatMinutes(projectConsumptionMinutes)} / offen`}
</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
<div
className="h-2 overflow-hidden rounded-full bg-muted"
title={projectConsumptionUsagePercent !== null ? `${projectConsumptionUsagePercent}% Verbrauch` : "ohne Gesamtbudget"}
aria-label={projectConsumptionUsagePercent !== null ? `${projectConsumptionUsagePercent}% Verbrauch` : "ohne Gesamtbudget"}
>
<div
className={cn("h-full rounded-full", projectConsumptionUsagePercent === null ? "bg-muted-foreground/20" : projectConsumptionBarClass(projectConsumptionUsagePercent))}
style={{ width: `${projectConsumptionUsagePercent === null ? 0 : Math.min(100, projectConsumptionUsagePercent)}%` }}
@@ -1441,7 +1462,6 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
})}
</div>
</div>
) : null}
<div className="text-sm">
<div className="min-w-0">
<div className="mb-1 flex items-center gap-2 font-medium">