app/components/BudgetSettingsModal.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>