feat(hours): open the hours invoice in the editor to add items

After 'Create draft invoice' mints the single scheduled invoice from a
per-event customer's unbilled hours, navigate to the bill editor so the
admin can add other line items before it ships (invoice is already
status='scheduled' + editable). Updated the per-event hint copy.
This commit is contained in:
Luca
2026-06-03 14:20:49 +02:00
parent 5d7b545bf7
commit 02742b3163
3 changed files with 9 additions and 5 deletions
@@ -15,7 +15,7 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { Clock, AlertTriangle } from 'lucide-react'; import { Clock, AlertTriangle } from 'lucide-react';
import { Button, Card, LocalizedDateInput, TimeField } from '../common'; import { Button, Card, LocalizedDateInput, TimeField } from '../common';
@@ -46,6 +46,7 @@ export const HoursSection: React.FC<HoursSectionProps> = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const qc = useQueryClient(); const qc = useQueryClient();
const navigate = useNavigate();
const { format: fmtDate, formatTime: fmtTime } = useLocalizedDate(); const { format: fmtDate, formatTime: fmtTime } = useLocalizedDate();
const [entryDate, setEntryDate] = useState(() => new Date().toISOString().slice(0, 10)); const [entryDate, setEntryDate] = useState(() => new Date().toISOString().slice(0, 10));
const [startTime, setStartTime] = useState('09:00'); const [startTime, setStartTime] = useState('09:00');
@@ -154,10 +155,13 @@ export const HoursSection: React.FC<HoursSectionProps> = ({
const billMutation = useMutation({ const billMutation = useMutation({
mutationFn: () => customerAdminService.billUnbilledHourEntries(customerId), mutationFn: () => customerAdminService.billUnbilledHourEntries(customerId),
onSuccess: () => { onSuccess: ({ invoiceId }) => {
qc.invalidateQueries({ queryKey: ['admin-customer-hour-entries', customerId] }); qc.invalidateQueries({ queryKey: ['admin-customer-hour-entries', customerId] });
qc.invalidateQueries({ queryKey: ['admin-customer', customerId] }); qc.invalidateQueries({ queryKey: ['admin-customer', customerId] });
toast.success(t('customers.hours.toast.billed', 'Hours billed')); toast.success(t('customers.hours.toast.billed', 'Hours billed'));
// Open the new scheduled invoice so the admin can add other line
// items in addition to the hours before it ships.
if (invoiceId) navigate(`/admin/clients/bills/${invoiceId}/edit`);
}, },
onError: (err: any) => { onError: (err: any) => {
toast.error(err?.response?.data?.error || 'Failed to bill hours'); toast.error(err?.response?.data?.error || 'Failed to bill hours');
@@ -202,7 +206,7 @@ export const HoursSection: React.FC<HoursSectionProps> = ({
? t('customers.hours.monthlyHint', ? t('customers.hours.monthlyHint',
'Entries auto-append to the current monthly draft. Edit / delete remains possible until the scheduler arms the draft for send.') 'Entries auto-append to the current monthly draft. Edit / delete remains possible until the scheduler arms the draft for send.')
: t('customers.hours.perEventHint', : t('customers.hours.perEventHint',
'Logged entries stay unbilled until you click "Create draft invoice" — a standalone draft invoice is generated with one line per entry, ready for you to review before sending.')} 'Logged entries stay unbilled until you click "Create draft invoice" — one scheduled invoice is generated with a line per entry and opened in the editor, so you can add other items before it ships.')}
</p> </p>
{/* Rate summary — hidden in compact mode (history-only on the {/* Rate summary — hidden in compact mode (history-only on the
+1 -1
View File
@@ -3107,7 +3107,7 @@
"hours": { "hours": {
"section": "Stunden", "section": "Stunden",
"monthlyHint": "Einträge werden automatisch dem aktuellen monatlichen Entwurf angehängt. Bearbeiten/Löschen möglich, bis der Scheduler den Versand auslöst.", "monthlyHint": "Einträge werden automatisch dem aktuellen monatlichen Entwurf angehängt. Bearbeiten/Löschen möglich, bis der Scheduler den Versand auslöst.",
"perEventHint": "Erfasste Einträge bleiben unverrechnet, bis Sie auf „Rechnungsentwurf erstellen“ klicken — dann wird ein eigenständiger Rechnungsentwurf mit einer Zeile pro Eintrag erzeugt, den Sie vor dem Versand prüfen können.", "perEventHint": "Erfasste Einträge bleiben unverrechnet, bis Sie auf „Rechnungsentwurf erstellen“ klicken — dann wird eine geplante Rechnung mit einer Zeile pro Eintrag erzeugt und im Editor geöffnet, sodass Sie vor dem Versand weitere Positionen hinzufügen können.",
"form": { "form": {
"title": "Neuen Eintrag erfassen", "title": "Neuen Eintrag erfassen",
"date": "Datum", "date": "Datum",
+1 -1
View File
@@ -3107,7 +3107,7 @@
"hours": { "hours": {
"section": "Hours", "section": "Hours",
"monthlyHint": "Entries auto-append to the current monthly draft. Edit / delete remains possible until the scheduler arms the draft for send.", "monthlyHint": "Entries auto-append to the current monthly draft. Edit / delete remains possible until the scheduler arms the draft for send.",
"perEventHint": "Logged entries stay unbilled until you click \"Create draft invoice\" — a standalone draft invoice is generated with one line per entry, ready for you to review before sending.", "perEventHint": "Logged entries stay unbilled until you click \"Create draft invoice\" — one scheduled invoice is generated with a line per entry and opened in the editor, so you can add other items before it ships.",
"form": { "form": {
"title": "Log new entry", "title": "Log new entry",
"date": "Date", "date": "Date",