app/composables/useStorSync.ts

260 lines
No EOL
8 KiB
TypeScript

/**
* 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
}
}