/** * Store Synchronization Composable * * Ensures that the legacy stores (streams, members, policies) always stay * synchronized with the new CoopBuilderStore. This makes the setup interface * the single source of truth while maintaining backward compatibility. */ export const useStoreSync = () => { const coopStore = useCoopBuilderStore(); const streamsStore = useStreamsStore(); const membersStore = useMembersStore(); const policiesStore = usePoliciesStore(); // Flags to prevent recursive syncing and duplicate watchers let isSyncing = false; let watchersSetup = false; // Sync CoopBuilder -> Legacy Stores const syncToLegacyStores = () => { if (isSyncing) return; isSyncing = true; // Sync streams streamsStore.resetStreams(); coopStore.streams.forEach((stream: any) => { streamsStore.upsertStream({ id: stream.id, name: stream.label, category: stream.category || "services", targetMonthlyAmount: stream.monthly, certainty: stream.certainty || "Probable", payoutDelayDays: 30, terms: "Net 30", targetPct: 0, revenueSharePct: 0, platformFeePct: 0, restrictions: "General", seasonalityWeights: new Array(12).fill(1), effortHoursPerMonth: 0, }); }); // Sync members membersStore.resetMembers(); coopStore.members.forEach((member: any) => { membersStore.upsertMember({ id: member.id, displayName: member.name, role: member.role || "", hoursPerWeek: Number(((member.hoursPerMonth || 0) / 4.33).toFixed(2)), minMonthlyNeeds: member.minMonthlyNeeds || 0, monthlyPayPlanned: member.monthlyPayPlanned || 0, targetMonthlyPay: member.targetMonthlyPay || 0, externalMonthlyIncome: member.externalMonthlyIncome || 0, }); }); // Sync policies - using individual update calls based on store structure policiesStore.updatePolicy("equalHourlyWage", coopStore.equalHourlyWage); policiesStore.updatePolicy("payrollOncostPct", coopStore.payrollOncostPct); policiesStore.updatePolicy( "savingsTargetMonths", coopStore.savingsTargetMonths ); policiesStore.updatePolicy( "minCashCushionAmount", coopStore.minCashCushion ); // Ensure pay policy relationship is kept in sync across stores if (coopStore.policy?.relationship) { if (typeof (policiesStore as any).setPayPolicy === "function") { (policiesStore as any).setPayPolicy(coopStore.policy.relationship); } else if (policiesStore.payPolicy) { policiesStore.payPolicy.relationship = coopStore.policy.relationship; } if (membersStore.payPolicy) { membersStore.payPolicy.relationship = coopStore.policy .relationship as any; } } // Reset flag after sync completes nextTick(() => { isSyncing = false; }); }; // Sync Legacy Stores -> CoopBuilder const syncFromLegacyStores = () => { if (isSyncing) return; isSyncing = true; // Sync streams from legacy store streamsStore.streams.forEach((stream: any) => { coopStore.upsertStream({ id: stream.id, label: stream.name, monthly: stream.targetMonthlyAmount, category: stream.category, certainty: stream.certainty, }); }); // Sync members from legacy store membersStore.members.forEach((member: any) => { coopStore.upsertMember({ id: member.id, name: member.displayName, role: member.role, hoursPerMonth: Number(((member.hoursPerWeek || 0) * 4.33).toFixed(2)), minMonthlyNeeds: member.minMonthlyNeeds, monthlyPayPlanned: member.monthlyPayPlanned, targetMonthlyPay: member.targetMonthlyPay, externalMonthlyIncome: member.externalMonthlyIncome, }); }); // Sync policies from legacy store if (policiesStore.isValid) { coopStore.setEqualWage(policiesStore.equalHourlyWage); coopStore.setOncostPct(policiesStore.payrollOncostPct); coopStore.savingsTargetMonths = policiesStore.savingsTargetMonths; coopStore.minCashCushion = policiesStore.minCashCushionAmount; if (policiesStore.payPolicy?.relationship) { coopStore.setPolicy(policiesStore.payPolicy.relationship as any); // Keep members store aligned with legacy policy if (membersStore.payPolicy) { membersStore.payPolicy.relationship = policiesStore.payPolicy .relationship as any; } } } // Also consider members store policy as a source of truth if set if (membersStore.payPolicy?.relationship) { coopStore.setPolicy(membersStore.payPolicy.relationship as any); if (typeof (policiesStore as any).setPayPolicy === "function") { (policiesStore as any).setPayPolicy( membersStore.payPolicy.relationship as any ); } else if (policiesStore.payPolicy) { policiesStore.payPolicy.relationship = membersStore.payPolicy .relationship as any; } } // Reset flag after sync completes nextTick(() => { isSyncing = false; }); }; // Watch for changes in CoopBuilder and sync to legacy stores const setupCoopBuilderWatchers = () => { // Watch streams changes watch( () => coopStore.streams, () => { if (!isSyncing) { syncToLegacyStores(); } }, { deep: true } ); // Watch members changes watch( () => coopStore.members, () => { if (!isSyncing) { syncToLegacyStores(); } }, { deep: true } ); // Watch policy changes watch( () => [ coopStore.equalHourlyWage, coopStore.payrollOncostPct, coopStore.savingsTargetMonths, coopStore.minCashCushion, coopStore.currency, coopStore.policy.relationship, ], () => { if (!isSyncing) { syncToLegacyStores(); } } ); }; // Watch for changes in legacy stores and sync to CoopBuilder const setupLegacyStoreWatchers = () => { // Watch streams store changes watch( () => streamsStore.streams, () => { if (!isSyncing) { syncFromLegacyStores(); } }, { deep: true } ); // Watch members store changes watch( () => membersStore.members, () => { if (!isSyncing) { syncFromLegacyStores(); } }, { deep: true } ); // Watch policies store changes watch( () => [ policiesStore.equalHourlyWage, policiesStore.payrollOncostPct, policiesStore.savingsTargetMonths, policiesStore.minCashCushionAmount, policiesStore.payPolicy?.relationship, membersStore.payPolicy?.relationship, ], () => { if (!isSyncing) { syncFromLegacyStores(); } } ); }; // Initialize synchronization const initSync = async () => { // Wait for next tick to ensure stores are mounted await nextTick(); // Force store hydration by accessing $state if (coopStore.$state) { console.log("🔄 CoopBuilder store hydrated"); } // Small delay to ensure localStorage is loaded await new Promise((resolve) => setTimeout(resolve, 10)); // Determine which store has data and sync accordingly const coopHasData = coopStore.members.length > 0 || coopStore.streams.length > 0; const legacyHasData = streamsStore.streams.length > 0 || membersStore.members.length > 0; console.log( "🔄 InitSync: CoopBuilder data:", coopHasData, "Legacy data:", legacyHasData ); console.log( "🔄 CoopBuilder members:", coopStore.members.length, "streams:", coopStore.streams.length ); console.log( "🔄 Legacy members:", membersStore.members.length, "streams:", streamsStore.streams.length ); if (coopHasData && !legacyHasData) { console.log("🔄 Syncing CoopBuilder → Legacy"); syncToLegacyStores(); } else if (legacyHasData && !coopHasData) { console.log("🔄 Syncing Legacy → CoopBuilder"); syncFromLegacyStores(); } else if (coopHasData && legacyHasData) { console.log("🔄 Both have data, keeping in sync"); // Both have data, ensure consistency by syncing from CoopBuilder (primary source) syncToLegacyStores(); } else { console.log("🔄 No data in either store"); } // Set up watchers for ongoing sync (only once) if (!watchersSetup) { setupCoopBuilderWatchers(); setupLegacyStoreWatchers(); watchersSetup = true; } // Return promise to allow awaiting return Promise.resolve(); }; // Get unified streams data (prioritize CoopBuilder) - make reactive const unifiedStreams = computed(() => { if (coopStore.streams.length > 0) { return coopStore.streams.map((stream) => ({ ...stream, name: stream.label, targetMonthlyAmount: stream.monthly, })); } return streamsStore.streams; }); // Get unified members data (prioritize CoopBuilder) - make reactive const unifiedMembers = computed(() => { if (coopStore.members.length > 0) { return coopStore.members.map((member) => ({ ...member, displayName: member.name, hoursPerWeek: Number(((member.hoursPerMonth || 0) / 4.33).toFixed(2)), })); } return membersStore.members; }); // Getter functions for backward compatibility const getStreams = () => unifiedStreams.value; const getMembers = () => unifiedMembers.value; return { syncToLegacyStores, syncFromLegacyStores, initSync, getStreams, getMembers, unifiedStreams, unifiedMembers, }; };