Add persistent workday records with localStorage integration and reset functionality
This commit is contained in:
1 parent
b541fcd11b
commit
21ed1e7744
1 file changed
+77
-29
+77
-29
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user