buckets/resources/js/components/SettingsPanel.tsx

142 lines
5.4 KiB
TypeScript

import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
import { csrfToken } from '@/lib/utils';
import { type SharedData } from '@/types';
import { router, usePage } from '@inertiajs/react';
import { useState } from 'react';
type SaveStatus = 'idle' | 'saving' | 'success' | 'error';
type DistributionMode = 'even' | 'priority';
interface DistributionOption {
value: DistributionMode;
label: string;
description: string;
}
const distributionOptions: DistributionOption[] = [
{
value: 'even',
label: 'Even Split',
description:
'Split evenly across buckets in each phase, respecting individual capacity',
},
{
value: 'priority',
label: 'Priority Order',
description: 'Fill highest-priority bucket first, then next',
},
];
interface SettingsPanelProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export default function SettingsPanel({
open,
onOpenChange,
}: SettingsPanelProps) {
const { scenario } = usePage<SharedData>().props;
const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
if (!scenario) return null;
const handleDistributionModeChange = async (value: DistributionMode) => {
if (value === scenario.distribution_mode) return;
setSaveStatus('saving');
try {
const response = await fetch(`/scenarios/${scenario.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken(),
},
body: JSON.stringify({ distribution_mode: value }),
});
if (!response.ok) throw new Error('Failed to update');
setSaveStatus('success');
router.reload({ only: ['scenario', 'buckets'] });
setTimeout(() => setSaveStatus('idle'), 1500);
} catch {
setSaveStatus('error');
setTimeout(() => setSaveStatus('idle'), 1500);
}
};
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="top" className="px-6 pb-6">
<SheetHeader>
<SheetTitle>Settings</SheetTitle>
</SheetHeader>
<div className="mx-auto max-w-2xl">
<fieldset disabled={saveStatus === 'saving'}>
<legend className="text-sm font-medium text-foreground">
Distribution Mode
{saveStatus === 'success' && (
<span className="ml-2 text-green-600">
Saved
</span>
)}
{saveStatus === 'error' && (
<span className="ml-2 text-red-600">
Failed to save
</span>
)}
</legend>
<p className="mt-1 text-sm text-muted-foreground">
How income is divided across buckets within each
phase
</p>
<div className="mt-3 space-y-2">
{distributionOptions.map((option) => (
<label
key={option.value}
className={`flex cursor-pointer items-start gap-3 rounded-lg border p-3 transition-colors ${
scenario.distribution_mode ===
option.value
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50'
}`}
>
<input
type="radio"
name="distribution_mode"
value={option.value}
checked={
scenario.distribution_mode ===
option.value
}
onChange={(e) =>
handleDistributionModeChange(
e.target.value as DistributionMode,
)
}
className="mt-0.5"
/>
<div>
<div className="text-sm font-medium">
{option.label}
</div>
<div className="text-sm text-muted-foreground">
{option.description}
</div>
</div>
</label>
))}
</div>
</fieldset>
</div>
</SheetContent>
</Sheet>
);
}