feat(reports): nicht abrechenbare Projekte ausblenden
Im Zeitreport stehen interne Projekte und Abwesenheiten zwischen den Kundenzahlen: 2025 sind das 19.451 von 45.762 Stunden, und die Auslastung zeigt 146 Prozent, weil Urlaub und Krankheit mitzaehlen. Ein Haken neben der Billable-Aufteilung blendet Projekte vom Typ "nicht abrechenbar" aus. Er wirkt auf den ganzen Report - Summen, Auslastung, alle vier Gruppierungen - und auf die Exporte, die denselben Report bauen; im PDF-Kopf steht dann "Nicht abrechenbare Projekte: ausgeblendet". Gefiltert wird in loadEntries, damit Kunden-, Projekt- und Taskliste dieselbe Grundmenge sehen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2710baa615
commit
80e46910fd
@@ -75,6 +75,9 @@ const TimeReport: React.FC = () => {
|
||||
const [customerId, setCustomerId] = useState<number | ''>('');
|
||||
const [team, setTeam] = useState<number | ''>('');
|
||||
const [includeFixed, setIncludeFixed] = useState(false);
|
||||
// Blendet interne Projekte und Abwesenheiten aus - der Report zeigt dann
|
||||
// nur das Kundengeschaeft, inklusive Auslastung und Export.
|
||||
const [hideNonBillable, setHideNonBillable] = useState(false);
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
const [leads, setLeads] = useState<DirectoryUser[]>([]);
|
||||
// Klick auf einen Mitarbeiter oeffnet seine Buchungen des Zeitraums als Liste.
|
||||
@@ -90,8 +93,16 @@ const TimeReport: React.FC = () => {
|
||||
|
||||
const range = periodRange(period, anchor, custom);
|
||||
const report = useLoader(
|
||||
() => reportsApi.time({ from: range.from, to: range.to, customer_id: customerId || null, team: team || null, include_fixed: includeFixed }),
|
||||
[range.from, range.to, customerId, team, includeFixed]
|
||||
() =>
|
||||
reportsApi.time({
|
||||
from: range.from,
|
||||
to: range.to,
|
||||
customer_id: customerId || null,
|
||||
team: team || null,
|
||||
include_fixed: includeFixed,
|
||||
hide_non_billable: hideNonBillable,
|
||||
}),
|
||||
[range.from, range.to, customerId, team, includeFixed, hideNonBillable]
|
||||
);
|
||||
|
||||
const shift = (step: number) => {
|
||||
@@ -101,7 +112,7 @@ const TimeReport: React.FC = () => {
|
||||
if (period === 'year') setAnchor(`${Number(anchor.slice(0, 4)) + step}-01-01`);
|
||||
};
|
||||
|
||||
const exportParams = { from: range.from, to: range.to, customer_id: customerId || undefined, team: team || undefined, include_fixed: includeFixed ? 1 : undefined };
|
||||
const exportParams = { from: range.from, to: range.to, customer_id: customerId || undefined, team: team || undefined, include_fixed: includeFixed ? 1 : undefined, hide_non_billable: hideNonBillable ? 1 : undefined };
|
||||
const groupName = { clients: 'Kunden', projects: 'Projekte', tasks: 'Tasks', users: 'Mitarbeiter' }[tab];
|
||||
const exportOptions = useMemo(
|
||||
() => [
|
||||
@@ -113,7 +124,7 @@ const TimeReport: React.FC = () => {
|
||||
{ label: 'CSV – alle Buchungen', path: `/exports/report${qs({ ...exportParams, group: 'detail', format: 'csv' })}`, filename: 'Zeitreport-Detail.csv' },
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[tab, range.from, range.to, customerId, team, includeFixed]
|
||||
[tab, range.from, range.to, customerId, team, includeFixed, hideNonBillable]
|
||||
);
|
||||
|
||||
const data = report.data;
|
||||
@@ -197,6 +208,15 @@ const TimeReport: React.FC = () => {
|
||||
</span>
|
||||
<b className="tabular text-ink-900">{formatHours(s!.non_billable_minutes)}</b>
|
||||
</p>
|
||||
<label className="flex cursor-pointer items-center gap-2 pt-1 text-xs font-semibold text-ink-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hideNonBillable}
|
||||
onChange={(e) => setHideNonBillable(e.target.checked)}
|
||||
className="h-4 w-4 accent-[var(--color-brand-500)]"
|
||||
/>
|
||||
Nicht abrechenbare Projekte ausblenden
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
+9
-2
@@ -403,6 +403,7 @@ export interface GroupRow {
|
||||
export interface TimeReport {
|
||||
range: { from: string; to: string };
|
||||
include_fixed: boolean;
|
||||
hide_non_billable: boolean;
|
||||
summary: {
|
||||
minutes: number;
|
||||
billable_minutes: number;
|
||||
@@ -447,8 +448,14 @@ export interface CustomerReport {
|
||||
}
|
||||
|
||||
export const reportsApi = {
|
||||
time: (params: { from: string; to: string; customer_id?: number | null; team?: number | null; include_fixed?: boolean }) =>
|
||||
get<TimeReport>(`/reports/time${qs({ ...params, include_fixed: params.include_fixed ? 1 : undefined })}`),
|
||||
time: (params: { from: string; to: string; customer_id?: number | null; team?: number | null; include_fixed?: boolean; hide_non_billable?: boolean }) =>
|
||||
get<TimeReport>(
|
||||
`/reports/time${qs({
|
||||
...params,
|
||||
include_fixed: params.include_fixed ? 1 : undefined,
|
||||
hide_non_billable: params.hide_non_billable ? 1 : undefined,
|
||||
})}`
|
||||
),
|
||||
project: (id: number) => get<ProjectDetail>(`/reports/project/${id}`),
|
||||
customer: (id: number) => get<CustomerReport>(`/reports/customer/${id}`),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user