1.1.0-rc1

This commit is contained in:
michael committed 2026-10-13 10:43:53 +02:00
1 parent 649dd6f028
commit 45ff46b7eb
26 files changed
+4752 -1108

No files matched your search

+46 -2
View File
@@ -1,4 +1,4 @@
import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Clock3, Download, ExternalLink, RotateCcw, SlidersHorizontal, Ticket, UserCheck, Users } from "lucide-react";
import { AlertCircle, ArrowDown, ArrowUp, ChevronLeft, ChevronRight, Clock3, Download, ExternalLink, RotateCcw, SlidersHorizontal, Ticket, UserCheck, Users } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { Fragment, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
@@ -168,6 +168,41 @@ function TicketStatus({ ticket }: { ticket: TicketSummary }) {
return <Badge variant="success">bewertet</Badge>;
}
function formatSignedMinutes(minutes: number) {
if (minutes === 0) {
return formatMinutes(0);
}
return `${minutes > 0 ? "+" : "-"}${formatMinutes(Math.abs(minutes))}`;
}
function teamspaceMonthDeltaMinutes(ticket: TicketSummary) {
const actuals = ticket.teamspace_month_actuals ?? [];
if (actuals.length === 0) {
return 0;
}
const tsMinutes = ticket.teamspace_month_billable_minutes ?? actuals.reduce((sum, actual) => sum + actual.teamspace_billable_minutes, 0);
return ticket.teamspace_month_delta_minutes ?? tsMinutes - ticket.crm_billed_minutes;
}
function TeamspaceDeviationIcon({ ticket }: { ticket: TicketSummary }) {
const deltaMinutes = teamspaceMonthDeltaMinutes(ticket);
if (deltaMinutes === 0) {
return <span className="inline-block size-4" aria-hidden="true" />;
}
const title = deltaMinutes > 0 ? `${formatMinutes(deltaMinutes)} fehlt in TT` : `${formatMinutes(Math.abs(deltaMinutes))} fehlt in TS`;
return (
<span title={`${formatSignedMinutes(deltaMinutes)} · ${title}`} aria-label={`Teamspace-Abweichung: ${title}`}>
<AlertCircle className="mx-auto size-4 text-amber-500" />
</span>
);
}
function ticketStatusKey(ticket: TicketSummary): "open" | "evaluated" {
return (ticket.ticket_open_count ?? ticket.open_count) > 0 ? "open" : "evaluated";
}
@@ -690,6 +725,9 @@ export function AnalysisPage({ currentUser, onNavigate }: AnalysisPageProps) {
<TableHeader>
<TableRow>
<TableHead>Ticket</TableHead>
<TableHead className="w-10 text-center">
<span className="sr-only">Teamspace-Abweichung</span>
</TableHead>
<TableHead>Organisation</TableHead>
<TableHead>Art</TableHead>
<TableHead>Sessions</TableHead>
@@ -730,6 +768,9 @@ export function AnalysisPage({ currentUser, onNavigate }: AnalysisPageProps) {
</div>
{ticket.title ? <p className="max-w-[220px] truncate text-xs text-muted-foreground">{ticket.title}</p> : null}
</TableCell>
<TableCell className="text-center">
<TeamspaceDeviationIcon ticket={ticket} />
</TableCell>
<TableCell>{ticket.customer_name ?? "-"}</TableCell>
<TableCell>{workTypeLabel(ticket.work_type)}</TableCell>
<TableCell>{ticket.session_count}</TableCell>
@@ -785,7 +826,10 @@ export function AnalysisPage({ currentUser, onNavigate }: AnalysisPageProps) {
{ticket.title ? <p className="truncate text-xs text-muted-foreground">{ticket.title}</p> : null}
<p className="text-sm text-muted-foreground">{ticket.customer_name ?? "Keine Organisation"} · {workTypeLabel(ticket.work_type)} · {ticket.session_count} Session(s)</p>
</div>
<TicketStatus ticket={ticket} />
<div className="flex shrink-0 items-center gap-2">
<TeamspaceDeviationIcon ticket={ticket} />
<TicketStatus ticket={ticket} />
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
<div className="rounded-md bg-muted/50 p-2">