refactor: update routing paths in app.vue, enhance AnnualBudget component layout, and streamline dashboard and budget pages for improved user experience

This commit is contained in:
Jennie Robinson Faber 2025-09-08 09:39:30 +01:00
parent 09d8794d72
commit 864a81065c
23 changed files with 3211 additions and 1978 deletions

View file

@ -172,15 +172,16 @@
<p class="content-paragraph mb-3 leading-relaxed text-left">
Any person who:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Shares our values and purpose</li>
<li>
Contributes labour to the cooperative (by doing actual work,
not just investing money)
</li>
<li>Commits to collective decision-making</li>
<li>Participates in governance responsibilities</li>
</ul>
<UFormField label="Member Requirements" class="form-group-large">
<UTextarea
v-model="formData.memberRequirements"
:rows="4"
placeholder="Enter member requirements"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
</div>
<div>
@ -413,43 +414,102 @@
class="subsection-title text-xl font-semibold text-neutral-700 dark:text-neutral-200 border-b border-neutral-200 dark:border-neutral-600 pb-1 mb-3">
Paying Ourselves
</h3>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Base rate: $<UInput
v-model="formData.baseRate"
type="number"
placeholder="25"
class="inline-field number-field"
@change="autoSave" />/hour for all members
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Or: Equal monthly draw of $<UInput
v-model="formData.monthlyDraw"
type="number"
placeholder="2000"
class="inline-field number-field"
@change="autoSave" />
per member
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Paid on the
<USelect
v-model="formData.paymentDay"
:items="dayOptions"
placeholder="15"
class="inline-field"
@change="autoSave" />
of each month
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Surplus (profit) distributed equally every
<UInput
v-model="formData.surplusFrequency"
placeholder="quarter"
class="inline-field"
@change="autoSave" />
</li>
</ul>
<!-- Pay Policy Selection -->
<UFormField label="Pay Policy" class="form-group-large mb-4">
<USelect
v-model="formData.payPolicy"
:items="payPolicyOptions"
placeholder="Select pay policy"
size="xl"
class="w-full"
@change="autoSave" />
</UFormField>
<!-- Equal Pay Policy -->
<div v-if="formData.payPolicy === 'equal-pay'" class="space-y-3">
<p class="content-paragraph">All members receive equal compensation regardless of role or hours worked.</p>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Base rate: $<UInput
v-model="formData.baseRate"
type="number"
placeholder="25"
class="inline-field number-field"
@change="autoSave" />/hour for all members
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Or: Equal monthly draw of $<UInput
v-model="formData.monthlyDraw"
type="number"
placeholder="2000"
class="inline-field number-field"
@change="autoSave" />
per member
</li>
</ul>
</div>
<!-- Hours-Weighted Policy -->
<div v-if="formData.payPolicy === 'hours-weighted'" class="space-y-3">
<p class="content-paragraph">Compensation is proportional to hours worked by each member.</p>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Hourly rate: $<UInput
v-model="formData.hourlyRate"
type="number"
placeholder="25"
class="inline-field number-field"
@change="autoSave" />/hour
</li>
<li>Members track their hours and are paid accordingly</li>
<li>Minimum hours commitment may apply</li>
</ul>
</div>
<!-- Needs-Weighted Policy -->
<div v-if="formData.payPolicy === 'needs-weighted'" class="space-y-3">
<p class="content-paragraph">Compensation is allocated based on each member's individual financial needs.</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Members declare their minimum monthly needs</li>
<li>Available payroll is distributed proportionally to cover needs</li>
<li>Regular needs assessment and adjustment process</li>
<li class="flex items-baseline gap-2 flex-wrap">
Minimum guaranteed amount: $<UInput
v-model="formData.minGuaranteedPay"
type="number"
placeholder="1000"
class="inline-field number-field"
@change="autoSave" />/month
</li>
</ul>
</div>
<!-- Common payment details -->
<div class="mt-4 space-y-2">
<p class="content-paragraph font-semibold">Payment Schedule:</p>
<ul class="content-list my-2 pl-6 list-disc">
<li class="flex items-baseline gap-2 flex-wrap">
Paid on the
<USelect
v-model="formData.paymentDay"
:items="dayOptions"
placeholder="15th"
arrow
class="inline-field"
@change="autoSave" />
of each month
</li>
<li class="flex items-baseline gap-2 flex-wrap">
Surplus (profit) distributed equally every
<UInput
v-model="formData.surplusFrequency"
placeholder="quarter"
class="inline-field"
@change="autoSave" />
</li>
</ul>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@ -522,15 +582,16 @@
@change="autoSave" />
months. Current roles include:
</p>
<ul class="content-list">
<li>
Financial coordinator (handles bookkeeping, not financial
decisions)
</li>
<li>Meeting facilitator</li>
<li>External communications</li>
<li>Others</li>
</ul>
<UFormField label="Rotating Roles" class="form-group-large">
<UTextarea
v-model="formData.rotatingRoles"
:rows="4"
placeholder="List rotating operational roles"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
</div>
<div>
@ -541,11 +602,16 @@
<p class="content-paragraph mb-3 leading-relaxed text-left">
All members participate in:
</p>
<ul class="content-list my-2 pl-6 list-disc">
<li>Governance and decision-making</li>
<li>Strategic planning</li>
<li>Mutual support and care</li>
</ul>
<UFormField label="Shared Responsibilities" class="form-group-large">
<UTextarea
v-model="formData.sharedResponsibilities"
:rows="3"
placeholder="List shared responsibilities for all members"
size="xl"
class="large-field"
@input="debouncedAutoSave"
@change="autoSave" />
</UFormField>
</div>
</div>
</div>
@ -690,47 +756,6 @@
</div>
</div>
<!-- Section 10: Agreement Review -->
<div class="section-card">
<h2
class="section-title text-2xl font-extrabold text-neutral-900 dark:text-neutral-100 mb-4">
10. Agreement Review
</h2>
<div class="space-y-4">
<p class="content-paragraph mb-3 leading-relaxed text-left">
By using this agreement, we commit to these principles and to
showing up for each other.
</p>
<div
class="bg-neutral-50 dark:bg-neutral-900 p-4 rounded-md border-l-4 border-emerald-300">
<p
class="content-paragraph mb-3 leading-relaxed text-left flex items-baseline gap-2 flex-wrap">
This agreement was last updated on
<UInput
v-model="formData.lastUpdated"
type="date"
class="inline-field"
@change="autoSave" />. We commit to reviewing it on
<UInput
v-model="formData.nextReview"
type="date"
class="inline-field"
@change="autoSave" />
or sooner if circumstances require.
</p>
</div>
<div
class="signature-space mt-8 p-8 border border-dashed border-neutral-300 rounded-md bg-neutral-50 dark:bg-neutral-950">
<p
class="content-paragraph mb-3 leading-relaxed text-center text-neutral-600 italic">
[Space for member signatures when printed]
</p>
</div>
</div>
</div>
</div>
</div>
</div>
@ -768,13 +793,36 @@ const monthOptions = [
"December",
];
const dayOptions = Array.from({ length: 31 }, (_, i) => (i + 1).toString());
const dayOptions = Array.from({ length: 31 }, (_, i) => ({
value: i + 1,
label: `${i + 1}${getOrdinalSuffix(i + 1)}`
}));
// Helper function to get ordinal suffix (1st, 2nd, 3rd, etc.)
function getOrdinalSuffix(num) {
if (num >= 11 && num <= 13) {
return 'th';
}
switch (num % 10) {
case 1: return 'st';
case 2: return 'nd';
case 3: return 'rd';
default: return 'th';
}
}
const payPolicyOptions = [
{ value: 'equal-pay', label: 'Equal Pay - All members receive equal compensation' },
{ value: 'hours-weighted', label: 'Hours-Weighted - Pay proportional to hours worked' },
{ value: 'needs-weighted', label: 'Needs-Weighted - Pay proportional to individual needs' }
];
const formData = ref({
cooperativeName: "",
dateEstablished: "",
purpose: "",
coreValues: "",
memberRequirements: "Shares our values and purpose\nContributes labour to the cooperative (by doing actual work, not just investing money)\nCommits to collective decision-making\nParticipates in governance responsibilities",
members: [{ name: "", email: "", joinDate: "", role: "" }],
trialPeriodMonths: 3,
buyInAmount: "",
@ -787,20 +835,24 @@ const formData = ref({
majorDebtThreshold: 5000,
meetingFrequency: "weekly",
emergencyNoticeHours: 24,
// Pay policy settings
payPolicy: "equal-pay",
baseRate: 25,
monthlyDraw: "",
hourlyRate: 25,
minGuaranteedPay: 1000,
paymentDay: 15,
surplusFrequency: "quarter",
targetHours: 40,
roleRotationMonths: 6,
rotatingRoles: "Financial coordinator (handles bookkeeping, not financial decisions)\nMeeting facilitator\nExternal communications\nOthers",
sharedResponsibilities: "Governance and decision-making\nStrategic planning\nMutual support and care",
reviewFrequency: "year",
assetDonationTarget: "",
legalStructure: "",
registeredLocation: "",
fiscalYearEndMonth: "December",
fiscalYearEndDay: 31,
lastUpdated: new Date().toISOString().split("T")[0],
nextReview: "",
});
// Load saved data immediately (before watchers)
@ -1017,28 +1069,12 @@ const handlePrint = () => {
// Export data for the ExportOptions component
const exportData = computed(() => ({
// Pass the complete formData object - this is what the export functions use
formData: formData.value,
// Also provide direct access to key fields for backward compatibility
cooperativeName: formData.value.cooperativeName || "Worker Cooperative",
dateEstablished: formData.value.dateEstablished,
purpose: formData.value.purpose,
coreValues: formData.value.coreValues,
members: formData.value.members,
trialPeriodMonths: formData.value.trialPeriodMonths,
policies: {
buyInAmount: formData.value.buyInAmount,
noticeDays: formData.value.noticeDays,
surplusPayoutDays: formData.value.surplusPayoutDays,
buyInReturnDays: formData.value.buyInReturnDays,
dayToDayLimit: formData.value.dayToDayLimit,
regularDecisionMin: formData.value.regularDecisionMin,
regularDecisionMax: formData.value.regularDecisionMax,
majorDebtThreshold: formData.value.majorDebtThreshold,
meetingFrequency: formData.value.meetingFrequency,
emergencyNoticeHours: formData.value.emergencyNoticeHours,
baseRate: formData.value.baseRate,
},
exportedAt: new Date().toISOString(),
section: "membership-agreement",
exportedAt: new Date().toISOString(),
}));
</script>