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 InlineEditSelect from '@/components/InlineEditSelect';
|
||||||
import SettingsPanel from '@/components/SettingsPanel';
|
import SettingsPanel from '@/components/SettingsPanel';
|
||||||
import { csrfToken } from '@/lib/utils';
|
import { csrfToken } from '@/lib/utils';
|
||||||
import { type Bucket, type Scenario } from '@/types';
|
import { type Bucket, type DistributionPreview, type Scenario } from '@/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
scenario: Scenario;
|
scenario: Scenario;
|
||||||
|
|
@ -60,10 +60,75 @@ export default function Show({ scenario, buckets }: Props) {
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [formData, setFormData] = useState({ ...defaultFormData });
|
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 containerRef = useRef<HTMLDivElement>(null);
|
||||||
const incomeRef = useRef<HTMLDivElement>(null);
|
const incomeRef = useRef<HTMLDivElement>(null);
|
||||||
const bucketRefs = useRef<Map<string, HTMLElement>>(new Map());
|
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 = () => {
|
const openCreateModal = () => {
|
||||||
setFormData({ ...defaultFormData });
|
setFormData({ ...defaultFormData });
|
||||||
setIsCreateModalOpen(true);
|
setIsCreateModalOpen(true);
|
||||||
|
|
@ -228,14 +293,32 @@ export default function Show({ scenario, buckets }: Props) {
|
||||||
INCOME
|
INCOME
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center space-y-4">
|
||||||
<input
|
<input
|
||||||
type="number"
|
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"
|
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"
|
placeholder="0"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
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>
|
</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;
|
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 {
|
export interface SharedData {
|
||||||
name: string;
|
name: string;
|
||||||
quote: { message: string; author: string };
|
quote: { message: string; author: string };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue