164 lines
9.4 KiB
TypeScript
164 lines
9.4 KiB
TypeScript
import React from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/Components/ui/card';
|
|
import { Button } from '@/Components/ui/button';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/Components/ui/table';
|
|
import { FileSpreadsheet, FileDown, AlertTriangle, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
import { ProjectWizardData } from '../../../types/project-wizard';
|
|
|
|
interface Step6EstimationProps {
|
|
project: ProjectWizardData;
|
|
isUnprofitableEst: boolean;
|
|
calculatedMaterialsCost: number;
|
|
calculatedLaborCost: number;
|
|
calculatedEquipmentCost: number;
|
|
totalEstimatedCost: number;
|
|
grossMarginVal: number;
|
|
grossMarginPct: number;
|
|
contractValueNum: number;
|
|
formatCurrency: (v: string | number) => string;
|
|
handleBack: () => void;
|
|
onNext: () => void;
|
|
}
|
|
|
|
export default function Step6Estimation({
|
|
project,
|
|
isUnprofitableEst,
|
|
calculatedMaterialsCost,
|
|
calculatedLaborCost,
|
|
calculatedEquipmentCost,
|
|
totalEstimatedCost,
|
|
grossMarginVal,
|
|
grossMarginPct,
|
|
contractValueNum,
|
|
formatCurrency,
|
|
handleBack,
|
|
onNext
|
|
}: Step6EstimationProps) {
|
|
return (
|
|
<div className="p-6 space-y-6">
|
|
<CardHeader className="p-0 pb-4 border-b border-slate-100 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<div>
|
|
<CardTitle className="text-md font-semibold text-slate-800">Step 6: Financial Estimation Margin Summary</CardTitle>
|
|
<CardDescription className="text-xs">Review total projected costs, contract margins, and flag checks before locking for approval.</CardDescription>
|
|
</div>
|
|
<div className="flex items-center gap-2 shrink-0">
|
|
<a href={route('projects.wizard.export.excel', project.ulid)} target="_blank" rel="noopener noreferrer">
|
|
<Button type="button" variant="outline" size="sm" className="h-8 border-slate-200 text-slate-600 hover:bg-slate-50 flex items-center gap-1.5 font-semibold">
|
|
<FileSpreadsheet className="h-4 w-4 text-emerald-600" />
|
|
Download Excel
|
|
</Button>
|
|
</a>
|
|
<a href={route('projects.wizard.export.pdf', project.ulid)} target="_blank" rel="noopener noreferrer">
|
|
<Button type="button" variant="outline" size="sm" className="h-8 border-slate-200 text-slate-650 hover:bg-slate-50 flex items-center gap-1.5 font-semibold">
|
|
<FileDown className="h-4 w-4 text-rose-600" />
|
|
Download PDF
|
|
</Button>
|
|
</a>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
{/* Gross Margin Health Banner */}
|
|
{isUnprofitableEst && (
|
|
<div className="p-4 rounded-xl border border-amber-200 bg-amber-50/70 text-amber-900 flex items-start gap-3 shadow-sm">
|
|
<AlertTriangle className="h-5 w-5 text-amber-600 shrink-0 mt-0.5" />
|
|
<div>
|
|
<h4 className="font-semibold text-sm">Special Handle: Profit Margin Risk / Overrun Warn</h4>
|
|
<p className="text-xs text-amber-700 mt-1">
|
|
This project's estimated costs exceed its contract value, resulting in a negative projected margin.
|
|
The unprofitable warning will flag this project in procurement logs and approval cycles.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<Card className="bg-slate-50/40">
|
|
<CardContent className="p-4">
|
|
<span className="text-[10px] uppercase font-bold tracking-wider text-slate-450">Contract Value</span>
|
|
<p className="text-lg font-bold text-slate-900 mt-1">{formatCurrency(project.contract_value)}</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50/40">
|
|
<CardContent className="p-4">
|
|
<span className="text-[10px] uppercase font-bold tracking-wider text-slate-450">Total Est. Cost</span>
|
|
<p className="text-lg font-bold text-slate-900 mt-1">{formatCurrency(totalEstimatedCost)}</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50/40">
|
|
<CardContent className="p-4">
|
|
<span className="text-[10px] uppercase font-bold tracking-wider text-slate-450">Projected Margin</span>
|
|
<p className={`text-lg font-bold mt-1 ${grossMarginVal >= 0 ? 'text-emerald-700' : 'text-rose-600'}`}>
|
|
{formatCurrency(grossMarginVal)}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="bg-slate-50/40">
|
|
<CardContent className="p-4">
|
|
<span className="text-[10px] uppercase font-bold tracking-wider text-slate-450">Margin %</span>
|
|
<p className={`text-lg font-bold mt-1 ${grossMarginVal >= 0 ? 'text-emerald-700' : 'text-rose-600'}`}>
|
|
{grossMarginPct.toFixed(2)}%
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Itemized Breakdown Card */}
|
|
<Card className="border-slate-200 shadow-none">
|
|
<CardHeader className="bg-slate-50/40 py-3 border-b border-slate-250">
|
|
<CardTitle className="text-xs font-bold uppercase tracking-wider text-slate-650">Itemized Cost Breakdown</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead className="font-semibold text-slate-700">Cost Center</TableHead>
|
|
<TableHead className="font-semibold text-slate-700 text-right">Estimated Cost</TableHead>
|
|
<TableHead className="font-semibold text-slate-700 text-right">% of Contract</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
<TableRow>
|
|
<TableCell className="font-medium text-slate-900">Materials Cost (Step 3)</TableCell>
|
|
<TableCell className="font-mono text-right text-slate-700">{formatCurrency(calculatedMaterialsCost)}</TableCell>
|
|
<TableCell className="text-right text-slate-500">
|
|
{contractValueNum > 0 ? ((calculatedMaterialsCost / contractValueNum) * 100).toFixed(1) : 0}%
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableCell className="font-medium text-slate-900">Labor Manpower Cost (Step 4)</TableCell>
|
|
<TableCell className="font-mono text-right text-slate-700">{formatCurrency(calculatedLaborCost)}</TableCell>
|
|
<TableCell className="text-right text-slate-500">
|
|
{contractValueNum > 0 ? ((calculatedLaborCost / contractValueNum) * 100).toFixed(1) : 0}%
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow>
|
|
<TableCell className="font-medium text-slate-900">Equipment Machinery Cost (Step 5)</TableCell>
|
|
<TableCell className="font-mono text-right text-slate-700">{formatCurrency(calculatedEquipmentCost)}</TableCell>
|
|
<TableCell className="text-right text-slate-500">
|
|
{contractValueNum > 0 ? ((calculatedEquipmentCost / contractValueNum) * 100).toFixed(1) : 0}%
|
|
</TableCell>
|
|
</TableRow>
|
|
<TableRow className="bg-slate-50/50 hover:bg-slate-50/50">
|
|
<TableCell className="font-bold text-slate-800">Total Summed Estimation</TableCell>
|
|
<TableCell className="font-mono font-bold text-slate-900 text-right">{formatCurrency(totalEstimatedCost)}</TableCell>
|
|
<TableCell className="font-bold text-right text-slate-700">
|
|
{contractValueNum > 0 ? ((totalEstimatedCost / contractValueNum) * 100).toFixed(1) : 0}%
|
|
</TableCell>
|
|
</TableRow>
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="flex justify-between pt-6 border-t border-slate-100">
|
|
<Button variant="outline" onClick={handleBack}>
|
|
<ChevronLeft className="mr-2 h-4 w-4" /> Back
|
|
</Button>
|
|
<Button onClick={onNext} className="bg-emerald-600 hover:bg-emerald-700 text-white font-medium">
|
|
Next: Submit Approval <ChevronRight className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|