Mobile Dev
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ActivityIndicator, Pressable, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
|
||||
import { router, useLocalSearchParams } from 'expo-router';
|
||||
|
||||
import {
|
||||
createStaffTimeEvent,
|
||||
@@ -47,6 +48,7 @@ function getTypeLabel(type: string): string {
|
||||
|
||||
export default function TimeTrackingScreen() {
|
||||
const { token, user } = useAuth();
|
||||
const params = useLocalSearchParams<{ action?: string | string[] }>();
|
||||
|
||||
const [entries, setEntries] = useState<StaffTimeSpan[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -55,8 +57,13 @@ export default function TimeTrackingScreen() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const currentUserId = useMemo(() => (user?.id ? String(user.id) : null), [user]);
|
||||
const handledActionRef = useRef<string | null>(null);
|
||||
|
||||
const active = useMemo(() => entries.find((entry) => !entry.stopped_at) || null, [entries]);
|
||||
const incomingAction = useMemo(() => {
|
||||
const raw = Array.isArray(params.action) ? params.action[0] : params.action;
|
||||
return String(raw || '').toLowerCase();
|
||||
}, [params.action]);
|
||||
|
||||
const load = useCallback(
|
||||
async (showSpinner = true) => {
|
||||
@@ -87,7 +94,7 @@ export default function TimeTrackingScreen() {
|
||||
await load(false);
|
||||
}
|
||||
|
||||
async function onStart() {
|
||||
const onStart = useCallback(async () => {
|
||||
if (!token || !currentUserId) return;
|
||||
setActionLoading(true);
|
||||
setError(null);
|
||||
@@ -105,9 +112,9 @@ export default function TimeTrackingScreen() {
|
||||
} finally {
|
||||
setActionLoading(false);
|
||||
}
|
||||
}
|
||||
}, [currentUserId, token, load]);
|
||||
|
||||
async function onStop() {
|
||||
const onStop = useCallback(async () => {
|
||||
if (!token || !currentUserId || !active) return;
|
||||
setActionLoading(true);
|
||||
setError(null);
|
||||
@@ -124,7 +131,7 @@ export default function TimeTrackingScreen() {
|
||||
} finally {
|
||||
setActionLoading(false);
|
||||
}
|
||||
}
|
||||
}, [active, currentUserId, token, load]);
|
||||
|
||||
async function onSubmit(entry: StaffTimeSpan) {
|
||||
if (!token || !entry.eventIds?.length) return;
|
||||
@@ -141,6 +148,57 @@ export default function TimeTrackingScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmitAll = useCallback(async () => {
|
||||
if (!token) return;
|
||||
|
||||
const submitCandidates = entries.filter(
|
||||
(entry) => (entry.state === 'draft' || entry.state === 'factual') && !!entry.stopped_at && !!entry.eventIds?.length
|
||||
);
|
||||
|
||||
if (submitCandidates.length === 0) return;
|
||||
|
||||
setActionLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
for (const entry of submitCandidates) {
|
||||
await submitStaffTime(token, entry.eventIds);
|
||||
}
|
||||
await load(false);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Einreichen fehlgeschlagen.');
|
||||
} finally {
|
||||
setActionLoading(false);
|
||||
}
|
||||
}, [entries, load, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token || !currentUserId) return;
|
||||
if (!incomingAction) return;
|
||||
if (handledActionRef.current === incomingAction) return;
|
||||
|
||||
if (incomingAction === 'start' && !active) {
|
||||
handledActionRef.current = incomingAction;
|
||||
void onStart().finally(() => router.replace('/(tabs)/time'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (incomingAction === 'stop' && active) {
|
||||
handledActionRef.current = incomingAction;
|
||||
void onStop().finally(() => router.replace('/(tabs)/time'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (incomingAction === 'submit') {
|
||||
handledActionRef.current = incomingAction;
|
||||
void onSubmitAll().finally(() => router.replace('/(tabs)/time'));
|
||||
return;
|
||||
}
|
||||
|
||||
handledActionRef.current = incomingAction;
|
||||
void router.replace('/(tabs)/time');
|
||||
}, [active, currentUserId, incomingAction, onStart, onStop, onSubmitAll, token]);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.container}
|
||||
@@ -148,7 +206,7 @@ export default function TimeTrackingScreen() {
|
||||
<View style={styles.statusCard}>
|
||||
<Text style={styles.statusLabel}>Aktive Zeit</Text>
|
||||
<Text style={styles.statusValue}>
|
||||
{active ? `Laeuft seit ${formatDateTime(active.started_at)}` : 'Keine aktive Zeit'}
|
||||
{active ? `Läuft seit ${formatDateTime(active.started_at)}` : 'Keine aktive Zeit'}
|
||||
</Text>
|
||||
|
||||
<View style={styles.statusActions}>
|
||||
@@ -179,7 +237,7 @@ export default function TimeTrackingScreen() {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{!loading && entries.length === 0 ? <Text style={styles.empty}>Keine Zeiteintraege vorhanden.</Text> : null}
|
||||
{!loading && entries.length === 0 ? <Text style={styles.empty}>Keine Zeiteinträge vorhanden.</Text> : null}
|
||||
|
||||
{!loading &&
|
||||
entries.map((entry) => {
|
||||
@@ -194,7 +252,7 @@ export default function TimeTrackingScreen() {
|
||||
|
||||
<Text style={styles.entryTime}>Start: {formatDateTime(entry.started_at)}</Text>
|
||||
<Text style={styles.entryTime}>
|
||||
Ende: {entry.stopped_at ? formatDateTime(entry.stopped_at) : 'laeuft...'}
|
||||
Ende: {entry.stopped_at ? formatDateTime(entry.stopped_at) : 'läuft...'}
|
||||
</Text>
|
||||
<Text style={styles.entryTime}>Dauer: {formatDuration(entry.duration_minutes)}</Text>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user