// SnapshotEditPage — create or edit a balance snapshot at a given date. // // Issue #146 / Bilan #1b ships the route `/balance/snapshot` with two modes // driven by the `?date=` query parameter: // - `?date=` absent → 'new' mode (date picker editable, defaults to today) // - `?date=YYYY-MM-DD` → 'edit' mode if a snapshot exists at that date, // otherwise 'new' mode pre-selected at that date (which mirrors the // "redirect to edit" flow when the user comes from the future // /balance overview's "Edit" link). // // The page itself only orchestrates: all DB work flows through // `useSnapshotEditor`, the editor view through `SnapshotEditor`. Per spec // (decisions row "Bouton Pré-remplir"), priced-kind prefill is a no-op // here (the priced editor lands in #140). import { useEffect, useMemo, useState } from "react"; import { useSearchParams, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { ArrowLeft, Trash2, Save, Wallet, RotateCcw, AlertTriangle, } from "lucide-react"; import { useSnapshotEditor } from "../hooks/useSnapshotEditor"; import SnapshotEditor from "../components/balance/SnapshotEditor"; export default function SnapshotEditPage() { const { t } = useTranslation(); const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const dateParam = searchParams.get("date"); const editor = useSnapshotEditor({ dateParam }); const { state } = editor; const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleteConfirmText, setDeleteConfirmText] = useState(""); // Reset the delete modal whenever the underlying snapshot changes (e.g. // after switching ?date=). useEffect(() => { setShowDeleteModal(false); setDeleteConfirmText(""); }, [state.snapshot?.id]); const isEditMode = state.mode === "edit"; const canPrefill = !!state.previousSnapshot; // Aggregate value across simple + priced lines (computed live as the // user types). Priced contribution = quantity × unit_price. const totalValue = useMemo(() => { let total = 0; let hasAny = false; for (const raw of Object.values(state.values)) { if (!raw) continue; const trimmed = String(raw).trim().replace(",", "."); const n = Number(trimmed); if (Number.isFinite(n)) { total += n; hasAny = true; } } for (const entry of Object.values(state.pricedValues)) { if (!entry) continue; const qty = Number(String(entry.quantity ?? "").trim().replace(",", ".")); const price = Number( String(entry.unit_price ?? "").trim().replace(",", ".") ); if (Number.isFinite(qty) && Number.isFinite(price)) { total += qty * price; hasAny = true; } } return hasAny ? total : null; }, [state.values, state.pricedValues]); const handleSave = async () => { try { await editor.save(); // After a successful create, the URL should become `?date=...` so // refreshing keeps the user in edit mode. if (!isEditMode) { setSearchParams( { date: state.snapshotDate }, { replace: true } ); } } catch { // The hook surfaced the error via state.errorCode/state.error. } }; const handleDelete = async () => { try { await editor.remove(); navigate("/balance/accounts"); } catch { // surfaced via state.error } }; return (
{t("balance.snapshot.page.dateImmutable")}
)}{t("balance.snapshot.page.noAccounts")}
{t("balance.snapshot.delete.body", { date: snapshotDate })}