2 - Add distribute button and wire up preview/apply endpoints
This commit is contained in:
parent
7938677950
commit
5ac8d48727
2 changed files with 99 additions and 2 deletions
|
|
@ -6,7 +6,7 @@ import InlineEditInput from '@/components/InlineEditInput';
|
|||
import InlineEditSelect from '@/components/InlineEditSelect';
|
||||
import SettingsPanel from '@/components/SettingsPanel';
|
||||
import { csrfToken } from '@/lib/utils';
|
||||
import { type Bucket, type Scenario } from '@/types';
|
||||
import { type Bucket, type DistributionPreview, type Scenario } from '@/types';
|
||||
|
||||
interface Props {
|
||||
scenario: Scenario;
|
||||
|
|
@ -60,10 +60,75 @@ export default function Show({ scenario, buckets }: Props) {
|
|||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [formData, setFormData] = useState({ ...defaultFormData });
|
||||
const [incomeAmount, setIncomeAmount] = useState('');
|
||||
const [distribution, setDistribution] = useState<DistributionPreview | null>(null);
|
||||
const [isDistributing, setIsDistributing] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const incomeRef = useRef<HTMLDivElement>(null);
|
||||
const bucketRefs = useRef<Map<string, HTMLElement>>(new Map());
|
||||
|
||||
const handleDistribute = async () => {
|
||||
const dollars = parseFloat(incomeAmount);
|
||||
if (!dollars || dollars <= 0) return;
|
||||
|
||||
setIsDistributing(true);
|
||||
try {
|
||||
const response = await fetch(`/scenarios/${scenario.id}/projections/preview`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken(),
|
||||
},
|
||||
body: JSON.stringify({ amount: dollarsToCents(dollars) }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setDistribution(await response.json());
|
||||
} else {
|
||||
console.error('Distribution preview failed:', response.status, await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching distribution preview:', error);
|
||||
} finally {
|
||||
setIsDistributing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveDistribution = async () => {
|
||||
const dollars = parseFloat(incomeAmount);
|
||||
if (!dollars || dollars <= 0 || !distribution) return;
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const response = await fetch(`/scenarios/${scenario.id}/projections/apply`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken(),
|
||||
},
|
||||
body: JSON.stringify({ amount: dollarsToCents(dollars) }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setDistribution(null);
|
||||
setIncomeAmount('');
|
||||
router.reload({ only: ['buckets'] });
|
||||
} else {
|
||||
console.error('Apply distribution failed:', response.status, await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error applying distribution:', error);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIncomeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIncomeAmount(e.target.value);
|
||||
setDistribution(null);
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
setFormData({ ...defaultFormData });
|
||||
setIsCreateModalOpen(true);
|
||||
|
|
@ -228,14 +293,32 @@ export default function Show({ scenario, buckets }: Props) {
|
|||
INCOME
|
||||
</h2>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-center space-y-4">
|
||||
<input
|
||||
type="number"
|
||||
value={incomeAmount}
|
||||
onChange={handleIncomeChange}
|
||||
className="w-48 bg-black border-2 border-red-500/50 px-4 py-2 text-center font-digital text-2xl text-red-500 focus:border-red-500 focus:outline-none"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
<button
|
||||
onClick={handleDistribute}
|
||||
disabled={!incomeAmount || parseFloat(incomeAmount) <= 0 || isDistributing}
|
||||
className="block mx-auto border-2 border-red-500 bg-black px-6 py-2 text-xs font-mono font-bold uppercase tracking-wider text-red-500 hover:bg-red-500 hover:text-black transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isDistributing ? 'CALCULATING...' : 'DISTRIBUTE'}
|
||||
</button>
|
||||
{distribution && (
|
||||
<button
|
||||
onClick={handleSaveDistribution}
|
||||
disabled={isSaving}
|
||||
className="block mx-auto border-2 border-red-500 bg-red-500 px-6 py-2 text-xs font-mono font-bold uppercase tracking-wider text-black hover:bg-red-400 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isSaving ? 'SAVING...' : 'SAVE'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
14
resources/js/types/index.d.ts
vendored
14
resources/js/types/index.d.ts
vendored
|
|
@ -50,6 +50,20 @@ export interface Scenario {
|
|||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface AllocationPreview {
|
||||
bucket_id: string;
|
||||
bucket_name: string;
|
||||
bucket_type: string;
|
||||
allocated_amount: number;
|
||||
remaining_capacity: number | null;
|
||||
}
|
||||
|
||||
export interface DistributionPreview {
|
||||
allocations: AllocationPreview[];
|
||||
total_allocated: number;
|
||||
unallocated: number;
|
||||
}
|
||||
|
||||
export interface SharedData {
|
||||
name: string;
|
||||
quote: { message: string; author: string };
|
||||
|
|
|
|||
Loading…
Reference in a new issue