Add persistent workday records with localStorage integration and reset functionality

This commit is contained in:
T.Landgraf committed 2026-08-14 08:41:42 +02:00
1 parent b541fcd11b
commit 21ed1e7744
1 file changed
+77 -29
+77 -29
View File
@@ -4,6 +4,29 @@ import * as React from 'react';
type Workday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday'; type Workday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday';
interface DayRecord {
start: string;
end: string;
breakMins: number;
workedHrs: number;
overtimeMinutes: number;
}
type Records = Partial<Record<Workday, DayRecord>>;
const STORAGE_KEY = 'workTrackerRecords';
const DEFAULT_RECORD: DayRecord = { start: '', end: '', breakMins: 0, workedHrs: 0, overtimeMinutes: 0 };
const loadRecords = (): Records => {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : {};
} catch {
return {};
}
};
const toMinutes = (time: string): number | null => { const toMinutes = (time: string): number | null => {
const match = /^(\d{2}):(\d{2})$/.exec(time); const match = /^(\d{2}):(\d{2})$/.exec(time);
if (!match) return null; if (!match) return null;
@@ -18,29 +41,48 @@ const fromMinutes = (mins: number): string => {
function App() { function App() {
const [selectedDay, setSelectedDay] = React.useState<Workday>('Monday'); const [selectedDay, setSelectedDay] = React.useState<Workday>('Monday');
const [workStart, setWorkStart] = React.useState<string>(''); const [records, setRecords] = React.useState<Records>(loadRecords);
const [workEnd, setWorkEnd] = React.useState<string>('');
const [breakMins, setBreakMins] = React.useState<number>(0); const record = records[selectedDay] ?? DEFAULT_RECORD;
const [workedHrs, setWorkedHrs] = React.useState<number>(0);
const [overtimeMinutes, setOvertimeMinutes] = React.useState<number>(0); React.useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(records));
}, [records]);
const updateRecord = (patch: Partial<DayRecord>) => {
setRecords((prev) => ({
...prev,
[selectedDay]: { ...(prev[selectedDay] ?? DEFAULT_RECORD), ...patch },
}));
};
const getHourDiff = () => { const getHourDiff = () => {
const start = toMinutes(workStart); const start = toMinutes(record.start);
const end = toMinutes(workEnd); const end = toMinutes(record.end);
if (start === null || end === null) return 0; if (start === null || end === null) return 0;
return (end - start) / 60 - breakMins / 60; return (end - start) / 60 - record.breakMins / 60;
}
const getWeeklyBalance = () => {
const days: Workday[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday'];
return days.reduce((sum, day) => sum + (records[day]?.overtimeMinutes ?? 0), 0);
} }
const getWorkEndTime = () => { const getWorkEndTime = () => {
const start = toMinutes(workStart); const start = toMinutes(record.start);
if (start === null) return '—'; if (start === null) return '—';
return fromMinutes(start + 8 * 60 + breakMins); const balance = selectedDay === 'Friday' ? getWeeklyBalance() : 0;
return fromMinutes(start + 8 * 60 - balance + record.breakMins);
} }
const handleSave = () => { const handleSave = () => {
const hours = getHourDiff(); const hours = getHourDiff();
setWorkedHrs(hours); updateRecord({ workedHrs: hours, overtimeMinutes: Math.round((hours - 8) * 60) });
setOvertimeMinutes(Math.round((hours - 8) * 60)); }
const handleReset = () => {
localStorage.removeItem(STORAGE_KEY);
setRecords({});
} }
return ( return (
@@ -48,48 +90,54 @@ function App() {
<Stack> <Stack>
<SegmentedControl <SegmentedControl
data={['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']} data={['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']}
onChange={(value) => { onChange={(value) => setSelectedDay(value as Workday)}
setWorkStart('');
setWorkEnd('');
setSelectedDay(value as Workday);
}}
className={"mt-2"} className={"mt-2"}
/> />
<TimePicker <TimePicker
value={workStart} value={record.start}
label={"Arbeitsbeginn"} label={"Arbeitsbeginn"}
onChange={setWorkStart} onChange={(value) => updateRecord({ start: value })}
format="24h" format="24h"
clearable clearable
/> />
<TimePicker <TimePicker
value={workEnd} value={record.end}
label={"Arbeitsende"} label={"Arbeitsende"}
onChange={setWorkEnd} onChange={(value) => updateRecord({ end: value })}
format="24h" format="24h"
clearable clearable
/> />
<NumberInput <NumberInput
label={"Pause in Minuten"} label={"Pause in Minuten"}
value={breakMins} value={record.breakMins}
onChange={(val) => setBreakMins(Number(val))} onChange={(val) => updateRecord({ breakMins: Number(val) })}
onKeyDown={(event) => {
if (event.key === 'Enter') handleSave();
}}
/> />
<Button onClick={handleSave}> <Button onClick={handleSave}>
Speichern Speichern
</Button> </Button>
<Text>Gearbeitete Stunden heute: {workedHrs.toFixed(2)}</Text> <Text>Gearbeitete Stunden heute: {record.workedHrs.toFixed(2)}</Text>
{selectedDay === 'Friday' && (
<Text c={"green"}>Übertragen aus Montag bis Donnerstag: {getWeeklyBalance()} Minuten</Text>
)}
<Text>Wann du gehen kannst für 8 Stunden: {getWorkEndTime()}</Text> <Text>Wann du gehen kannst für 8 Stunden: {getWorkEndTime()}</Text>
{overtimeMinutes === 0 && ( {record.overtimeMinutes === 0 && (
<Text>Du hast genau 8 Stunden gearbeitet.</Text> <Text>Du hast genau 8 Stunden gearbeitet.</Text>
)} )}
{overtimeMinutes > 0 && ( {record.overtimeMinutes > 0 && (
<Text c="yellow">Überstunden: {overtimeMinutes} Minuten</Text> <Text c="yellow">Überstunden: {record.overtimeMinutes} Minuten</Text>
)} )}
{overtimeMinutes < 0 && ( {record.overtimeMinutes < 0 && (
<Text>Fehlende Minuten für 8 Stunden: {-overtimeMinutes} Minuten</Text> <Text c={"red"}>Fehlende Minuten für 8 Stunden: {-record.overtimeMinutes} Minuten</Text>
)} )}
<Button color="red" variant="outline" onClick={handleReset}>
Reset saved records
</Button>
</Stack> </Stack>
</Center> </Center>
); );