Remove save button and implement auto-save functionality
Replaces the manual save button with an automatic save feature that triggers after a short delay and updates the UI to reflect the save status. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 55837015-10e9-4be9-b857-7f5e6be73772 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 5bc80858-66c8-4834-9177-6c352a80c5e3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1cc377db-7ea0-49f2-97ce-c3e87e0228cc/55837015-10e9-4be9-b857-7f5e6be73772/Sh1LoFS Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
7b2caa45fd
commit
b454523241
@ -13,7 +13,6 @@ import {
|
|||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Save,
|
|
||||||
Send,
|
Send,
|
||||||
Plus,
|
Plus,
|
||||||
Trash2,
|
Trash2,
|
||||||
@ -105,25 +104,19 @@ export default function TimesheetDetailPage() {
|
|||||||
}, [timesheet]);
|
}, [timesheet]);
|
||||||
|
|
||||||
const HOUR_OPTIONS = [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 7.7];
|
const HOUR_OPTIONS = [0, 0.5, 1, 2, 3, 4, 5, 6, 7, 7.7];
|
||||||
|
const [saveStatus, setSaveStatus] = useState<"idle" | "saving" | "saved">("idle");
|
||||||
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const localEntriesRef = useRef(localEntries);
|
||||||
|
localEntriesRef.current = localEntries;
|
||||||
|
|
||||||
const handleSetHours = (lineId: number, dateStr: string, hours: number) => {
|
const doSave = useCallback(() => {
|
||||||
const key = `${lineId}-${dateStr}`;
|
|
||||||
setLocalEntries(prev => ({ ...prev, [key]: hours }));
|
|
||||||
setHasUnsavedChanges(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutateFnRef = useRef(upsertEntries.mutate);
|
|
||||||
mutateFnRef.current = upsertEntries.mutate;
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
|
||||||
if (!timesheetId) return;
|
if (!timesheetId) return;
|
||||||
|
const entries = localEntriesRef.current;
|
||||||
|
|
||||||
const entriesToSave: LocalEntry[] = [];
|
const entriesToSave: LocalEntry[] = [];
|
||||||
Object.entries(localEntries).forEach(([key, hours]) => {
|
Object.entries(entries).forEach(([key, hours]) => {
|
||||||
const [lineIdStr, date] = key.split("-", 2);
|
const lineIdStr = key.split("-")[0];
|
||||||
// Reconstruct the full date string since we split by '-'
|
|
||||||
const fullDate = key.substring(lineIdStr.length + 1);
|
const fullDate = key.substring(lineIdStr.length + 1);
|
||||||
|
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
entriesToSave.push({
|
entriesToSave.push({
|
||||||
timesheetLineId: parseInt(lineIdStr),
|
timesheetLineId: parseInt(lineIdStr),
|
||||||
@ -133,37 +126,84 @@ export default function TimesheetDetailPage() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mutateFnRef.current({
|
setSaveStatus("saving");
|
||||||
|
upsertEntries.mutate({
|
||||||
timesheetId,
|
timesheetId,
|
||||||
data: { entries: entriesToSave }
|
data: { entries: entriesToSave }
|
||||||
}, {
|
}, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast({ title: "Modifications sauvegardées" });
|
setSaveStatus("saved");
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
queryClient.invalidateQueries({ queryKey: getGetTimesheetQueryKey(timesheetId) });
|
queryClient.invalidateQueries({ queryKey: getGetTimesheetQueryKey(timesheetId) });
|
||||||
|
setTimeout(() => setSaveStatus("idle"), 2000);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
setSaveStatus("idle");
|
||||||
toast({ title: "Erreur de sauvegarde", variant: "destructive" });
|
toast({ title: "Erreur de sauvegarde", variant: "destructive" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [timesheetId, localEntries, toast, queryClient]);
|
}, [timesheetId, upsertEntries, queryClient, toast]);
|
||||||
|
|
||||||
|
const handleSetHours = (lineId: number, dateStr: string, hours: number) => {
|
||||||
|
const key = `${lineId}-${dateStr}`;
|
||||||
|
setLocalEntries(prev => ({ ...prev, [key]: hours }));
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
|
||||||
|
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
|
||||||
|
saveTimerRef.current = setTimeout(() => doSave(), 800);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (hasUnsavedChanges) {
|
if (saveTimerRef.current) {
|
||||||
toast({ title: "Veuillez sauvegarder avant de soumettre", variant: "destructive" });
|
clearTimeout(saveTimerRef.current);
|
||||||
return;
|
saveTimerRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (confirm("Êtes-vous sûr de vouloir soumettre ce CRA ? Il ne pourra plus être modifié.")) {
|
const flushAndSubmit = () => {
|
||||||
updateTimesheet.mutate({
|
if (confirm("Êtes-vous sûr de vouloir soumettre ce CRA ? Il ne pourra plus être modifié.")) {
|
||||||
id: timesheetId,
|
updateTimesheet.mutate({
|
||||||
data: { status: "submitted" }
|
id: timesheetId,
|
||||||
}, {
|
data: { status: "submitted" }
|
||||||
onSuccess: () => {
|
}, {
|
||||||
toast({ title: "CRA soumis avec succès" });
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: getGetTimesheetQueryKey(timesheetId) });
|
toast({ title: "CRA soumis avec succès" });
|
||||||
|
queryClient.invalidateQueries({ queryKey: getGetTimesheetQueryKey(timesheetId) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasUnsavedChanges) {
|
||||||
|
const entries = localEntriesRef.current;
|
||||||
|
const entriesToSave: LocalEntry[] = [];
|
||||||
|
Object.entries(entries).forEach(([key, hours]) => {
|
||||||
|
const lineIdStr = key.split("-")[0];
|
||||||
|
const fullDate = key.substring(lineIdStr.length + 1);
|
||||||
|
if (hours > 0) {
|
||||||
|
entriesToSave.push({ timesheetLineId: parseInt(lineIdStr), date: fullDate, hours });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setSaveStatus("saving");
|
||||||
|
upsertEntries.mutate({ timesheetId, data: { entries: entriesToSave } }, {
|
||||||
|
onSuccess: () => {
|
||||||
|
setSaveStatus("idle");
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
queryClient.invalidateQueries({ queryKey: getGetTimesheetQueryKey(timesheetId) });
|
||||||
|
flushAndSubmit();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setSaveStatus("idle");
|
||||||
|
toast({ title: "Erreur de sauvegarde, impossible de soumettre", variant: "destructive" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
flushAndSubmit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -247,20 +287,31 @@ export default function TimesheetDetailPage() {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{isEditable && (
|
{isEditable && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<span className="text-xs text-muted-foreground flex items-center gap-1.5">
|
||||||
variant={hasUnsavedChanges ? "default" : "outline"}
|
{saveStatus === "saving" && (
|
||||||
onClick={handleSave}
|
<>
|
||||||
disabled={!hasUnsavedChanges || upsertEntries.isPending}
|
<span className="h-2 w-2 rounded-full bg-amber-400 animate-pulse" />
|
||||||
className="gap-2"
|
Enregistrement...
|
||||||
>
|
</>
|
||||||
<Save className="h-4 w-4" />
|
)}
|
||||||
{hasUnsavedChanges ? "Sauvegarder les modifs" : "Sauvegardé"}
|
{saveStatus === "saved" && (
|
||||||
</Button>
|
<>
|
||||||
|
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
|
||||||
|
Enregistré
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{saveStatus === "idle" && !hasUnsavedChanges && (
|
||||||
|
<>
|
||||||
|
<CheckCircle className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
À jour
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="default"
|
variant="default"
|
||||||
className="gap-2 bg-green-600 hover:bg-green-700 text-white"
|
className="gap-2 bg-green-600 hover:bg-green-700 text-white"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={hasUnsavedChanges || updateTimesheet.isPending}
|
disabled={saveStatus === "saving" || updateTimesheet.isPending}
|
||||||
>
|
>
|
||||||
<Send className="h-4 w-4" />
|
<Send className="h-4 w-4" />
|
||||||
Soumettre
|
Soumettre
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user