From 4b2585a59fd9d91ba0863d94f18ecf2eec4ae168 Mon Sep 17 00:00:00 2001 From: Timothy Cardoza Date: Fri, 13 Mar 2026 15:04:37 -0500 Subject: [PATCH] history working --- .../estimate-builder/HistoryModal.tsx | 170 +++++++++++++----- .../estimate-builder/useEstimateSync.ts | 91 ++++++++-- 2 files changed, 196 insertions(+), 65 deletions(-) diff --git a/src/components/estimate-builder/HistoryModal.tsx b/src/components/estimate-builder/HistoryModal.tsx index e160bb7..3b56d9e 100644 --- a/src/components/estimate-builder/HistoryModal.tsx +++ b/src/components/estimate-builder/HistoryModal.tsx @@ -37,72 +37,144 @@ const HistoryModal = (props: HistoryModalProps) => {
{/* Current Live Version */}
-
-
- -
-
-
-

Latest Version

- Live - - Active - +
+
+
+ +
+
+
+

Latest Version

+ Live + + Active + +
+

+ The current tip of the project +

-

- The current tip of the project -

+ + + +
- - - + + {/* Change Summary Breakdown for Latest */} + 0}> +
+

Changes in this Version:

+
+ + {(sum: any) => ( +
+
+ {sum.scopeName} +
+ 0}> + +{sum.itemsAdded} Add + + 0}> + -{sum.itemsRemoved} Rem + + 0}> + {sum.itemsUpdated} Upd + +
+
+ 0}> +

+ ↳ {sum.fieldChanges} field adjustments +

+
+
+ )} +
+
+
{/* Historical Snapshots */} {(snapshot) => ( -
-
-
- -
-
-
-

- {snapshot.jobInfo?.name || snapshot.name || 'Version'} -

+
+
+
+
+ +
+
+
+

+ {snapshot.jobInfo?.name || snapshot.name || 'Version'} +

+
+

+ {new Date(snapshot.timestamp || Date.now()).toLocaleString()} +

-

- {new Date(snapshot.timestamp || Date.now()).toLocaleString()} -

+ +
- - + + {/* Change Summary Breakdown */} + 0}> +
+

Scope Changes in this Version:

+
+ + {(sum: any) => ( +
+
+ {sum.scopeName} +
+ 0}> + +{sum.itemsAdded} Add + + 0}> + -{sum.itemsRemoved} Rem + + 0}> + {sum.itemsUpdated} Upd + +
+
+ 0}> +

+ ↳ {sum.fieldChanges} field adjustments +

+
+
+ )} +
+
+
+
)} diff --git a/src/components/estimate-builder/useEstimateSync.ts b/src/components/estimate-builder/useEstimateSync.ts index 543fca0..8d58ca6 100644 --- a/src/components/estimate-builder/useEstimateSync.ts +++ b/src/components/estimate-builder/useEstimateSync.ts @@ -77,21 +77,83 @@ export function useEstimateSync( }; }; + + const calculateChangeSummary = (prevData: any, newData: any) => { + const summary: Record = {}; + + const prevItems = prevData.items || []; + const newItems = newData.items || []; + + const allScopeIds = new Set([ + ...prevItems.map((i: any) => i.scopeId), + ...newItems.map((i: any) => i.scopeId) + ]); + + const scopesMap = new Map((newData.scopes || []).map((s: any) => [s.id, s.name])); + + allScopeIds.forEach(rawId => { + const scopeId = String(rawId); + if (!scopeId || scopeId === 'undefined') return; + const scopeName = scopesMap.get(scopeId) || "Unknown Scope"; + + const scopePrevItems = prevItems.filter((i: any) => i.scopeId === scopeId); + const scopeNewItems = newItems.filter((i: any) => i.scopeId === scopeId); + + let added = 0; + let removed = 0; + let updated = 0; + let fields = 0; + + const scopePrevMap = new Map(scopePrevItems.map((i: any) => [i.id, i])); + const scopeNewMap = new Map(scopeNewItems.map((i: any) => [i.id, i])); + + scopeNewItems.forEach((item: any) => { + const prev = scopePrevMap.get(item.id); + if (!prev) { + added++; + } else { + let changed = false; + ['description', 'quantity', 'unit', 'price', 'type'].forEach(k => { + const v1 = (item as any)[k]; + const v2 = (prev as any)[k]; + if (String(v1) !== String(v2)) { + fields++; + changed = true; + } + }); + if (changed) updated++; + } + }); + + scopePrevItems.forEach((item: any) => { + if (!scopeNewMap.has(item.id)) { + removed++; + } + }); + + if (added > 0 || removed > 0 || updated > 0 || fields > 0) { + (summary as any)[scopeId] = { scopeName, itemsAdded: added, itemsRemoved: removed, itemsUpdated: updated, fieldChanges: fields }; + } + }); + + return summary; + }; + const saveToCloud = async (name: string) => { setIsCloudLoading(true); try { const data = getFullEstimateData(); const record = await pb.collection(COLLECTIONS.ESTIMATES).create({ name, - items: data, - history: [] // New estimate start with empty history + items: { ...data, changeSummary: null }, + history: [] }); setShowSaveModal(false); setEstimateName(name); setEstimateId(record.id); setHistory([]); - setLatestSnapshot(data); + setLatestSnapshot({ ...data, changeSummary: null }); setIsLatestVersion(true); alert('New estimate saved successfully!'); } catch (err) { @@ -109,24 +171,23 @@ export function useEstimateSync( try { const currentData = getFullEstimateData(); - // 1. Fetch current record to get latest history (to be safe against concurrency) const record = await pb.collection(COLLECTIONS.ESTIMATES).getOne(id); const updatedHistory = [...(record.history || [])]; - // 2. Push current state (before this save) to history if it's new - // Actually, the user wants to save "as diffs" or "history". - // In a single-record approach, 'items' is the LATEST state. - // When we "Save Changes", we move the PREVIOUS 'items' to history. - updatedHistory.push(record.items); + const changeSummary = calculateChangeSummary(record.items, currentData); + + const newTip = { + ...currentData, + changeSummary + }; - // 3. Update record with new current items and expanded history await pb.collection(COLLECTIONS.ESTIMATES).update(id, { - items: currentData, - history: updatedHistory + items: newTip, + history: [...updatedHistory, record.items] }); - setHistory(updatedHistory); - setLatestSnapshot(currentData); + setHistory([...updatedHistory, record.items]); + setLatestSnapshot(newTip); setIsLatestVersion(true); alert('Changes saved successfully!'); } catch (err) { @@ -138,8 +199,6 @@ export function useEstimateSync( }; const saveAsNewestToCloud = async () => { - // "Save as Newest" is exactly same as saveChanges in this model - // It pushes the current editor state as the new 'items' and moves whatever was in 'items' to history. await saveChangesToCloud(); };