Show non-billable status without statistic hours

This commit is contained in:
2026-08-05 22:40:47 +02:00
parent 7d4d373863
commit eff8823371
3 changed files with 17 additions and 20 deletions
+2 -2
View File
@@ -349,13 +349,13 @@ Das bedeutet:
Farben: Farben:
- Abgerechnet: blau - Abgerechnet: blau
- Teamspace: gruen - Teamspace: fuchsia
- Positive oder ausgeglichene Differenz: gruen - Positive oder ausgeglichene Differenz: gruen
- Negative Differenz: rot - Negative Differenz: rot
- Nicht abrechenbar: indigo - Nicht abrechenbar: indigo
- Offen: gelb/orange - Offen: gelb/orange
`Nicht abrechenbar` zeigt Sessions, die bewusst nicht fakturiert werden. Diese Minuten werden nicht mit Teamspace verglichen. `Nicht abrechenbar` zeigt Sessions, die bewusst nicht fakturiert werden. In den Statistiken wird dazu kein Stundenwert ausgegeben; diese Sessions werden nicht mit Teamspace verglichen.
`Offen` zeigt Sessions ohne Bewertung. Diese blockieren den Monatsabschluss. `Offen` zeigt Sessions ohne Bewertung. Diese blockieren den Monatsabschluss.
+6 -6
View File
@@ -231,7 +231,7 @@ export const helpSections: HelpSection[] = [
"Teamspace zeigt, wie viel Zeit für dieselben abrechenbaren Ticket-Tage im CRM eingetragen wurde.", "Teamspace zeigt, wie viel Zeit für dieselben abrechenbaren Ticket-Tage im CRM eingetragen wurde.",
"Eine positive Differenz bedeutet: In Teamspace steht mehr Zeit als abgerechnet wurde.", "Eine positive Differenz bedeutet: In Teamspace steht mehr Zeit als abgerechnet wurde.",
"Eine negative Differenz bedeutet: In Teamspace steht weniger Zeit als abgerechnet wurde.", "Eine negative Differenz bedeutet: In Teamspace steht weniger Zeit als abgerechnet wurde.",
"Nicht abrechenbar und Offen werden jeweils als eigene Werte ausgewiesen.", "Nicht abrechenbar und Offen werden jeweils als eigene Statuswerte ausgewiesen.",
"Sie werden nicht in Relationen, Graphen, Teamspace-Differenzen, Hotspots, Manuell/Fix-Werte oder Durchschnittswerte eingerechnet.", "Sie werden nicht in Relationen, Graphen, Teamspace-Differenzen, Hotspots, Manuell/Fix-Werte oder Durchschnittswerte eingerechnet.",
"Der Monatsverlauf zeigt abgerechnete Session-Zeit als Balken und Teamspace als Linie.", "Der Monatsverlauf zeigt abgerechnete Session-Zeit als Balken und Teamspace als Linie.",
"Organisationen, Typen und Ticket-Hotspots helfen dabei, Auffälligkeiten im Monat schnell zu finden." "Organisationen, Typen und Ticket-Hotspots helfen dabei, Auffälligkeiten im Monat schnell zu finden."
@@ -283,8 +283,8 @@ export const helpSections: HelpSection[] = [
], ],
details: [ details: [
"Nicht abrechenbar ist getrennt von Abgerechnet, damit interne, Kulanz- oder nicht fakturierbare Arbeit sichtbar bleibt.", "Nicht abrechenbar ist getrennt von Abgerechnet, damit interne, Kulanz- oder nicht fakturierbare Arbeit sichtbar bleibt.",
"Diese Minuten werden nicht mit Teamspace verglichen.", "Diese Sessions werden nicht mit Teamspace verglichen.",
"Die Zeit zeigt die Summe der nicht abrechenbaren Session-Minuten.", "Die Statistik zeigt die Anzahl der nicht abrechenbaren Sessions, aber keinen Stundenwert.",
"Es werden daraus keine Anteile, Differenzen oder Vergleichswerte berechnet.", "Es werden daraus keine Anteile, Differenzen oder Vergleichswerte berechnet.",
"Wenn eine Bewertung zurückgenommen wird, wandert die Session wieder in Offen." "Wenn eine Bewertung zurückgenommen wird, wandert die Session wieder in Offen."
] ]
@@ -318,7 +318,7 @@ export const helpSections: HelpSection[] = [
details: [ details: [
"Der Monatsverlauf zeigt jeden Tag des Monats. Tage ohne Sessions bleiben leer, damit die Monatsstruktur sichtbar bleibt.", "Der Monatsverlauf zeigt jeden Tag des Monats. Tage ohne Sessions bleiben leer, damit die Monatsstruktur sichtbar bleibt.",
"Die blauen Balken stehen für abgerechnete Session-Zeit.", "Die blauen Balken stehen für abgerechnete Session-Zeit.",
"Die grüne Linie steht für Teamspace-Zeit, sofern für den Tag Werte gepflegt wurden.", "Die fuchsiafarbene Linie steht für Teamspace-Zeit, sofern für den Tag Werte gepflegt wurden.",
"Der Bewertungsstand weist Abgerechnet, Nicht abrechenbar und Offen nur als getrennte Werte aus.", "Der Bewertungsstand weist Abgerechnet, Nicht abrechenbar und Offen nur als getrennte Werte aus.",
"Der Teamspace-Abgleich zeigt die Gesamtdifferenz zwischen CRM-Werten und abgerechneter Session-Zeit.", "Der Teamspace-Abgleich zeigt die Gesamtdifferenz zwischen CRM-Werten und abgerechneter Session-Zeit.",
"Manuell und Fix trennen selbst erfasste abrechenbare Sessions von automatisch erzeugten abrechenbaren Sessions aus fixen Abrechnungen." "Manuell und Fix trennen selbst erfasste abrechenbare Sessions von automatisch erzeugten abrechenbaren Sessions aus fixen Abrechnungen."
@@ -338,7 +338,7 @@ export const helpSections: HelpSection[] = [
"Sessions zeigt die Anzahl der einzelnen Arbeitseinträge.", "Sessions zeigt die Anzahl der einzelnen Arbeitseinträge.",
"Zeiten vergleicht abgerechnete Session-Zeit mit Teamspace-Zeit.", "Zeiten vergleicht abgerechnete Session-Zeit mit Teamspace-Zeit.",
"Die Differenz wird als Teamspace minus abgerechnete Zeit berechnet.", "Die Differenz wird als Teamspace minus abgerechnete Zeit berechnet.",
"Nicht abrechenbare Zeit wird separat ausgewiesen und nicht in die Differenz gerechnet.", "Nicht abrechenbare Sessions werden separat ausgewiesen und nicht in die Differenz gerechnet.",
"Der Anteil am Monat zeigt, wie groß die abgerechnete Zeit dieser Organisation im Verhältnis zur größten Organisation im Monat ist.", "Der Anteil am Monat zeigt, wie groß die abgerechnete Zeit dieser Organisation im Verhältnis zur größten Organisation im Monat ist.",
"Bei vielen Organisationen wird die Liste paginiert, damit die Seite kompakt bleibt." "Bei vielen Organisationen wird die Liste paginiert, damit die Seite kompakt bleibt."
] ]
@@ -372,7 +372,7 @@ export const helpSections: HelpSection[] = [
details: [ details: [
"Ticket-Hotspots helfen, die größten Aufwandstreiber im Monat schnell zu erkennen.", "Ticket-Hotspots helfen, die größten Aufwandstreiber im Monat schnell zu erkennen.",
"Die Liste zeigt die wichtigsten Tickets nach abgerechneter Zeit.", "Die Liste zeigt die wichtigsten Tickets nach abgerechneter Zeit.",
"Pro Ticket siehst du Organisation, Sessionanzahl, aktive abrechenbare Tage, abgerechnete Zeit, nicht abrechenbare Zeit, Teamspace-Zeit und Differenz.", "Pro Ticket siehst du Organisation, Sessionanzahl, aktive abrechenbare Tage, abgerechnete Zeit, nicht abrechenbare Sessions, Teamspace-Zeit und Differenz.",
"Offene Tickets werden mit einem Warnstatus angezeigt, damit Bewertungen nachgezogen werden können.", "Offene Tickets werden mit einem Warnstatus angezeigt, damit Bewertungen nachgezogen werden können.",
"Über Öffnen gelangst du direkt in die Ticketdetailansicht des Monats." "Über Öffnen gelangst du direkt in die Ticketdetailansicht des Monats."
] ]
+9 -12
View File
@@ -31,11 +31,11 @@ type StatisticsPageProps = {
}; };
const trackedTextClass = "text-sky-700 dark:text-sky-300"; const trackedTextClass = "text-sky-700 dark:text-sky-300";
const teamspaceTextClass = "text-emerald-700 dark:text-emerald-300"; const teamspaceTextClass = "text-fuchsia-700 dark:text-fuchsia-300";
const trackedFillClass = "fill-sky-500/70 dark:fill-sky-400/65"; const trackedFillClass = "fill-sky-500/70 dark:fill-sky-400/65";
const teamspaceStrokeClass = "text-emerald-600 dark:text-emerald-300"; const teamspaceStrokeClass = "text-fuchsia-600 dark:text-fuchsia-300";
const trackedDotClass = "bg-sky-500 dark:bg-sky-400"; const trackedDotClass = "bg-sky-500 dark:bg-sky-400";
const teamspaceDotClass = "bg-emerald-500 dark:bg-emerald-400"; const teamspaceDotClass = "bg-fuchsia-500 dark:bg-fuchsia-400";
const nonBillableTextClass = "text-indigo-700 dark:text-indigo-300"; const nonBillableTextClass = "text-indigo-700 dark:text-indigo-300";
const openTextClass = "text-amber-700 dark:text-amber-300"; const openTextClass = "text-amber-700 dark:text-amber-300";
const organizationPageSize = 8; const organizationPageSize = 8;
@@ -195,7 +195,6 @@ function buildMonthSeries(month: string, buckets: StatisticsDailyBucket[]) {
sessions: bucket?.sessions ?? 0, sessions: bucket?.sessions ?? 0,
totalMinutes: bucket?.total_minutes ?? 0, totalMinutes: bucket?.total_minutes ?? 0,
billedMinutes: bucket?.billed_minutes ?? 0, billedMinutes: bucket?.billed_minutes ?? 0,
nonBillableMinutes: bucket?.non_billable_minutes ?? 0,
openMinutes: bucket?.open_minutes ?? 0, openMinutes: bucket?.open_minutes ?? 0,
billedSessions: bucket?.billed_sessions ?? 0, billedSessions: bucket?.billed_sessions ?? 0,
nonBillableSessions: bucket?.non_billable_sessions ?? 0, nonBillableSessions: bucket?.non_billable_sessions ?? 0,
@@ -345,7 +344,7 @@ function TicketHotspot({ ticket, month, onNavigate }: { ticket: StatisticsTicket
<div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs"> <div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs">
<span className={trackedTextClass}>Abgerechnet {formatMinutes(ticket.total_minutes)}</span> <span className={trackedTextClass}>Abgerechnet {formatMinutes(ticket.total_minutes)}</span>
<span className={teamspaceTextClass}>Teamspace {formatMinutes(ticket.crm_billed_minutes)}</span> <span className={teamspaceTextClass}>Teamspace {formatMinutes(ticket.crm_billed_minutes)}</span>
<span className={nonBillableTextClass}>Nicht {formatMinutes(ticket.non_billable_minutes)}</span> <span className={nonBillableTextClass}>Nicht {ticket.non_billable_sessions} Session(s)</span>
<span className="text-muted-foreground">{ticket.billed_sessions} abgerechnet</span> <span className="text-muted-foreground">{ticket.billed_sessions} abgerechnet</span>
<span>{ticket.active_days} Tag(e)</span> <span>{ticket.active_days} Tag(e)</span>
<span className={deltaTextClass(ticket.crm_delta_minutes)}>{formatTeamspaceDelta(ticket.crm_delta_minutes)}</span> <span className={deltaTextClass(ticket.crm_delta_minutes)}>{formatTeamspaceDelta(ticket.crm_delta_minutes)}</span>
@@ -423,7 +422,6 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
const billedSessions = totals?.billedSessions ?? 0; const billedSessions = totals?.billedSessions ?? 0;
const billedMinutes = totals?.billedMinutes ?? 0; const billedMinutes = totals?.billedMinutes ?? 0;
const nonBillableSessions = totals?.nonBillableSessions ?? 0; const nonBillableSessions = totals?.nonBillableSessions ?? 0;
const nonBillableMinutes = totals?.nonBillableMinutes ?? 0;
const openSessions = totals?.openSessions ?? 0; const openSessions = totals?.openSessions ?? 0;
const openMinutes = totals?.openMinutes ?? 0; const openMinutes = totals?.openMinutes ?? 0;
const crmCoverage = percentage(totals?.crmBilledMinutes ?? 0, totals?.minutes ?? 0); const crmCoverage = percentage(totals?.crmBilledMinutes ?? 0, totals?.minutes ?? 0);
@@ -492,10 +490,9 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
/> />
<MetricCard <MetricCard
label="Nicht abrechenbar" label="Nicht abrechenbar"
value={formatMinutes(nonBillableMinutes)} value={`${nonBillableSessions} Session(s)`}
detail={ detail={
<div className="space-y-0.5"> <div className="space-y-0.5">
<div>{nonBillableSessions} Session(s)</div>
<div>wird nicht kalkuliert</div> <div>wird nicht kalkuliert</div>
</div> </div>
} }
@@ -546,7 +543,7 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
</div> </div>
<div className="grid gap-2 text-xs sm:grid-cols-3 xl:grid-cols-1 2xl:grid-cols-3"> <div className="grid gap-2 text-xs sm:grid-cols-3 xl:grid-cols-1 2xl:grid-cols-3">
<div className={trackedTextClass}>Abgerechnet: {billedSessions} Session(s), {formatMinutes(billedMinutes)}</div> <div className={trackedTextClass}>Abgerechnet: {billedSessions} Session(s), {formatMinutes(billedMinutes)}</div>
<div className={nonBillableTextClass}>Nicht: {nonBillableSessions} Session(s), {formatMinutes(nonBillableMinutes)}</div> <div className={nonBillableTextClass}>Nicht: {nonBillableSessions} Session(s)</div>
<div className={openTextClass}>Offen: {openSessions} Session(s), {formatMinutes(openMinutes)}</div> <div className={openTextClass}>Offen: {openSessions} Session(s), {formatMinutes(openMinutes)}</div>
</div> </div>
</div> </div>
@@ -590,7 +587,7 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
<p className="flex flex-wrap gap-x-3 gap-y-1 text-xs"> <p className="flex flex-wrap gap-x-3 gap-y-1 text-xs">
<span className={trackedTextClass}>Abgerechnet {formatMinutes(topOrganization.total_minutes)}</span> <span className={trackedTextClass}>Abgerechnet {formatMinutes(topOrganization.total_minutes)}</span>
<span className={teamspaceTextClass}>Teamspace {formatMinutes(topOrganization.crm_billed_minutes)}</span> <span className={teamspaceTextClass}>Teamspace {formatMinutes(topOrganization.crm_billed_minutes)}</span>
<span className={nonBillableTextClass}>Nicht {formatMinutes(topOrganization.non_billable_minutes)}</span> <span className={nonBillableTextClass}>Nicht {topOrganization.non_billable_sessions} Session(s)</span>
</p> </p>
</div> </div>
) : null} ) : null}
@@ -650,7 +647,7 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
<div className="flex flex-wrap gap-x-3 gap-y-1 text-xs"> <div className="flex flex-wrap gap-x-3 gap-y-1 text-xs">
<span className={trackedTextClass}>Abgerechnet {formatMinutes(organization.total_minutes)}</span> <span className={trackedTextClass}>Abgerechnet {formatMinutes(organization.total_minutes)}</span>
<span className={teamspaceTextClass}>Teamspace {formatMinutes(organization.crm_billed_minutes)}</span> <span className={teamspaceTextClass}>Teamspace {formatMinutes(organization.crm_billed_minutes)}</span>
<span className={nonBillableTextClass}>Nicht {formatMinutes(organization.non_billable_minutes)}</span> <span className={nonBillableTextClass}>Nicht {organization.non_billable_sessions} Session(s)</span>
<span className="text-muted-foreground">{percentage(organization.total_minutes, totals?.minutes ?? 0)}% vom Monat</span> <span className="text-muted-foreground">{percentage(organization.total_minutes, totals?.minutes ?? 0)}% vom Monat</span>
<span className={deltaTextClass(organization.crm_delta_minutes)}>{formatTeamspaceDelta(organization.crm_delta_minutes)}</span> <span className={deltaTextClass(organization.crm_delta_minutes)}>{formatTeamspaceDelta(organization.crm_delta_minutes)}</span>
</div> </div>
@@ -723,7 +720,7 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
</div> </div>
<div className="grid grid-cols-3 gap-2 text-xs text-muted-foreground"> <div className="grid grid-cols-3 gap-2 text-xs text-muted-foreground">
<span>Abr. {group.billed_sessions} / {formatMinutes(group.billed_minutes)}</span> <span>Abr. {group.billed_sessions} / {formatMinutes(group.billed_minutes)}</span>
<span>Nicht {group.non_billable_sessions} / {formatMinutes(group.non_billable_minutes)}</span> <span>Nicht {group.non_billable_sessions}</span>
<span>Offen {group.open_sessions} / {formatMinutes(group.open_minutes)}</span> <span>Offen {group.open_sessions} / {formatMinutes(group.open_minutes)}</span>
</div> </div>
</div> </div>