2026-03-22 01:08:44 +01:00
|
|
|
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',
|
2026-03-22 02:59:40 +01:00
|
|
|
label: 'EVEN SPLIT',
|
2026-03-22 01:08:44 +01:00
|
|
|
description:
|
|
|
|
|
'Split evenly across buckets in each phase, respecting individual capacity',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
value: 'priority',
|
2026-03-22 02:59:40 +01:00
|
|
|
label: 'PRIORITY ORDER',
|
2026-03-22 01:08:44 +01:00
|
|
|
description: 'Fill highest-priority bucket first, then next',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
interface SettingsPanelProps {
|
|
|
|
|
onOpenChange: (open: boolean) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function SettingsPanel({
|
|
|
|
|
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 (
|
2026-03-22 02:59:40 +01:00
|
|
|
<div className="mb-8 border-4 border-red-500 bg-black p-6 glow-red">
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
<h2 className="text-lg font-mono font-bold tracking-wider uppercase text-red-500">
|
|
|
|
|
SETTINGS
|
|
|
|
|
</h2>
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => onOpenChange(false)}
|
|
|
|
|
className="text-red-500/60 hover:text-red-500 font-mono text-sm transition-colors"
|
|
|
|
|
>
|
|
|
|
|
CLOSE
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-03-22 01:08:44 +01:00
|
|
|
|
2026-03-22 02:59:40 +01:00
|
|
|
<fieldset disabled={saveStatus === 'saving'}>
|
|
|
|
|
<legend className="text-xs font-mono uppercase text-red-500/60">
|
|
|
|
|
DISTRIBUTION MODE
|
|
|
|
|
{saveStatus === 'success' && (
|
|
|
|
|
<span className="ml-2 text-green-500">SAVED</span>
|
|
|
|
|
)}
|
|
|
|
|
{saveStatus === 'error' && (
|
|
|
|
|
<span className="ml-2 text-red-500">FAILED</span>
|
|
|
|
|
)}
|
|
|
|
|
</legend>
|
|
|
|
|
<div className="mt-3 space-y-2">
|
|
|
|
|
{distributionOptions.map((option) => (
|
|
|
|
|
<label
|
|
|
|
|
key={option.value}
|
|
|
|
|
className={`flex cursor-pointer items-start gap-3 border-2 p-3 transition-colors ${
|
|
|
|
|
scenario.distribution_mode === option.value
|
|
|
|
|
? 'border-red-500 bg-red-500/10'
|
|
|
|
|
: 'border-red-500/30 hover:border-red-500/60'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<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 accent-red-500"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<div className="text-sm font-mono font-bold text-red-500">
|
|
|
|
|
{option.label}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs font-mono text-red-500/60">
|
|
|
|
|
{option.description}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
2026-03-22 01:08:44 +01:00
|
|
|
</div>
|
2026-03-22 02:59:40 +01:00
|
|
|
</fieldset>
|
|
|
|
|
</div>
|
2026-03-22 01:08:44 +01:00
|
|
|
);
|
|
|
|
|
}
|