/** * 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: Math.round((member.hoursPerMonth || 0) / 4.33), 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) // 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: Math.round((member.hoursPerWeek || 0) * 4.33), 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) } } // 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 ], () => { 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: Math.round((member.hoursPerMonth || 0) / 4.33) })) } return membersStore.members }) // Getter functions for backward compatibility const getStreams = () => unifiedStreams.value const getMembers = () => unifiedMembers.value return { syncToLegacyStores, syncFromLegacyStores, initSync, getStreams, getMembers, unifiedStreams, unifiedMembers } }