pattanshetti / src /pages /InvoiceEnhanced.tsx
triflix's picture
Upload 99 files
4be2b2b verified
import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { ArrowLeft, Save, Printer, AlertCircle } from "lucide-react";
import { Link, useNavigate } from "react-router-dom";
import { useToast } from "@/hooks/use-toast";
import { parties, billBooks, lots, getNextBillNumber, getAvailableLotsByVariety } from "@/lib/mockData";
import type { Lot } from "@/types";
import { useLanguage } from "@/contexts/LanguageContext";
import { LanguageToggle } from "@/components/LanguageToggle";
export default function InvoiceEnhanced() {
const { toast } = useToast();
const navigate = useNavigate();
const { t } = useLanguage();
const [formData, setFormData] = useState({
partyId: "",
partyName: "",
date: new Date().toISOString().split('T')[0],
serialNumber: "",
billBookId: "",
particular: "",
lotNumber: "",
bags: 0,
grossWeight: 40,
rate: 0,
});
const [charges, setCharges] = useState({
bardhana: true,
hamali: false,
adhath: 0,
cess: 2,
gaadiBharni: 0,
});
const [availableLots, setAvailableLots] = useState<Lot[]>([]);
const [selectedLot, setSelectedLot] = useState<Lot | null>(null);
const [showBillWarning, setShowBillWarning] = useState(false);
// Auto-generate bill number from active bill book
useEffect(() => {
try {
const { billBookId, serialNumber } = getNextBillNumber();
setFormData(prev => ({
...prev,
billBookId,
serialNumber,
}));
} catch (error: any) {
toast({
title: "त्रुटी",
description: error.message,
variant: "destructive",
});
}
}, []);
// Load available lots when variety is selected
useEffect(() => {
if (formData.particular) {
const lots = getAvailableLotsByVariety(formData.particular);
setAvailableLots(lots);
if (lots.length === 0) {
toast({
title: "माहिती",
description: `${formData.particular} साठी कोणताही लॉट उपलब्ध नाही`,
});
}
} else {
setAvailableLots([]);
setSelectedLot(null);
}
}, [formData.particular]);
// Load lot details when lot is selected
useEffect(() => {
if (formData.lotNumber) {
const lot = availableLots.find(l => l.lotNumber === formData.lotNumber);
setSelectedLot(lot || null);
if (lot) {
setFormData(prev => ({
...prev,
grossWeight: lot.grossWeightPerBag,
rate: lot.purchaseRate,
}));
}
}
}, [formData.lotNumber, availableLots]);
const calculateNetWeight = () => {
const bags = parseFloat(formData.bags.toString()) || 0;
const grossPerBag = parseFloat(formData.grossWeight.toString()) || 40;
return bags * (grossPerBag - 1);
};
const calculateBasicAmount = () => {
const netWeight = calculateNetWeight();
const rate = parseFloat(formData.rate.toString()) || 0;
return netWeight * rate;
};
const calculateCharges = () => {
const bags = parseFloat(formData.bags.toString()) || 0;
const basicAmount = calculateBasicAmount();
let total = basicAmount;
const breakdown = {
bardhana: charges.bardhana ? bags * 18 : 0,
hamali: charges.hamali ? bags * 6 : 0,
adhath: (charges.adhath / 100) * basicAmount,
cess: (charges.cess / 100) * basicAmount,
gaadiBharni: parseFloat(charges.gaadiBharni.toString()) || 0,
};
total += breakdown.bardhana + breakdown.hamali + breakdown.adhath + breakdown.cess + breakdown.gaadiBharni;
return { breakdown, total };
};
const handleSave = () => {
// Validation
if (!formData.partyId) {
toast({
title: "त्रुटी",
description: "कृपया पार्टी निवडा",
variant: "destructive",
});
return;
}
if (!formData.particular || !formData.lotNumber) {
toast({
title: "त्रुटी",
description: "कृपया जात आणि लॉट नंबर निवडा",
variant: "destructive",
});
return;
}
if (formData.bags <= 0) {
toast({
title: "त्रुटी",
description: "कृपया पोत्यांची संख्या भरा",
variant: "destructive",
});
return;
}
// Check if enough bags available in lot
if (selectedLot && formData.bags > selectedLot.availableBags) {
toast({
title: "त्रुटी",
description: `फक्त ${selectedLot.availableBags} पोत्या उपलब्ध आहेत`,
variant: "destructive",
});
return;
}
const { breakdown, total } = calculateCharges();
const invoiceData = {
...formData,
items: [{
particular: formData.particular,
lotNumber: formData.lotNumber,
bags: formData.bags,
grossWeightPerBag: formData.grossWeight,
netWeightPerBag: formData.grossWeight - 1,
rate: formData.rate,
basicAmount: calculateBasicAmount(),
}],
charges: {
bardhana: breakdown.bardhana,
hamali: breakdown.hamali,
adhath: breakdown.adhath,
cess: breakdown.cess,
gaadiBharni: breakdown.gaadiBharni,
},
subtotal: calculateBasicAmount(),
totalCharges: breakdown.bardhana + breakdown.hamali + breakdown.adhath + breakdown.cess + breakdown.gaadiBharni,
totalAmount: total,
};
console.log("Invoice Data:", invoiceData);
toast({
title: "जावक बिल जतन झाले",
description: `बिल नं. ${formData.serialNumber} यशस्वीरित्या जतन केले`,
});
// In production, save to database and update lot stock
// updateLotAfterSale(selectedLot.id, formData.bags);
// Navigate to stock or dashboard
// navigate('/stock');
};
const handlePrint = () => {
window.print();
};
const { breakdown, total } = calculateCharges();
return (
<>
{/* Mobile-optimized header */}
<header className="sticky top-0 z-20 h-14 border-b bg-card/95 backdrop-blur print:hidden">
<div className="px-4 h-full flex items-center justify-between">
<div className="flex items-center gap-3">
<Link to="/">
<Button variant="ghost" size="icon">
<ArrowLeft className="h-5 w-5" />
</Button>
</Link>
<div>
<h1 className="text-lg font-bold">{t('invoice.title')}</h1>
</div>
</div>
<LanguageToggle />
</div>
</header>
{/* Main Content */}
<main className="pb-32">
<div className="container mx-auto px-4 py-6">
<div className="grid gap-6 lg:grid-cols-3">
{/* Invoice Form */}
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle>{t('invoice.billDetails')}</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Bill Number and Date */}
<div className="grid gap-4 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="billBook" className="text-base">{t('invoice.billBook')}</Label>
<Select
value={formData.billBookId}
onValueChange={(value) => setFormData({ ...formData, billBookId: value })}
>
<SelectTrigger id="billBook" className="h-12 text-base">
<SelectValue placeholder="बिल बुक निवडा" />
</SelectTrigger>
<SelectContent>
{billBooks.map((bb) => (
<SelectItem key={bb.id} value={bb.id} disabled={!bb.isActive}>
{bb.name} ({bb.serialFrom}-{bb.serialTo})
{bb.isActive && " ✓"}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="serialNumber" className="text-base">{t('invoice.billNumber')}</Label>
<Input
id="serialNumber"
value={formData.serialNumber}
onChange={(e) => {
setFormData({ ...formData, serialNumber: e.target.value });
setShowBillWarning(true);
}}
className="h-12 text-base font-semibold"
/>
</div>
<div className="space-y-2">
<Label htmlFor="date" className="text-base">{t('invoice.date')}</Label>
<Input
id="date"
type="date"
value={formData.date}
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
className="h-12 text-base"
/>
</div>
</div>
{showBillWarning && (
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription>
{t('invoice.billNumberWarning')}
</AlertDescription>
</Alert>
)}
<div className="space-y-2">
<Label htmlFor="party" className="text-base">{t('invoice.partyName')}</Label>
<Select
value={formData.partyId}
onValueChange={(value) => {
const party = parties.find(p => p.id === value);
setFormData({
...formData,
partyId: value,
partyName: party?.name || '',
});
}}
>
<SelectTrigger id="party">
<SelectValue placeholder={t('invoice.selectParty')} />
</SelectTrigger>
<SelectContent>
{parties.map((party) => (
<SelectItem key={party.id} value={party.id}>
{party.name}
{party.balance !== 0 && (
<span className={`ml-2 text-xs ${party.balance > 0 ? 'text-green-600' : 'text-red-600'}`}>
(बाकी: ₹{Math.abs(party.balance)})
</span>
)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<Separator />
{/* Product Details */}
<div className="space-y-4">
<h3 className="font-semibold">माल तपशील</h3>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="particular">जात (Variety)</Label>
<Select
value={formData.particular}
onValueChange={(value) => setFormData({ ...formData, particular: value, lotNumber: "" })}
>
<SelectTrigger id="particular">
<SelectValue placeholder="जात निवडा" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Teja">तेजा (Teja)</SelectItem>
<SelectItem value="Garuda">गरुड (Garuda)</SelectItem>
<SelectItem value="341">३४१ (341)</SelectItem>
<SelectItem value="Sannam">सन्नम (Sannam)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="lotNumber">लॉट नंबर</Label>
<Select
value={formData.lotNumber}
onValueChange={(value) => setFormData({ ...formData, lotNumber: value })}
disabled={!formData.particular || availableLots.length === 0}
>
<SelectTrigger id="lotNumber">
<SelectValue placeholder={
!formData.particular ? "रथम िवडा" :
availableLots.length === 0 ? " उपलब ी" :
" िवडा"
} />
</SelectTrigger>
<SelectContent>
{availableLots.map((lot) => (
<SelectItem key={lot.id} value={lot.lotNumber}>
{lot.lotNumber} ({lot.availableBags} पोत्या उपलब्ध)
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{selectedLot && (
<Alert className="bg-blue-50 border-blue-200">
<AlertDescription className="text-sm">
<div className="grid grid-cols-2 gap-2">
<div><strong>खरेदी रेट:</strong> ₹{selectedLot.purchaseRate}/kg</div>
<div><strong>उपलब्ध पोत्या:</strong> {selectedLot.availableBags}</div>
<div><strong>दर्जा:</strong> {selectedLot.quality || 'N/A'}</div>
<div><strong>पार्टी:</strong> {selectedLot.partyName}</div>
</div>
</AlertDescription>
</Alert>
)}
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="bags">पोत्यांची संख्या</Label>
<Input
id="bags"
type="number"
value={formData.bags || ''}
onChange={(e) => setFormData({ ...formData, bags: parseFloat(e.target.value) || 0 })}
placeholder="10"
max={selectedLot?.availableBags}
/>
{selectedLot && formData.bags > selectedLot.availableBags && (
<p className="text-xs text-destructive">
फक्त {selectedLot.availableBags} पोत्या उपलब्ध आहेत
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="rate">रेट (₹/kg)</Label>
<Input
id="rate"
type="number"
value={formData.rate || ''}
onChange={(e) => setFormData({ ...formData, rate: parseFloat(e.target.value) || 0 })}
placeholder="200"
/>
</div>
</div>
<div className="grid gap-4 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="grossWeight">ग्रॉस वजन (kg/पोती)</Label>
<Input
id="grossWeight"
type="number"
value={formData.grossWeight || ''}
onChange={(e) => setFormData({ ...formData, grossWeight: parseFloat(e.target.value) || 0 })}
placeholder="40"
/>
</div>
<div className="space-y-2">
<Label>नेट वजन (kg/पोती)</Label>
<Input
value={(parseFloat(formData.grossWeight.toString()) || 40) - 1}
disabled
className="bg-muted"
/>
</div>
<div className="space-y-2">
<Label>एकूण नेट वजन</Label>
<Input
value={`${calculateNetWeight()} kg`}
disabled
className="bg-muted font-semibold"
/>
</div>
</div>
</div>
<Separator />
{/* Additional Charges */}
<div className="space-y-4">
<h3 className="font-semibold">अतिरिक्त खर्च</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="bardhana"
checked={charges.bardhana}
onChange={(e) => setCharges({ ...charges, bardhana: e.target.checked })}
className="h-4 w-4"
/>
<Label htmlFor="bardhana">बर्डाणा (₹18/पोती)</Label>
</div>
<span className="text-sm text-muted-foreground">₹{breakdown.bardhana.toFixed(2)}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="hamali"
checked={charges.hamali}
onChange={(e) => setCharges({ ...charges, hamali: e.target.checked })}
className="h-4 w-4"
/>
<Label htmlFor="hamali">हमाली (₹6/पोती)</Label>
</div>
<span className="text-sm text-muted-foreground">₹{breakdown.hamali.toFixed(2)}</span>
</div>
<div className="flex items-center justify-between gap-4">
<Label htmlFor="adhath">अडत (Commission %)</Label>
<div className="flex items-center gap-2">
<Input
id="adhath"
type="number"
value={charges.adhath}
onChange={(e) => setCharges({ ...charges, adhath: parseFloat(e.target.value) || 0 })}
className="w-20"
min="0"
max="6"
step="0.5"
/>
<span className="text-sm text-muted-foreground">₹{breakdown.adhath.toFixed(2)}</span>
</div>
</div>
<div className="flex items-center justify-between gap-4">
<Label htmlFor="cess">सेस (Cess %)</Label>
<div className="flex items-center gap-2">
<Input
id="cess"
type="number"
value={charges.cess}
onChange={(e) => setCharges({ ...charges, cess: parseFloat(e.target.value) || 0 })}
className="w-20"
min="0"
max="6"
step="0.5"
/>
<span className="text-sm text-muted-foreground">₹{breakdown.cess.toFixed(2)}</span>
</div>
</div>
<div className="flex items-center justify-between gap-4">
<Label htmlFor="gaadiBharni">गाडी भरणी (Optional)</Label>
<div className="flex items-center gap-2">
<Input
id="gaadiBharni"
type="number"
value={charges.gaadiBharni || ''}
onChange={(e) => setCharges({ ...charges, gaadiBharni: parseFloat(e.target.value) || 0 })}
className="w-20"
placeholder="0"
/>
<span className="text-sm text-muted-foreground">₹{breakdown.gaadiBharni.toFixed(2)}</span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Invoice Summary */}
<div className="lg:col-span-1">
<Card className="sticky top-4">
<CardHeader>
<CardTitle>बिल सारांश</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">पोत्या:</span>
<span className="font-medium">{formData.bags || 0}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">ग्रॉस वजन:</span>
<span className="font-medium">
{(parseFloat(formData.bags.toString()) || 0) * (parseFloat(formData.grossWeight.toString()) || 40)} kg
</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">नेट वजन:</span>
<span className="font-medium">{calculateNetWeight()} kg</span>
</div>
<Separator />
<div className="flex justify-between text-base">
<span className="font-medium">मूळ रक्कम:</span>
<span className="font-bold">₹{calculateBasicAmount().toFixed(2)}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">अतिरिक्त खर्च:</span>
<span>₹{(breakdown.bardhana + breakdown.hamali + breakdown.adhath + breakdown.cess + breakdown.gaadiBharni).toFixed(2)}</span>
</div>
<Separator />
<div className="flex justify-between text-lg font-bold text-primary">
<span>एकूण रक्कम:</span>
<span>₹{total.toFixed(2)}</span>
</div>
</div>
{selectedLot && (
<div className="mt-4 p-3 bg-muted rounded-lg text-xs space-y-1">
<p className="font-semibold">लॉट माहिती:</p>
<p>नंबर: {selectedLot.lotNumber}</p>
<p>उपलब्ध: {selectedLot.availableBags - formData.bags} पोत्या</p>
<p>खरेदी रेट: ₹{selectedLot.purchaseRate}/kg</p>
{formData.rate > selectedLot.purchaseRate && (
<p className="text-green-600 font-semibold">
नफा: ₹{((formData.rate - selectedLot.purchaseRate) * calculateNetWeight()).toFixed(2)}
</p>
)}
</div>
)}
</CardContent>
</Card>
</div>
</div>
</div>
</main>
{/* Bottom Action Bar - Thumb zone */}
<div className="fixed bottom-0 left-0 right-0 z-30 bg-card/95 backdrop-blur border-t p-4 safe-area-inset-bottom shadow-2xl print:hidden">
<div className="container mx-auto flex items-center justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="text-xs text-muted-foreground">एकूण रक्कम</div>
<div className="text-2xl font-bold truncate">
₹{total.toLocaleString('en-IN')}
</div>
</div>
<div className="flex gap-2">
<Button
variant="outline"
onClick={handlePrint}
className="h-12 px-4"
>
<Printer className="h-5 w-5" />
</Button>
<Button
onClick={handleSave}
className="h-12 px-8 font-semibold"
>
<Save className="h-5 w-5 mr-2" />
जतन करा
</Button>
</div>
</div>
</div>
</>
);
}