136 lines
4.7 KiB
TypeScript
136 lines
4.7 KiB
TypeScript
// presentation/components/timeTracker/Timer/Timer.tsx
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useTimeTracking } from '../../../hooks/useTimeTracking';
|
|
import { pipe, Option, fromNullable } from '../../../../utils/fp/option';
|
|
import { Button } from '../../common/Button';
|
|
import { formatDuration } from '../../../../utils/date/dateUtils';
|
|
|
|
interface TimerProps {
|
|
onComplete?: (duration: number) => void;
|
|
}
|
|
|
|
export const Timer: React.FC<TimerProps> = ({ onComplete }) => {
|
|
const [isRunning, setIsRunning] = useState(false);
|
|
const [startTime, setStartTime] = useState<Option<Date>>(Option.none());
|
|
const [elapsedTime, setElapsedTime] = useState(0);
|
|
const [selectedProject, setSelectedProject] = useState<Option<string>>(Option.none());
|
|
const [selectedActivity, setSelectedActivity] = useState<Option<string>>(Option.none());
|
|
|
|
const { lastTimeEntry, projects, activities } = useTimeTracking();
|
|
|
|
// Beim ersten Rendering die letzte Zeitbuchung laden
|
|
useEffect(() => {
|
|
pipe(
|
|
fromNullable(lastTimeEntry),
|
|
Option.map(entry => {
|
|
setSelectedProject(Option.some(entry.projectId));
|
|
setSelectedActivity(Option.some(entry.activityId));
|
|
})
|
|
);
|
|
}, [lastTimeEntry]);
|
|
|
|
// Timer-Logik
|
|
useEffect(() => {
|
|
let interval: NodeJS.Timeout | null = null;
|
|
|
|
if (isRunning) {
|
|
interval = setInterval(() => {
|
|
const now = new Date();
|
|
pipe(
|
|
startTime,
|
|
Option.map(start => {
|
|
const diff = now.getTime() - start.getTime();
|
|
setElapsedTime(Math.floor(diff / 1000));
|
|
})
|
|
);
|
|
}, 1000);
|
|
} else if (interval) {
|
|
clearInterval(interval);
|
|
}
|
|
|
|
return () => {
|
|
if (interval) clearInterval(interval);
|
|
};
|
|
}, [isRunning, startTime]);
|
|
|
|
// Timer starten
|
|
const handleStart = () => {
|
|
setStartTime(Option.some(new Date()));
|
|
setIsRunning(true);
|
|
};
|
|
|
|
// Timer stoppen
|
|
const handleStop = () => {
|
|
setIsRunning(false);
|
|
|
|
// Prüfen, ob Projekt und Aktivität ausgewählt wurden
|
|
const projectId = pipe(
|
|
selectedProject,
|
|
Option.getOrElse(() => '')
|
|
);
|
|
|
|
const activityId = pipe(
|
|
selectedActivity,
|
|
Option.getOrElse(() => '')
|
|
);
|
|
|
|
if (projectId && activityId && onComplete) {
|
|
onComplete(elapsedTime);
|
|
}
|
|
|
|
// Timer zurücksetzen
|
|
setElapsedTime(0);
|
|
setStartTime(Option.none());
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-md p-4">
|
|
<div className="text-4xl text-center font-mono mb-4">
|
|
{formatDuration(elapsedTime)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Projekt
|
|
</label>
|
|
<select
|
|
className="w-full border border-gray-300 rounded-md px-3 py-2"
|
|
value={pipe(selectedProject, Option.getOrElse(() => ''))}
|
|
onChange={(e) => setSelectedProject(Option.some(e.target.value))}
|
|
disabled={isRunning}
|
|
>
|
|
<option value="">Projekt auswählen</option>
|
|
{projects.map((project) => (
|
|
<option key={project.id} value={project.id}>
|
|
{project.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Tätigkeit
|
|
</label>
|
|
<select
|
|
className="w-full border border-gray-300 rounded-md px-3 py-2"
|
|
value={pipe(selectedActivity, Option.getOrElse(() => ''))}
|
|
onChange={(e) => setSelectedActivity(Option.some(e.target.value))}
|
|
disabled={isRunning}
|
|
>
|
|
<option value="">Tätigkeit auswählen</option>
|
|
{activities.map((activity) => (
|
|
<option key={activity.id} value={activity.id}>
|
|
{activity.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-center">
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |