208 lines
No EOL
6.5 KiB
Vue
208 lines
No EOL
6.5 KiB
Vue
<template>
|
|
<UModal
|
|
v-model:open="isOpen"
|
|
title="Budget Settings"
|
|
description="Configure payroll taxes and cash flow settings"
|
|
:dismissible="true">
|
|
<template #body>
|
|
<div class="space-y-8">
|
|
<!-- Payroll Tax Settings -->
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-black dark:text-white mb-4">
|
|
Payroll Tax Percentage
|
|
</h3>
|
|
<p class="text-sm text-neutral-600 dark:text-neutral-400 mb-4">
|
|
Set the percentage added to base payroll for taxes, benefits, and other oncosts.
|
|
</p>
|
|
|
|
<div class="space-y-4">
|
|
<UFormField label="Tax Percentage">
|
|
<UInput
|
|
v-model="newOncostPct"
|
|
type="number"
|
|
placeholder="Enter percentage"
|
|
min="0"
|
|
max="100"
|
|
step="0.1"
|
|
size="lg"
|
|
class="text-lg">
|
|
<template #trailing>
|
|
<span class="text-neutral-500 text-lg">%</span>
|
|
</template>
|
|
</UInput>
|
|
</UFormField>
|
|
|
|
<!-- Quick selection buttons -->
|
|
<div class="flex flex-wrap gap-2">
|
|
<span class="text-sm text-neutral-600 dark:text-neutral-400 mr-2">Quick select:</span>
|
|
<UButton
|
|
v-for="range in commonOncostRanges"
|
|
:key="range.value"
|
|
@click="newOncostPct = range.value"
|
|
size="xs"
|
|
variant="outline"
|
|
:ui="{ base: 'hover:bg-neutral-100 dark:hover:bg-neutral-800' }">
|
|
{{ range.value }}%
|
|
</UButton>
|
|
</div>
|
|
|
|
<!-- Preview -->
|
|
<div class="p-3 bg-neutral-100 dark:bg-neutral-800 rounded">
|
|
<p class="text-sm text-neutral-700 dark:text-neutral-300">
|
|
<strong>Example:</strong> €100 base payroll + {{ newOncostPct }}% tax =
|
|
<strong>€{{ Math.round(100 * (1 + newOncostPct / 100)) }} total cost</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Minimum Cash Threshold Settings -->
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-black dark:text-white mb-4">
|
|
Minimum Cash Threshold
|
|
</h3>
|
|
<p class="text-sm text-neutral-600 dark:text-neutral-400 mb-4">
|
|
Set the minimum cash balance that your co-op should never go below. The budget will automatically reduce payroll allocations if needed to maintain this threshold.
|
|
</p>
|
|
|
|
<div class="space-y-4">
|
|
<UFormField label="Minimum Cash Balance">
|
|
<UInput
|
|
v-model="newMinCashThreshold"
|
|
type="number"
|
|
placeholder="Enter minimum amount"
|
|
min="0"
|
|
step="100"
|
|
size="lg"
|
|
class="text-lg">
|
|
<template #leading>
|
|
<span class="text-neutral-500 text-lg">{{ getCurrencySymbol(coopStore.currency) }}</span>
|
|
</template>
|
|
</UInput>
|
|
</UFormField>
|
|
|
|
<!-- Quick selection buttons -->
|
|
<div class="flex flex-wrap gap-2">
|
|
<span class="text-sm text-neutral-600 dark:text-neutral-400 mr-2">Quick select:</span>
|
|
<UButton
|
|
v-for="amount in commonCashThresholds"
|
|
:key="amount"
|
|
@click="newMinCashThreshold = amount"
|
|
size="xs"
|
|
variant="outline"
|
|
:ui="{ base: 'hover:bg-neutral-100 dark:hover:bg-neutral-800' }">
|
|
{{ getCurrencySymbol(coopStore.currency) }}{{ formatAmount(amount) }}
|
|
</UButton>
|
|
</div>
|
|
|
|
<!-- Preview -->
|
|
<div class="p-3 bg-neutral-100 dark:bg-neutral-800 rounded">
|
|
<p class="text-sm text-neutral-700 dark:text-neutral-300">
|
|
<strong>Safety buffer:</strong> Your co-op will maintain at least
|
|
<strong>{{ getCurrencySymbol(coopStore.currency) }}{{ formatAmount(newMinCashThreshold) }}</strong>
|
|
in cash at all times.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #footer>
|
|
<div class="flex justify-between items-center w-full">
|
|
<UButton
|
|
@click="resetToDefaults"
|
|
variant="ghost"
|
|
color="neutral"
|
|
:ui="{ base: 'hover:bg-neutral-100 dark:hover:bg-neutral-800' }">
|
|
Reset to Defaults
|
|
</UButton>
|
|
|
|
<div class="flex gap-2">
|
|
<UButton
|
|
@click="isOpen = false"
|
|
variant="outline"
|
|
color="neutral">
|
|
Cancel
|
|
</UButton>
|
|
<UButton
|
|
@click="saveSettings"
|
|
:disabled="!isValidSettings"
|
|
color="primary">
|
|
Save Settings
|
|
</UButton>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UModal>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { getCurrencySymbol } from "~/utils/currency";
|
|
|
|
// Props
|
|
const isOpen = defineModel<boolean>("open", { default: false });
|
|
|
|
// Emit events for parent component
|
|
const emit = defineEmits<{
|
|
"settings-updated": [];
|
|
}>();
|
|
|
|
// Stores
|
|
const coopStore = useCoopBuilderStore();
|
|
|
|
// Reactive form data
|
|
const newOncostPct = ref(coopStore.payrollOncostPct || 25);
|
|
const newMinCashThreshold = ref(coopStore.minCashThreshold || 0);
|
|
|
|
// Common ranges for quick selection
|
|
const commonOncostRanges = [
|
|
{ value: 0 },
|
|
{ value: 15 },
|
|
{ value: 20 },
|
|
{ value: 25 },
|
|
{ value: 30 },
|
|
{ value: 35 }
|
|
];
|
|
|
|
const commonCashThresholds = [0, 1000, 2500, 5000, 7500, 10000];
|
|
|
|
// Computed properties
|
|
const isValidSettings = computed(() =>
|
|
newOncostPct.value >= 0 &&
|
|
newOncostPct.value <= 100 &&
|
|
newMinCashThreshold.value >= 0
|
|
);
|
|
|
|
// Utility function to format amounts
|
|
function formatAmount(amount: number): string {
|
|
return new Intl.NumberFormat().format(amount);
|
|
}
|
|
|
|
// Reset form when modal opens
|
|
watch(() => isOpen.value, (open) => {
|
|
if (open) {
|
|
newOncostPct.value = coopStore.payrollOncostPct || 25;
|
|
newMinCashThreshold.value = coopStore.minCashThreshold || 0;
|
|
}
|
|
});
|
|
|
|
function resetToDefaults() {
|
|
newOncostPct.value = 25;
|
|
newMinCashThreshold.value = 0;
|
|
}
|
|
|
|
function saveSettings() {
|
|
// Update payroll oncost percentage
|
|
coopStore.payrollOncostPct = newOncostPct.value;
|
|
|
|
// Update minimum cash threshold
|
|
coopStore.setMinCashThreshold(newMinCashThreshold.value);
|
|
|
|
// Close modal
|
|
isOpen.value = false;
|
|
|
|
// Emit event to parent to refresh calculations
|
|
emit("settings-updated");
|
|
}
|
|
</script> |