refactor: remove deprecated components and streamline member coverage calculations, enhance budget management with improved payroll handling, and update UI elements for better clarity
This commit is contained in:
parent
983aeca2dc
commit
09d8794d72
42 changed files with 2166 additions and 2974 deletions
|
|
@ -60,6 +60,24 @@ export function useCoopBuilder() {
|
|||
}
|
||||
})
|
||||
|
||||
const currency = computed({
|
||||
get: () => {
|
||||
try {
|
||||
return store.currency || 'EUR'
|
||||
} catch (e) {
|
||||
console.warn('Error accessing currency:', e)
|
||||
return 'EUR'
|
||||
}
|
||||
},
|
||||
set: (value: string) => {
|
||||
try {
|
||||
store.setCurrency(value)
|
||||
} catch (e) {
|
||||
console.warn('Error setting currency:', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const scenario = computed({
|
||||
get: () => store.scenario,
|
||||
set: (value) => store.setScenario(value)
|
||||
|
|
@ -78,12 +96,6 @@ export function useCoopBuilder() {
|
|||
const baseStreams = [...streams.value]
|
||||
|
||||
switch (scenario.value) {
|
||||
case 'quit-jobs':
|
||||
return {
|
||||
members: baseMembers.map(m => ({ ...m, externalMonthlyIncome: 0 })),
|
||||
streams: baseStreams
|
||||
}
|
||||
|
||||
case 'start-production':
|
||||
return {
|
||||
members: baseMembers,
|
||||
|
|
@ -154,25 +166,21 @@ export function useCoopBuilder() {
|
|||
}
|
||||
|
||||
// Coverage calculation for a single member
|
||||
function coverage(member: Member): { minPct: number; targetPct: number } {
|
||||
const totalIncome = (member.monthlyPayPlanned || 0) + (member.externalMonthlyIncome || 0)
|
||||
function coverage(member: Member): { coveragePct: number } {
|
||||
const coopPay = member.monthlyPayPlanned || 0
|
||||
|
||||
const minPct = member.minMonthlyNeeds > 0
|
||||
? Math.min(200, (totalIncome / member.minMonthlyNeeds) * 100)
|
||||
: 100
|
||||
|
||||
const targetPct = member.targetMonthlyPay > 0
|
||||
? Math.min(200, (totalIncome / member.targetMonthlyPay) * 100)
|
||||
const coveragePct = member.minMonthlyNeeds > 0
|
||||
? Math.min(200, (coopPay / member.minMonthlyNeeds) * 100)
|
||||
: 100
|
||||
|
||||
return { minPct, targetPct }
|
||||
return { coveragePct }
|
||||
}
|
||||
|
||||
// Team coverage statistics
|
||||
function teamCoverageStats() {
|
||||
try {
|
||||
const allocatedMembers = allocatePayroll() || []
|
||||
const coverages = allocatedMembers.map(m => coverage(m).minPct).filter(c => !isNaN(c))
|
||||
const coverages = allocatedMembers.map(m => coverage(m).coveragePct).filter(c => !isNaN(c))
|
||||
|
||||
if (coverages.length === 0) {
|
||||
return { median: 0, under100: 0, over100Pct: 0, gini: 0 }
|
||||
|
|
@ -354,6 +362,7 @@ export function useCoopBuilder() {
|
|||
streams,
|
||||
policy,
|
||||
operatingMode,
|
||||
currency,
|
||||
scenario,
|
||||
stress,
|
||||
milestones,
|
||||
|
|
@ -380,6 +389,7 @@ export function useCoopBuilder() {
|
|||
setPolicy: (relationship: "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded") => store.setPolicy(relationship),
|
||||
setRoleBands: (bands: Record<string, number>) => store.setRoleBands(bands),
|
||||
setEqualWage: (wage: number) => store.setEqualWage(wage),
|
||||
setCurrency: (currency: string) => store.setCurrency(currency),
|
||||
|
||||
// Testing helpers
|
||||
clearAll,
|
||||
|
|
|
|||
37
composables/useCurrency.ts
Normal file
37
composables/useCurrency.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { getCurrencySymbol } from '~/utils/currency'
|
||||
|
||||
export function useCurrency() {
|
||||
const coop = useCoopBuilder()
|
||||
|
||||
const currencySymbol = computed(() => getCurrencySymbol(coop.currency.value))
|
||||
|
||||
const formatCurrency = (amount: number, options?: { showSymbol?: boolean; precision?: number }) => {
|
||||
const { showSymbol = true, precision = 0 } = options || {}
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
minimumFractionDigits: precision,
|
||||
maximumFractionDigits: precision
|
||||
}).format(amount)
|
||||
|
||||
if (showSymbol) {
|
||||
return `${currencySymbol.value}${formatted}`
|
||||
}
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
const formatCurrencyCompact = (amount: number) => {
|
||||
if (amount >= 1000000) {
|
||||
return `${currencySymbol.value}${(amount / 1000000).toFixed(1)}M`
|
||||
} else if (amount >= 1000) {
|
||||
return `${currencySymbol.value}${(amount / 1000).toFixed(1)}k`
|
||||
}
|
||||
return formatCurrency(amount)
|
||||
}
|
||||
|
||||
return {
|
||||
currencySymbol,
|
||||
formatCurrency,
|
||||
formatCurrencyCompact
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,14 @@ import { useMembersStore } from '~/stores/members'
|
|||
import { usePoliciesStore } from '~/stores/policies'
|
||||
import { useStreamsStore } from '~/stores/streams'
|
||||
import { useBudgetStore } from '~/stores/budget'
|
||||
import { useScenariosStore } from '~/stores/scenarios'
|
||||
import { useCashStore } from '~/stores/cash'
|
||||
import { useSessionStore } from '~/stores/session'
|
||||
|
||||
export type AppSnapshot = {
|
||||
members: any[]
|
||||
policies: Record<string, any>
|
||||
streams: any[]
|
||||
budget: Record<string, any>
|
||||
scenarios: Record<string, any>
|
||||
cash: Record<string, any>
|
||||
session: Record<string, any>
|
||||
}
|
||||
|
||||
export function useFixtureIO() {
|
||||
|
|
@ -22,9 +18,7 @@ export function useFixtureIO() {
|
|||
const policies = usePoliciesStore()
|
||||
const streams = useStreamsStore()
|
||||
const budget = useBudgetStore()
|
||||
const scenarios = useScenariosStore()
|
||||
const cash = useCashStore()
|
||||
const session = useSessionStore()
|
||||
|
||||
return {
|
||||
members: members.members,
|
||||
|
|
@ -48,23 +42,12 @@ export function useFixtureIO() {
|
|||
productionCosts: budget.productionCosts,
|
||||
currentPeriod: budget.currentPeriod
|
||||
},
|
||||
scenarios: {
|
||||
sliders: scenarios.sliders,
|
||||
activeScenario: scenarios.activeScenario
|
||||
},
|
||||
cash: {
|
||||
cashEvents: cash.cashEvents,
|
||||
paymentQueue: cash.paymentQueue,
|
||||
currentCash: cash.currentCash,
|
||||
currentSavings: cash.currentSavings
|
||||
},
|
||||
session: {
|
||||
checklist: session.checklist,
|
||||
draftAllocations: session.draftAllocations,
|
||||
rationale: session.rationale,
|
||||
currentSession: session.currentSession,
|
||||
savedRecords: session.savedRecords
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,9 +56,7 @@ export function useFixtureIO() {
|
|||
const policies = usePoliciesStore()
|
||||
const streams = useStreamsStore()
|
||||
const budget = useBudgetStore()
|
||||
const scenarios = useScenariosStore()
|
||||
const cash = useCashStore()
|
||||
const session = useSessionStore()
|
||||
|
||||
try {
|
||||
// Import members
|
||||
|
|
@ -98,10 +79,6 @@ export function useFixtureIO() {
|
|||
budget.$patch(snapshot.budget)
|
||||
}
|
||||
|
||||
// Import scenarios
|
||||
if (snapshot.scenarios) {
|
||||
scenarios.$patch(snapshot.scenarios)
|
||||
}
|
||||
|
||||
// Import cash
|
||||
if (snapshot.cash) {
|
||||
|
|
@ -118,10 +95,6 @@ export function useFixtureIO() {
|
|||
}
|
||||
}
|
||||
|
||||
// Import session
|
||||
if (snapshot.session) {
|
||||
session.$patch(snapshot.session)
|
||||
}
|
||||
|
||||
console.log('Successfully imported data snapshot')
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
import { monthlyPayroll } from '~/types/members'
|
||||
|
||||
export function useScenarios() {
|
||||
const membersStore = useMembersStore()
|
||||
const streamsStore = useStreamsStore()
|
||||
const policiesStore = usePoliciesStore()
|
||||
const budgetStore = useBudgetStore()
|
||||
const cashStore = useCashStore()
|
||||
|
||||
// Base runway calculation
|
||||
function calculateScenarioRunway(
|
||||
members: any[],
|
||||
streams: any[],
|
||||
operatingMode: 'minimum' | 'target' = 'minimum'
|
||||
) {
|
||||
// Calculate payroll for scenario
|
||||
const payrollCost = monthlyPayroll(members, operatingMode)
|
||||
const oncostPct = policiesStore.payrollOncostPct || 0
|
||||
const totalPayroll = payrollCost * (1 + oncostPct / 100)
|
||||
|
||||
// Calculate revenue
|
||||
const totalRevenue = streams.reduce((sum, s) => sum + (s.targetMonthlyAmount || 0), 0)
|
||||
|
||||
// Add overhead
|
||||
const overheadCost = budgetStore.overheadCosts.reduce((sum, cost) => sum + (cost.amount || 0), 0)
|
||||
|
||||
// Net monthly
|
||||
const monthlyNet = totalRevenue - totalPayroll - overheadCost
|
||||
|
||||
// Cash + savings
|
||||
const cash = cashStore.currentCash || 50000
|
||||
const savings = cashStore.currentSavings || 15000
|
||||
const totalLiquid = cash + savings
|
||||
|
||||
// Runway calculation
|
||||
const monthlyBurn = totalPayroll + overheadCost
|
||||
const runway = monthlyBurn > 0 ? totalLiquid / monthlyBurn : Infinity
|
||||
|
||||
return {
|
||||
runway: Math.max(0, runway),
|
||||
monthlyNet,
|
||||
monthlyBurn,
|
||||
totalRevenue,
|
||||
totalPayroll
|
||||
}
|
||||
}
|
||||
|
||||
// Scenario transformations per CLAUDE.md
|
||||
const scenarioTransforms = {
|
||||
current: () => ({
|
||||
members: [...membersStore.members],
|
||||
streams: [...streamsStore.streams]
|
||||
}),
|
||||
|
||||
quitJobs: () => ({
|
||||
// Set external income to 0 for members who have day jobs
|
||||
members: membersStore.members.map(m => ({
|
||||
...m,
|
||||
externalMonthlyIncome: 0 // Assume everyone quits their day job
|
||||
})),
|
||||
streams: [...streamsStore.streams]
|
||||
}),
|
||||
|
||||
startProduction: () => ({
|
||||
members: [...membersStore.members],
|
||||
// Reduce service revenue, increase production costs
|
||||
streams: streamsStore.streams.map(s => {
|
||||
// Reduce service contracts by 30%
|
||||
if (s.category?.toLowerCase().includes('service') || s.name.toLowerCase().includes('service')) {
|
||||
return { ...s, targetMonthlyAmount: (s.targetMonthlyAmount || 0) * 0.7 }
|
||||
}
|
||||
return s
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Calculate all scenarios
|
||||
const scenarios = computed(() => {
|
||||
const currentMode = policiesStore.operatingMode || 'minimum'
|
||||
|
||||
const current = scenarioTransforms.current()
|
||||
const quitJobs = scenarioTransforms.quitJobs()
|
||||
const startProduction = scenarioTransforms.startProduction()
|
||||
|
||||
return {
|
||||
current: {
|
||||
name: 'Operate Current',
|
||||
status: 'Active',
|
||||
...calculateScenarioRunway(current.members, current.streams, currentMode)
|
||||
},
|
||||
quitJobs: {
|
||||
name: 'Quit Day Jobs',
|
||||
status: 'Scenario',
|
||||
...calculateScenarioRunway(quitJobs.members, quitJobs.streams, currentMode)
|
||||
},
|
||||
startProduction: {
|
||||
name: 'Start Production',
|
||||
status: 'Scenario',
|
||||
...calculateScenarioRunway(startProduction.members, startProduction.streams, currentMode)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
scenarios,
|
||||
calculateScenarioRunway,
|
||||
scenarioTransforms
|
||||
}
|
||||
}
|
||||
83
composables/useSetupState.ts
Normal file
83
composables/useSetupState.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Setup State Management
|
||||
*
|
||||
* Provides utilities to determine setup completion status and manage
|
||||
* field locking based on setup state
|
||||
*/
|
||||
|
||||
export const useSetupState = () => {
|
||||
const coopStore = useCoopBuilderStore()
|
||||
const membersStore = useMembersStore()
|
||||
const policiesStore = usePoliciesStore()
|
||||
const streamsStore = useStreamsStore()
|
||||
|
||||
// Check if setup is complete using the same logic as middleware
|
||||
const isSetupComplete = computed(() => {
|
||||
// Legacy stores OR new coop builder store (either is enough)
|
||||
const legacyComplete =
|
||||
membersStore.isValid &&
|
||||
policiesStore.isValid &&
|
||||
streamsStore.hasValidStreams
|
||||
|
||||
const coopComplete = Boolean(
|
||||
coopStore &&
|
||||
Array.isArray(coopStore.members) &&
|
||||
coopStore.members.length > 0 &&
|
||||
Array.isArray(coopStore.streams) &&
|
||||
coopStore.streams.length > 0
|
||||
)
|
||||
|
||||
return legacyComplete || coopComplete
|
||||
})
|
||||
|
||||
// Determine if revenue and expense fields should be locked
|
||||
const areRevenueFieldsLocked = computed(() => {
|
||||
return isSetupComplete.value
|
||||
})
|
||||
|
||||
const areExpenseFieldsLocked = computed(() => {
|
||||
return isSetupComplete.value
|
||||
})
|
||||
|
||||
// Determine if member management should be in separate interface
|
||||
const shouldUseSeparateMemberInterface = computed(() => {
|
||||
return isSetupComplete.value
|
||||
})
|
||||
|
||||
// Get setup completion percentage for progress display
|
||||
const setupProgress = computed(() => {
|
||||
let completed = 0
|
||||
let total = 4 // policies, members, revenue, costs
|
||||
|
||||
if (policiesStore.isValid || (coopStore.equalHourlyWage > 0)) completed++
|
||||
if (membersStore.members.length > 0 || coopStore.members.length > 0) completed++
|
||||
if (streamsStore.hasValidStreams || coopStore.streams.length > 0) completed++
|
||||
if (coopStore.overheadCosts.length > 0) completed++
|
||||
|
||||
return Math.round((completed / total) * 100)
|
||||
})
|
||||
|
||||
// Navigation helpers
|
||||
const goToSetup = () => {
|
||||
navigateTo('/coop-planner')
|
||||
}
|
||||
|
||||
const goToMemberManagement = () => {
|
||||
navigateTo('/settings#members')
|
||||
}
|
||||
|
||||
const goToRevenueMix = () => {
|
||||
navigateTo('/mix')
|
||||
}
|
||||
|
||||
return {
|
||||
isSetupComplete,
|
||||
areRevenueFieldsLocked,
|
||||
areExpenseFieldsLocked,
|
||||
shouldUseSeparateMemberInterface,
|
||||
setupProgress,
|
||||
goToSetup,
|
||||
goToMemberManagement,
|
||||
goToRevenueMix
|
||||
}
|
||||
}
|
||||
260
composables/useStorSync.ts
Normal file
260
composables/useStorSync.ts
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue