diff --git a/src/components/CloudLoadModal.tsx b/src/components/CloudLoadModal.tsx index b87f846..9f05c18 100644 --- a/src/components/CloudLoadModal.tsx +++ b/src/components/CloudLoadModal.tsx @@ -4,6 +4,7 @@ import { Portal } from 'solid-js/web'; import { X } from 'lucide-solid'; import { pb, COLLECTIONS } from '../utils/db'; import { appStore } from '../store/appStore'; +import { normalizeScopes } from '../utils/scope-utils'; interface CloudLoadModalProps { show: boolean; @@ -39,7 +40,7 @@ const CloudLoadModal: Component = (props) => { const data = record.items; // items holds the full JSON blob if (!data) return; - if (data.scopes) appStore.setScopes(data.scopes); + if (data.scopes) appStore.setScopes(normalizeScopes(data.scopes)); if (data.subScopes) appStore.setSubScopes(data.subScopes); if (data.items) appStore.setEstimateItems(data.items); if (data.clientInfo) appStore.setClientInfo(data.clientInfo); diff --git a/src/components/EstimateBuilder.tsx b/src/components/EstimateBuilder.tsx index fd89597..c611c14 100644 --- a/src/components/EstimateBuilder.tsx +++ b/src/components/EstimateBuilder.tsx @@ -96,8 +96,12 @@ const EstimateBuilder: Component = (props) => { name: 'New Scope', managementFee: 0, managementFeeType: 'percent', + managementFeeQuantity: 1, + managementFeeRate: 0, deliveryFee: 0, deliveryFeeType: 'percent', + deliveryFeeQuantity: 1, + deliveryFeeRate: 0, useManagementLogic: 'percent', useDeliveryLogic: 'percent', estimatorNotes: '', diff --git a/src/components/ScopeCard.tsx b/src/components/ScopeCard.tsx index 49663c5..6be6edd 100644 --- a/src/components/ScopeCard.tsx +++ b/src/components/ScopeCard.tsx @@ -312,21 +312,63 @@ const ScopeCard: Component = (props) => { -
+
+ {props.scope.useManagementLogic === 'percent' ? ( props.onUpdateScope(props.scope.id, { managementFee: val })} class="w-20 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" /> - ${formatNumber(totals().mgmtTotal)} -
+ ) : ( +
+
+ Qty + { + const qty = val; + const rate = props.scope.managementFeeRate || 0; + props.onUpdateScope(props.scope.id, { + managementFeeQuantity: qty, + managementFee: qty * rate + }); + }} + class="w-16 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" + /> +
+ × +
+ Rate +
+ $ + { + const rate = val; + const qty = props.scope.managementFeeQuantity || 0; + props.onUpdateScope(props.scope.id, { + managementFeeRate: rate, + managementFee: qty * rate + }); + }} + class="w-20 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" + /> +
+
+
+ )} + ${formatNumber(totals().mgmtTotal)} +
{/* Delivery Fee */} @@ -341,21 +383,63 @@ const ScopeCard: Component = (props) => { -
+
+ {props.scope.useDeliveryLogic === 'percent' ? ( props.onUpdateScope(props.scope.id, { deliveryFee: val })} class="w-20 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" /> - ${formatNumber(totals().deliveryTotal)} -
+ ) : ( +
+
+ Qty + { + const qty = val; + const rate = props.scope.deliveryFeeRate || 0; + props.onUpdateScope(props.scope.id, { + deliveryFeeQuantity: qty, + deliveryFee: qty * rate + }); + }} + class="w-16 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" + /> +
+ × +
+ Rate +
+ $ + { + const rate = val; + const qty = props.scope.deliveryFeeQuantity || 0; + props.onUpdateScope(props.scope.id, { + deliveryFeeRate: rate, + deliveryFee: qty * rate + }); + }} + class="w-20 text-right text-sm border-b border-dashed border-border focus:border-primary outline-none bg-transparent" + /> +
+
+
+ )} + ${formatNumber(totals().deliveryTotal)} +
{/* Scope Notes Section */} diff --git a/src/components/estimate-builder/useEstimateSync.ts b/src/components/estimate-builder/useEstimateSync.ts index 8d58ca6..1f64b04 100644 --- a/src/components/estimate-builder/useEstimateSync.ts +++ b/src/components/estimate-builder/useEstimateSync.ts @@ -1,6 +1,7 @@ import { createSignal } from 'solid-js'; import { appStore } from '../../store/appStore'; import { pb, COLLECTIONS } from '../../utils/db'; +import { normalizeScopes } from '../../utils/scope-utils'; export function useEstimateSync( setShowSaveModal: (show: boolean) => void @@ -49,7 +50,7 @@ export function useEstimateSync( reader.onload = (event) => { try { const data = JSON.parse(event.target?.result as string); - if (data.scopes) setScopes(data.scopes); + if (data.scopes) setScopes(normalizeScopes(data.scopes)); if (data.subScopes) setSubScopes(data.subScopes); if (data.items) setItems(data.items); if (data.clientInfo) setClientInfo(data.clientInfo); @@ -206,7 +207,7 @@ export function useEstimateSync( // snapshot is an entry from the history array if (!snapshot) return; - if (snapshot.scopes) setScopes(snapshot.scopes); + if (snapshot.scopes) setScopes(normalizeScopes(snapshot.scopes)); if (snapshot.subScopes) setSubScopes(snapshot.subScopes); if (snapshot.items) setItems(snapshot.items); if (snapshot.clientInfo) setClientInfo(snapshot.clientInfo); diff --git a/src/types.ts b/src/types.ts index e3f6e5e..b575901 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,8 +3,12 @@ export interface Scope { name: string; managementFee: number; managementFeeType: 'percent' | 'amount'; + managementFeeQuantity: number; + managementFeeRate: number; deliveryFee: number; deliveryFeeType: 'percent' | 'amount'; + deliveryFeeQuantity: number; + deliveryFeeRate: number; useManagementLogic: 'percent' | 'simple'; useDeliveryLogic: 'percent' | 'simple'; estimatorNotes?: string; diff --git a/src/utils/scope-utils.ts b/src/utils/scope-utils.ts new file mode 100644 index 0000000..cbfcc37 --- /dev/null +++ b/src/utils/scope-utils.ts @@ -0,0 +1,13 @@ +import type { Scope } from '../types'; + +export const normalizeScopes = (scopes: any[]): Scope[] => { + if (!Array.isArray(scopes)) return []; + + return scopes.map(scope => ({ + ...scope, + managementFeeQuantity: scope.managementFeeQuantity ?? 1, + managementFeeRate: scope.managementFeeRate ?? 0, + deliveryFeeQuantity: scope.deliveryFeeQuantity ?? 1, + deliveryFeeRate: scope.deliveryFeeRate ?? 0 + })); +};