refactor: enhance routing and state management in CoopBuilder, add migration checks on startup, and update Tailwind configuration for improved component styling
This commit is contained in:
parent
848386e3dd
commit
4cea1f71fe
55 changed files with 4053 additions and 1486 deletions
277
stores/coopBuilder.ts
Normal file
277
stores/coopBuilder.ts
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useCoopBuilderStore = defineStore("coop", {
|
||||
state: () => ({
|
||||
operatingMode: "min" as "min" | "target",
|
||||
|
||||
// Flag to track if data was intentionally cleared
|
||||
_wasCleared: false,
|
||||
|
||||
members: [] as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
role?: string;
|
||||
hoursPerMonth?: number;
|
||||
minMonthlyNeeds: number;
|
||||
targetMonthlyPay: number;
|
||||
externalMonthlyIncome: number;
|
||||
monthlyPayPlanned: number;
|
||||
}>,
|
||||
|
||||
streams: [] as Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
monthly: number;
|
||||
category?: string;
|
||||
certainty?: string;
|
||||
}>,
|
||||
|
||||
milestones: [] as Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
date: string;
|
||||
}>,
|
||||
|
||||
// Scenario and stress test state
|
||||
scenario: "current" as
|
||||
| "current"
|
||||
| "quit-jobs"
|
||||
| "start-production"
|
||||
| "custom",
|
||||
stress: {
|
||||
revenueDelay: 0,
|
||||
costShockPct: 0,
|
||||
grantLost: false,
|
||||
},
|
||||
|
||||
// Policy settings
|
||||
policy: {
|
||||
relationship: "equal-pay" as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded",
|
||||
roleBands: {} as Record<string, number>,
|
||||
},
|
||||
equalHourlyWage: 50,
|
||||
payrollOncostPct: 25,
|
||||
savingsTargetMonths: 6,
|
||||
minCashCushion: 10000,
|
||||
|
||||
// Cash reserves
|
||||
currentCash: 50000,
|
||||
currentSavings: 15000,
|
||||
|
||||
// Overhead costs
|
||||
overheadCosts: [] as Array<{
|
||||
id?: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
category?: string;
|
||||
}>,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
totalRevenue: (state) => {
|
||||
return state.streams.reduce((sum, s) => sum + (s.monthly || 0), 0);
|
||||
},
|
||||
|
||||
totalOverhead: (state) => {
|
||||
return state.overheadCosts.reduce((sum, c) => sum + (c.amount || 0), 0);
|
||||
},
|
||||
|
||||
totalLiquid: (state) => {
|
||||
return state.currentCash + state.currentSavings;
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
// Member actions
|
||||
upsertMember(m: any) {
|
||||
const i = this.members.findIndex((x) => x.id === m.id);
|
||||
// Ensure all keys exist (prevents undefined stripping)
|
||||
const withDefaults = {
|
||||
id: m.id || Date.now().toString(),
|
||||
name: m.name || m.displayName || "",
|
||||
role: m.role ?? "",
|
||||
hoursPerMonth: m.hoursPerMonth ?? 0,
|
||||
minMonthlyNeeds: m.minMonthlyNeeds ?? 0,
|
||||
targetMonthlyPay: m.targetMonthlyPay ?? 0,
|
||||
externalMonthlyIncome: m.externalMonthlyIncome ?? 0,
|
||||
monthlyPayPlanned: m.monthlyPayPlanned ?? 0,
|
||||
};
|
||||
if (i === -1) {
|
||||
this.members.push(withDefaults);
|
||||
} else {
|
||||
this.members[i] = withDefaults;
|
||||
}
|
||||
},
|
||||
|
||||
removeMember(id: string) {
|
||||
this.members = this.members.filter((m) => m.id !== id);
|
||||
},
|
||||
|
||||
// Stream actions
|
||||
upsertStream(s: any) {
|
||||
const i = this.streams.findIndex((x) => x.id === s.id);
|
||||
const withDefaults = {
|
||||
id: s.id || Date.now().toString(),
|
||||
label: s.label || s.name || "",
|
||||
monthly: s.monthly || s.targetMonthlyAmount || 0,
|
||||
category: s.category ?? "",
|
||||
certainty: s.certainty ?? "Probable",
|
||||
};
|
||||
if (i === -1) {
|
||||
this.streams.push(withDefaults);
|
||||
} else {
|
||||
this.streams[i] = withDefaults;
|
||||
}
|
||||
},
|
||||
|
||||
removeStream(id: string) {
|
||||
this.streams = this.streams.filter((s) => s.id !== id);
|
||||
},
|
||||
|
||||
// Milestone actions
|
||||
addMilestone(label: string, date: string) {
|
||||
this.milestones.push({
|
||||
id: Date.now().toString(),
|
||||
label,
|
||||
date,
|
||||
});
|
||||
},
|
||||
|
||||
removeMilestone(id: string) {
|
||||
this.milestones = this.milestones.filter((m) => m.id !== id);
|
||||
},
|
||||
|
||||
// Operating mode
|
||||
setOperatingMode(mode: "min" | "target") {
|
||||
this.operatingMode = mode;
|
||||
},
|
||||
|
||||
// Scenario
|
||||
setScenario(
|
||||
scenario: "current" | "quit-jobs" | "start-production" | "custom"
|
||||
) {
|
||||
this.scenario = scenario;
|
||||
},
|
||||
|
||||
// Stress test
|
||||
updateStress(updates: Partial<typeof this.stress>) {
|
||||
this.stress = { ...this.stress, ...updates };
|
||||
},
|
||||
|
||||
// Policy updates
|
||||
setPolicy(relationship: "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded") {
|
||||
this.policy.relationship = relationship;
|
||||
},
|
||||
|
||||
setRoleBands(bands: Record<string, number>) {
|
||||
this.policy.roleBands = bands;
|
||||
},
|
||||
|
||||
setEqualWage(wage: number) {
|
||||
this.equalHourlyWage = wage;
|
||||
},
|
||||
|
||||
setOncostPct(pct: number) {
|
||||
this.payrollOncostPct = pct;
|
||||
},
|
||||
|
||||
// Overhead costs
|
||||
addOverheadCost(cost: any) {
|
||||
const withDefaults = {
|
||||
id: cost.id || Date.now().toString(),
|
||||
name: cost.name || "",
|
||||
amount: cost.amount || 0,
|
||||
category: cost.category ?? "",
|
||||
};
|
||||
this.overheadCosts.push(withDefaults);
|
||||
},
|
||||
|
||||
upsertOverheadCost(cost: any) {
|
||||
const i = this.overheadCosts.findIndex((c) => c.id === cost.id);
|
||||
const withDefaults = {
|
||||
id: cost.id || Date.now().toString(),
|
||||
name: cost.name || "",
|
||||
amount: cost.amount || 0,
|
||||
category: cost.category ?? "",
|
||||
};
|
||||
if (i === -1) {
|
||||
this.overheadCosts.push(withDefaults);
|
||||
} else {
|
||||
this.overheadCosts[i] = withDefaults;
|
||||
}
|
||||
},
|
||||
|
||||
removeOverheadCost(id: string) {
|
||||
this.overheadCosts = this.overheadCosts.filter((c) => c.id !== id);
|
||||
},
|
||||
|
||||
// Initialize with default data if empty - DISABLED
|
||||
// NO automatic initialization - stores should start empty
|
||||
initializeDefaults() {
|
||||
// DISABLED: No automatic data loading
|
||||
// User must explicitly choose to load demo data
|
||||
return;
|
||||
},
|
||||
|
||||
// Clear ALL data - no exceptions
|
||||
clearAll() {
|
||||
// Reset ALL state to initial empty values
|
||||
this._wasCleared = true;
|
||||
this.operatingMode = "min";
|
||||
this.members = [];
|
||||
this.streams = [];
|
||||
this.milestones = [];
|
||||
this.scenario = "current";
|
||||
this.stress = {
|
||||
revenueDelay: 0,
|
||||
costShockPct: 0,
|
||||
grantLost: false,
|
||||
};
|
||||
this.policy = {
|
||||
relationship: "equal-pay",
|
||||
roleBands: {},
|
||||
};
|
||||
this.equalHourlyWage = 0;
|
||||
this.payrollOncostPct = 0;
|
||||
this.savingsTargetMonths = 0;
|
||||
this.minCashCushion = 0;
|
||||
this.currentCash = 0;
|
||||
this.currentSavings = 0;
|
||||
this.overheadCosts = [];
|
||||
|
||||
// Clear ALL localStorage data
|
||||
if (typeof window !== "undefined") {
|
||||
// Save cleared flag first
|
||||
localStorage.setItem("urgent-tools-cleared-flag", "true");
|
||||
|
||||
// Remove all known keys
|
||||
const keysToRemove = [
|
||||
"coop_builder_v1",
|
||||
"urgent-tools-members",
|
||||
"urgent-tools-policies",
|
||||
"urgent-tools-streams",
|
||||
"urgent-tools-budget",
|
||||
"urgent-tools-cash",
|
||||
"urgent-tools-schema-version",
|
||||
];
|
||||
|
||||
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
||||
|
||||
// Clear any other urgent-tools or coop keys
|
||||
const allKeys = Object.keys(localStorage);
|
||||
allKeys.forEach((key) => {
|
||||
if (key.startsWith("urgent-tools-") || key.startsWith("coop_")) {
|
||||
if (key !== "urgent-tools-cleared-flag") {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
persist: {
|
||||
key: "coop_builder_v1",
|
||||
storage: typeof window !== "undefined" ? localStorage : undefined,
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue