Removed tax settings

This commit is contained in:
Spencer Twaddle
2026-05-03 07:20:19 -05:00
parent 89759abcca
commit f3fe1ea146
14 changed files with 319 additions and 126 deletions
+1 -14
View File
@@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { api } from './client';
import type { SummaryDto } from '../types/index';
@@ -8,16 +8,3 @@ export function useSummary(budgetId: string) {
queryFn: () => api.get<SummaryDto>(`/api/budgets/${budgetId}/summary`),
});
}
export function useUpdateTaxRate(budgetId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: (effectiveTaxRate: number) =>
api.put<void>(`/api/budgets/${budgetId}/summary/tax-rate`, { effectiveTaxRate }),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['budgets', budgetId, 'summary'] });
qc.invalidateQueries({ queryKey: ['budgets', budgetId] });
},
meta: { successMessage: 'Tax rate saved', errorMessage: 'Failed to save tax rate' },
});
}
+5 -46
View File
@@ -6,14 +6,11 @@ import type { SharePermission } from '../types/index';
import { Trash2 } from 'lucide-react';
import { BudgetNav } from '../components/BudgetNav';
import { useBudget, useUpdateBudget } from '../api/budgets';
import { useUpdateTaxRate } from '../api/summary';
import { useShares, useAddShare, useUpdateShare, useRevokeShare } from '../api/shares';
import {
createBudgetSchema,
updateTaxRateSchema,
createShareSchema,
type CreateBudgetInput,
type UpdateTaxRateInput,
type CreateShareInput,
} from '../schemas/index';
@@ -23,8 +20,7 @@ export function SettingsPage() {
const { data: shares = [] } = useShares(budgetId!);
const updateBudget = useUpdateBudget(budgetId!);
const updateTaxRate = useUpdateTaxRate(budgetId!);
const addShare = useAddShare(budgetId!);
const addShare = useAddShare(budgetId!);
const updateShare = useUpdateShare(budgetId!);
const revokeShare = useRevokeShare(budgetId!);
@@ -33,31 +29,19 @@ export function SettingsPage() {
defaultValues: { name: '' },
});
const taxForm = useForm<UpdateTaxRateInput>({
resolver: zodResolver(updateTaxRateSchema),
defaultValues: { effectiveTaxRate: 0 },
});
const shareForm = useForm<CreateShareInput>({
resolver: zodResolver(createShareSchema),
defaultValues: { email: '', permission: 'View' },
});
useEffect(() => {
if (budget) {
renameForm.reset({ name: budget.name });
taxForm.reset({ effectiveTaxRate: Math.round(budget.effectiveTaxRate * 100) });
}
if (budget) renameForm.reset({ name: budget.name });
}, [budget]); // eslint-disable-line react-hooks/exhaustive-deps
const onRename = async (data: CreateBudgetInput) => {
await updateBudget.mutateAsync({ name: data.name });
};
const onSaveTaxRate = async (data: UpdateTaxRateInput) => {
await updateTaxRate.mutateAsync(data.effectiveTaxRate / 100);
};
const onAddShare = async (data: CreateShareInput) => {
await addShare.mutateAsync({ email: data.email, permission: data.permission });
shareForm.reset({ email: '', permission: 'View' });
@@ -90,33 +74,6 @@ export function SettingsPage() {
</form>
</div>
<div className="card" style={{ maxWidth: '480px' }}>
<div className="section-title">Effective Tax Rate</div>
<form onSubmit={taxForm.handleSubmit(onSaveTaxRate)}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}>
<label htmlFor="tax-rate-settings">Rate (%)</label>
<input
id="tax-rate-settings"
type="number"
min="0"
max="99"
style={{ width: '72px' }}
{...taxForm.register('effectiveTaxRate', { valueAsNumber: true })}
/>
<button
type="submit"
className="btn btn-primary btn-sm"
disabled={taxForm.formState.isSubmitting || updateTaxRate.isPending}
>
Save
</button>
</div>
{taxForm.formState.errors.effectiveTaxRate && (
<span className="field-error">{taxForm.formState.errors.effectiveTaxRate.message}</span>
)}
</form>
</div>
<div className="card">
<div className="section-title">Sharing</div>
@@ -150,7 +107,9 @@ export function SettingsPage() {
: <span className="badge badge-save">Active</span>}
</td>
<td className="col-actions">
<button className="btn btn-danger btn-icon" title="Revoke access" onClick={() => revokeShare.mutate(s.id)}><Trash2 size={15} /></button>
<button className="btn btn-danger btn-icon" title="Revoke access" onClick={() => revokeShare.mutate(s.id)}>
<Trash2 size={15} />
</button>
</td>
</tr>
))}
-6
View File
@@ -38,13 +38,7 @@ export const createShareSchema = z.object({
permission: z.enum(['View', 'Edit']),
});
// UI shows percent (0-99); callers divide by 100 before sending to API
export const updateTaxRateSchema = z.object({
effectiveTaxRate: z.number().min(0, 'Must be ≥ 0').max(99, 'Must be less than 100'),
});
export type CreateBudgetInput = z.infer<typeof createBudgetSchema>;
export type CreateIncomeInput = z.infer<typeof createIncomeSchema>;
export type CreateOutgoInput = z.infer<typeof createOutgoSchema>;
export type CreateShareInput = z.infer<typeof createShareSchema>;
export type UpdateTaxRateInput = z.infer<typeof updateTaxRateSchema>;
-6
View File
@@ -43,7 +43,6 @@ export interface OutgoDto {
export interface BudgetDto {
id: string;
name: string;
effectiveTaxRate: number;
createdAt: string;
updatedAt: string;
}
@@ -70,9 +69,4 @@ export interface SummaryDto {
monthlyIncome: number;
annualIncome: number;
breakdown: SummaryBreakdownItem[];
preTaxIncome: {
effectiveTaxRate: number;
minimumAnnualGross: number;
minimumMonthlyGross: number;
};
}