feat(buchung): Buchung per Monatskalender auf mehrere Tage kopieren
Das Kopier-Icon (Liste, Wochenansicht) und "Kopieren" im Bearbeiten-Dialog oeffnen jetzt einen kleinen Monatskalender. Dort werden beliebige Tage an- und abgewaehlt, auch ueber Monate hinweg, und in einem Schritt gebucht. - Tage ausserhalb der Projektlaufzeit bzw. ohne Budgettopf sind gesperrt. - Urlaub/Feiertage sind markiert, bereits gebuchte Stunden je Tag sichtbar. - "Werktage" waehlt Mo-Fr des Monats ohne Urlaub und Feiertag. - Buchungen laufen nacheinander; fehlgeschlagene Tage bleiben ausgewaehlt. - Der Vorlage-Modus im Buchungsdialog entfaellt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2b9b7a6ae3
commit
ef709d423c
@@ -13,8 +13,6 @@ interface Props {
|
||||
projects: Project[];
|
||||
date: string;
|
||||
entry?: TimeEntry | null;
|
||||
/** Vorlage fuer eine neue Buchung (Kopie): Projekt, Task, Notiz und Zeit werden uebernommen. */
|
||||
template?: TimeEntry | null;
|
||||
onClose: () => void;
|
||||
onSaved: () => void;
|
||||
onDuplicate?: (entry: TimeEntry) => void;
|
||||
@@ -54,26 +52,19 @@ function firstTask(project: Project | undefined): number | '' {
|
||||
* Buchungsdialog: oben Projekt, darunter Task, darunter Freitext mit der Zeit
|
||||
* rechts daneben. Die Zeit versteht "1:23" und "1,5".
|
||||
*/
|
||||
export const EntryModal: React.FC<Props> = ({ projects, date: initialDate, entry, template, onClose, onSaved, onDuplicate }) => {
|
||||
export const EntryModal: React.FC<Props> = ({ projects, date: initialDate, entry, onClose, onSaved, onDuplicate }) => {
|
||||
const { settings } = useAuth();
|
||||
const { addToast } = useToast();
|
||||
const bookable = useMemo(() => projects.filter((p) => p.is_active || p.id === entry?.project_id), [projects, entry]);
|
||||
/*
|
||||
* Kopie: nur uebernehmen, solange das Projekt noch buchbar ist. Ein
|
||||
* inaktiver Task faellt ueber den Projekt-Effekt unten auf den ersten
|
||||
* aktiven Task zurueck.
|
||||
*/
|
||||
const copyFrom = !entry && template && bookable.some((p) => p.id === template.project_id) ? template : null;
|
||||
const source = entry ?? copyFrom;
|
||||
|
||||
const [projectId, setProjectId] = useState<number | ''>(() => source?.project_id ?? defaultProject(bookable, initialDate, settings));
|
||||
const [projectId, setProjectId] = useState<number | ''>(() => entry?.project_id ?? defaultProject(bookable, initialDate, settings));
|
||||
// Neue Buchung: erster aktiver Task des vorausgewaehlten Projekts.
|
||||
const [taskId, setTaskId] = useState<number | ''>(() =>
|
||||
source ? source.task_id ?? '' : firstTask(bookable.find((p) => p.id === projectId))
|
||||
entry ? entry.task_id ?? '' : firstTask(bookable.find((p) => p.id === projectId))
|
||||
);
|
||||
const [note, setNote] = useState(source?.note ?? '');
|
||||
const [note, setNote] = useState(entry?.note ?? '');
|
||||
const [date, setDate] = useState(entry?.entry_date ?? initialDate);
|
||||
const [timeText, setTimeText] = useState(source ? formatDuration(source.minutes) : '0:00');
|
||||
const [timeText, setTimeText] = useState(entry ? formatDuration(entry.minutes) : '0:00');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
const timeRef = useRef<HTMLInputElement>(null);
|
||||
@@ -105,7 +96,7 @@ export const EntryModal: React.FC<Props> = ({ projects, date: initialDate, entry
|
||||
// Projekt gewechselt: passt der Task nicht mehr, den ersten Task des neuen Projekts nehmen.
|
||||
useEffect(() => {
|
||||
if (taskId && tasks.some((t) => t.id === taskId)) return;
|
||||
if (!taskId && source && source.project_id === projectId) return; // bewusst ohne Task gebucht
|
||||
if (!taskId && entry && entry.project_id === projectId) return; // bewusst ohne Task gebucht
|
||||
setTaskId(firstTask(project ?? undefined));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId]);
|
||||
@@ -182,7 +173,7 @@ export const EntryModal: React.FC<Props> = ({ projects, date: initialDate, entry
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={entry ? 'Buchung bearbeiten' : copyFrom ? 'Buchung kopieren' : 'Zeit buchen'}
|
||||
title={entry ? 'Buchung bearbeiten' : 'Zeit buchen'}
|
||||
subtitle={formatDate(date, 'long')}
|
||||
onClose={onClose}
|
||||
size="md"
|
||||
@@ -194,8 +185,8 @@ export const EntryModal: React.FC<Props> = ({ projects, date: initialDate, entry
|
||||
<span className="hidden sm:inline">Löschen</span>
|
||||
</Button>
|
||||
{onDuplicate && (
|
||||
<Button variant="ghost" icon="copy" onClick={() => onDuplicate(entry)} title="Als neue Buchung kopieren" aria-label="Duplizieren">
|
||||
<span className="hidden sm:inline">Duplizieren</span>
|
||||
<Button variant="ghost" icon="copy" onClick={() => onDuplicate(entry)} title="Auf andere Tage kopieren" aria-label="Kopieren">
|
||||
<span className="hidden sm:inline">Kopieren</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user