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
|
|
@ -1,97 +1,78 @@
|
|||
<template>
|
||||
<div class="max-w-4xl mx-auto space-y-6">
|
||||
<!-- Section Header with Export Controls -->
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h3 class="text-2xl font-black text-black mb-2">Set your wage & pay policy</h3>
|
||||
<p class="text-neutral-600">
|
||||
Choose how to allocate payroll among members and set the base hourly rate.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<UButton variant="outline" color="neutral" size="sm" @click="exportPolicies">
|
||||
<UIcon name="i-heroicons-arrow-down-tray" class="mr-1" />
|
||||
Export
|
||||
</UButton>
|
||||
</div>
|
||||
<!-- Section Header -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-2xl font-black text-black mb-2">
|
||||
How will you share money?
|
||||
</h3>
|
||||
<p class="text-neutral-600">
|
||||
This is the foundation of your co-op's finances. Choose a pay approach
|
||||
and set your hourly rate.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Pay Policy Selection -->
|
||||
<div class="p-6 border-3 border-black rounded-xl bg-white shadow-md">
|
||||
<h4 class="font-bold mb-4">Pay Allocation Policy</h4>
|
||||
<div class="space-y-3">
|
||||
<label
|
||||
v-for="option in policyOptions"
|
||||
:key="option.value"
|
||||
class="flex items-start gap-3 cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
:value="option.value"
|
||||
v-model="selectedPolicy"
|
||||
@change="updatePolicy(option.value)"
|
||||
class="mt-1 w-4 h-4 text-black border-2 border-gray-300 focus:ring-2 focus:ring-black"
|
||||
/>
|
||||
<span class="text-sm flex-1">{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Role bands editor if role-banded is selected -->
|
||||
<div v-if="selectedPolicy === 'role-banded'" class="mt-4 p-4 bg-gray-50 rounded-lg">
|
||||
<h5 class="text-sm font-medium mb-3">Role Bands (monthly € or weight)</h5>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="member in uniqueRoles"
|
||||
:key="member.role"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<span class="text-sm w-32">{{ member.role || "No role" }}</span>
|
||||
<UInput
|
||||
v-model="roleBands[member.role || '']"
|
||||
type="text"
|
||||
placeholder="3000"
|
||||
size="sm"
|
||||
class="w-24"
|
||||
@update:model-value="updateRoleBands"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
class="mt-4"
|
||||
color="primary"
|
||||
variant="soft"
|
||||
icon="i-heroicons-information-circle"
|
||||
>
|
||||
<template #description>
|
||||
Policies affect payroll allocation and member coverage. You can iterate later.
|
||||
</template>
|
||||
</UAlert>
|
||||
<div class="p-6 border-2 border-black rounded-xl bg-white shadow-md">
|
||||
<h4 class="font-bold mb-2">Step 1: Choose your pay approach</h4>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
How should available money be shared among members?
|
||||
</p>
|
||||
<URadioGroup
|
||||
v-model="selectedPolicy"
|
||||
:items="policyOptions"
|
||||
@update:model-value="updatePolicy"
|
||||
variant="list"
|
||||
size="xl"
|
||||
class="flex flex-col gap-2 w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Hourly Wage Input -->
|
||||
<div class="p-6 border-3 border-black rounded-xl bg-white shadow-md">
|
||||
<h4 class="font-bold mb-4">Base Hourly Wage</h4>
|
||||
<div class="max-w-md">
|
||||
<UInput
|
||||
v-model="wageText"
|
||||
type="text"
|
||||
placeholder="0.00"
|
||||
size="xl"
|
||||
class="text-4xl font-black w-full h-20"
|
||||
@update:model-value="validateAndSaveWage"
|
||||
>
|
||||
<template #leading>
|
||||
<span class="text-neutral-500 text-3xl">€</span>
|
||||
</template>
|
||||
</UInput>
|
||||
<div class="p-6 border-2 border-black rounded-xl bg-white shadow-md">
|
||||
<h4 class="font-bold mb-2">Step 2: Set your base wage</h4>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
This hourly rate applies to all paid work in your co-op
|
||||
</p>
|
||||
<div class="flex gap-4 items-start">
|
||||
<!-- Currency Selection -->
|
||||
<UFormField label="Currency" class="w-1/2">
|
||||
<USelect
|
||||
v-model="selectedCurrency"
|
||||
:items="currencySelectOptions"
|
||||
placeholder="Select currency"
|
||||
size="xl"
|
||||
class="w-full"
|
||||
@update:model-value="updateCurrency">
|
||||
<template #leading>
|
||||
<span class="text-lg">{{
|
||||
getCurrencySymbol(selectedCurrency)
|
||||
}}</span>
|
||||
</template>
|
||||
</USelect>
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Hourly Rate" class="w-1/2">
|
||||
<UInput
|
||||
v-model="wageText"
|
||||
type="text"
|
||||
placeholder="0.00"
|
||||
size="xl"
|
||||
class="text-2xl font-bold w-full"
|
||||
@update:model-value="validateAndSaveWage">
|
||||
<template #leading>
|
||||
<span class="text-neutral-500 text-xl">{{
|
||||
getCurrencySymbol(selectedCurrency)
|
||||
}}</span>
|
||||
</template>
|
||||
</UInput>
|
||||
</UFormField>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { currencyOptions, getCurrencySymbol } from "~/utils/currency";
|
||||
|
||||
const emit = defineEmits<{
|
||||
"save-status": [status: "saving" | "saved" | "error"];
|
||||
}>();
|
||||
|
|
@ -104,6 +85,7 @@ const store = useCoopBuilderStore();
|
|||
const selectedPolicy = ref(coop.policy.value?.relationship || "equal-pay");
|
||||
const roleBands = ref(coop.policy.value?.roleBands || {});
|
||||
const wageText = ref(String(store.equalHourlyWage || ""));
|
||||
const selectedCurrency = ref(coop.currency.value || "EUR");
|
||||
|
||||
function parseNumberInput(val: unknown): number {
|
||||
if (typeof val === "number") return val;
|
||||
|
|
@ -115,35 +97,42 @@ function parseNumberInput(val: unknown): number {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Pay policy options
|
||||
// Simplified pay policy options
|
||||
const policyOptions = [
|
||||
{
|
||||
value: "equal-pay",
|
||||
label: "Equal pay - Everyone gets the same monthly amount",
|
||||
},
|
||||
{
|
||||
value: "needs-weighted",
|
||||
label: "Needs-weighted - Allocate based on minimum needs",
|
||||
label: "Equal pay - Everyone gets the same amount",
|
||||
},
|
||||
{
|
||||
value: "hours-weighted",
|
||||
label: "Hours-weighted - Allocate based on hours worked",
|
||||
label: "Hours-based - Pay proportional to hours worked",
|
||||
},
|
||||
{
|
||||
value: "needs-weighted",
|
||||
label: "Needs-based - Pay proportional to individual needs",
|
||||
},
|
||||
{ value: "role-banded", label: "Role-banded - Different amounts per role" },
|
||||
];
|
||||
|
||||
// Currency options for USelect (simplified format)
|
||||
const currencySelectOptions = computed(() =>
|
||||
currencyOptions.map((currency) => ({
|
||||
label: `${currency.name} (${currency.code})`,
|
||||
value: currency.code,
|
||||
}))
|
||||
);
|
||||
|
||||
// Already initialized above with store values
|
||||
|
||||
const uniqueRoles = computed(() => {
|
||||
const roles = new Set(coop.members.value.map((m) => m.role || ""));
|
||||
return Array.from(roles).map((role) => ({ role }));
|
||||
});
|
||||
// Removed uniqueRoles computed - no longer needed with simplified policies
|
||||
|
||||
function updateCurrency(value: string) {
|
||||
selectedCurrency.value = value;
|
||||
coop.setCurrency(value);
|
||||
emit("save-status", "saved");
|
||||
}
|
||||
|
||||
function updatePolicy(value: string) {
|
||||
selectedPolicy.value = value;
|
||||
coop.setPolicy(
|
||||
value as "equal-pay" | "needs-weighted" | "hours-weighted" | "role-banded"
|
||||
);
|
||||
coop.setPolicy(value as "equal-pay" | "needs-weighted" | "hours-weighted");
|
||||
|
||||
// Trigger payroll reallocation after policy change
|
||||
const allocatedMembers = coop.allocatePayroll();
|
||||
|
|
@ -154,19 +143,7 @@ function updatePolicy(value: string) {
|
|||
emit("save-status", "saved");
|
||||
}
|
||||
|
||||
function updateRoleBands() {
|
||||
coop.setRoleBands(roleBands.value);
|
||||
|
||||
// Trigger payroll reallocation after role bands change
|
||||
if (selectedPolicy.value === "role-banded") {
|
||||
const allocatedMembers = coop.allocatePayroll();
|
||||
allocatedMembers.forEach((m) => {
|
||||
coop.upsertMember(m);
|
||||
});
|
||||
}
|
||||
|
||||
emit("save-status", "saved");
|
||||
}
|
||||
// Removed updateRoleBands - no longer needed with simplified policies
|
||||
|
||||
// Text input for wage with validation (initialized above)
|
||||
|
||||
|
|
@ -188,28 +165,4 @@ function validateAndSaveWage(value: string) {
|
|||
emit("save-status", "saved");
|
||||
}
|
||||
}
|
||||
|
||||
function exportPolicies() {
|
||||
const exportData = {
|
||||
policies: {
|
||||
selectedPolicy: coop.policy.value?.relationship || selectedPolicy.value,
|
||||
roleBands: coop.policy.value?.roleBands || roleBands.value,
|
||||
equalHourlyWage: store.equalHourlyWage || parseFloat(wageText.value),
|
||||
},
|
||||
exportedAt: new Date().toISOString(),
|
||||
section: "policies",
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `coop-policies-${new Date().toISOString().split("T")[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue