Spaces:
Sleeping
Sleeping
| import { forwardRef } from 'react'; | |
| import { useLanguage } from '@/contexts/LanguageContext'; | |
| import { format } from 'date-fns'; | |
| interface InvoiceItem { | |
| particular: string; | |
| bags: number; | |
| grossWeightPerBag: number; | |
| netWeight: number; | |
| rate: number; | |
| amount: number; | |
| } | |
| interface InvoiceCharges { | |
| bardhana: boolean; | |
| bardhanAmount: number; | |
| hamali: boolean; | |
| hamaliAmount: number; | |
| adhath: boolean; | |
| adhathPercent: number; | |
| adhathAmount: number; | |
| cess: number; | |
| cessAmount: number; | |
| gaadiBharni: number; | |
| } | |
| interface CustomerInvoiceProps { | |
| shopName: string; | |
| shopAddress: string; | |
| shopPhone: string; | |
| shopGST?: string; | |
| billNumber: string; | |
| date: string; | |
| partyName: string; | |
| partyAddress?: string; | |
| partyPhone?: string; | |
| items: InvoiceItem[]; | |
| charges: InvoiceCharges; | |
| itemTotal: number; | |
| grandTotal: number; | |
| } | |
| // This component is for customer-facing invoice | |
| // DOES NOT SHOW: Lot numbers, purchase rates, supplier info, margins | |
| export const CustomerInvoice = forwardRef<HTMLDivElement, CustomerInvoiceProps>( | |
| (props, ref) => { | |
| const { t, language } = useLanguage(); | |
| const { | |
| shopName, | |
| shopAddress, | |
| shopPhone, | |
| shopGST, | |
| billNumber, | |
| date, | |
| partyName, | |
| partyAddress, | |
| partyPhone, | |
| items, | |
| charges, | |
| itemTotal, | |
| grandTotal, | |
| } = props; | |
| const formatDate = (dateString: string) => { | |
| const d = new Date(dateString); | |
| return format(d, 'dd/MM/yyyy'); | |
| }; | |
| return ( | |
| <div | |
| ref={ref} | |
| className="bg-white p-8 max-w-4xl mx-auto" | |
| style={{ fontFamily: language === 'mr' ? 'Noto Sans Devanagari, sans-serif' : 'Arial, sans-serif' }} | |
| > | |
| {/* Header */} | |
| <div className="border-b-2 border-gray-800 pb-4 mb-6"> | |
| <div className="text-center"> | |
| <h1 className="text-3xl font-bold text-gray-800 mb-2"> | |
| {shopName} | |
| </h1> | |
| <p className="text-sm text-gray-600">{shopAddress}</p> | |
| <p className="text-sm text-gray-600"> | |
| {language === 'mr' ? 'फोन' : 'Phone'}: {shopPhone} | |
| </p> | |
| {shopGST && ( | |
| <p className="text-sm text-gray-600"> | |
| {language === 'mr' ? 'जीएसटी नंबर' : 'GST No'}: {shopGST} | |
| </p> | |
| )} | |
| </div> | |
| </div> | |
| {/* Bill Info */} | |
| <div className="grid grid-cols-2 gap-4 mb-6"> | |
| <div> | |
| <p className="text-sm font-semibold text-gray-700"> | |
| {language === 'mr' ? 'बिल नंबर' : 'Bill No'}: | |
| </p> | |
| <p className="text-lg font-bold">{billNumber}</p> | |
| </div> | |
| <div className="text-right"> | |
| <p className="text-sm font-semibold text-gray-700"> | |
| {language === 'mr' ? 'तारीख' : 'Date'}: | |
| </p> | |
| <p className="text-lg font-bold">{formatDate(date)}</p> | |
| </div> | |
| </div> | |
| {/* Party Details */} | |
| <div className="bg-gray-50 p-4 rounded mb-6"> | |
| <p className="text-sm font-semibold text-gray-700 mb-2"> | |
| {language === 'mr' ? 'पार्टीचे नाव' : 'Party Name'}: | |
| </p> | |
| <p className="text-lg font-bold text-gray-800">{partyName}</p> | |
| {partyAddress && ( | |
| <p className="text-sm text-gray-600 mt-1">{partyAddress}</p> | |
| )} | |
| {partyPhone && ( | |
| <p className="text-sm text-gray-600"> | |
| {language === 'mr' ? 'फोन' : 'Phone'}: {partyPhone} | |
| </p> | |
| )} | |
| </div> | |
| {/* Items Table */} | |
| <table className="w-full mb-6"> | |
| <thead> | |
| <tr className="border-b-2 border-gray-800"> | |
| <th className="text-left py-2 text-sm font-semibold"> | |
| {language === 'mr' ? 'तपशील' : 'Particulars'} | |
| </th> | |
| <th className="text-center py-2 text-sm font-semibold"> | |
| {language === 'mr' ? 'पोत्या' : 'Bags'} | |
| </th> | |
| <th className="text-center py-2 text-sm font-semibold"> | |
| {language === 'mr' ? 'वजन (kg)' : 'Weight (kg)'} | |
| </th> | |
| <th className="text-right py-2 text-sm font-semibold"> | |
| {language === 'mr' ? 'रेट' : 'Rate'} | |
| </th> | |
| <th className="text-right py-2 text-sm font-semibold"> | |
| {language === 'mr' ? 'रक्कम' : 'Amount'} | |
| </th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {items.map((item, index) => ( | |
| <tr key={index} className="border-b border-gray-200"> | |
| <td className="py-3 text-sm">{item.particular}</td> | |
| <td className="text-center py-3 text-sm">{item.bags}</td> | |
| <td className="text-center py-3 text-sm">{item.netWeight}</td> | |
| <td className="text-right py-3 text-sm">₹{item.rate.toFixed(2)}</td> | |
| <td className="text-right py-3 text-sm font-semibold"> | |
| ₹{item.amount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| {/* Charges Breakdown */} | |
| <div className="border-t-2 border-gray-300 pt-4 mb-6"> | |
| <div className="flex justify-end"> | |
| <div className="w-1/2 space-y-2"> | |
| {/* Item Total */} | |
| <div className="flex justify-between text-sm"> | |
| <span>{language === 'mr' ? 'माल एकूण' : 'Item Total'}:</span> | |
| <span className="font-semibold"> | |
| ₹{itemTotal.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |
| </span> | |
| </div> | |
| {/* Bardhana */} | |
| {charges.bardhana && charges.bardhanAmount > 0 && ( | |
| <div className="flex justify-between text-sm"> | |
| <span> | |
| {language === 'mr' ? 'बर्डाणा' : 'Packing'} | |
| ({items.reduce((sum, item) => sum + item.bags, 0)} × ₹18): | |
| </span> | |
| <span>₹{charges.bardhanAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |
| </div> | |
| )} | |
| {/* Hamali */} | |
| {charges.hamali && charges.hamaliAmount > 0 && ( | |
| <div className="flex justify-between text-sm"> | |
| <span> | |
| {language === 'mr' ? 'हमाली' : 'Labor'} | |
| ({items.reduce((sum, item) => sum + item.bags, 0)} × ₹6): | |
| </span> | |
| <span>₹{charges.hamaliAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |
| </div> | |
| )} | |
| {/* Commission */} | |
| {charges.adhath && charges.adhathAmount > 0 && ( | |
| <div className="flex justify-between text-sm"> | |
| <span> | |
| {language === 'mr' ? 'अडत' : 'Commission'} ({charges.adhathPercent}%): | |
| </span> | |
| <span>₹{charges.adhathAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |
| </div> | |
| )} | |
| {/* Cess */} | |
| {charges.cessAmount > 0 && ( | |
| <div className="flex justify-between text-sm"> | |
| <span> | |
| {language === 'mr' ? 'सेस' : 'Cess'} ({charges.cess}%): | |
| </span> | |
| <span>₹{charges.cessAmount.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |
| </div> | |
| )} | |
| {/* Vehicle Loading */} | |
| {charges.gaadiBharni > 0 && ( | |
| <div className="flex justify-between text-sm"> | |
| <span>{language === 'mr' ? 'गाडी भरणी' : 'Vehicle Loading'}:</span> | |
| <span>₹{charges.gaadiBharni.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span> | |
| </div> | |
| )} | |
| {/* Grand Total */} | |
| <div className="border-t-2 border-gray-800 pt-2 mt-2 flex justify-between"> | |
| <span className="text-lg font-bold"> | |
| {language === 'mr' ? 'एकूण रक्कम' : 'Grand Total'}: | |
| </span> | |
| <span className="text-xl font-bold text-primary"> | |
| ₹{grandTotal.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Footer */} | |
| <div className="border-t border-gray-300 pt-4 mt-8"> | |
| <div className="flex justify-between items-end"> | |
| <div className="text-sm text-gray-600"> | |
| <p>{language === 'mr' ? 'नोट: कृपया रक्कम वेळेवर भरावी' : 'Note: Please pay on time'}</p> | |
| </div> | |
| <div className="text-center"> | |
| <div className="border-t border-gray-400 pt-2 mt-8 w-32"> | |
| <p className="text-sm font-semibold"> | |
| {language === 'mr' ? 'अधिकृत स्वाक्षरी' : 'Authorized Sign'} | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Print/Share Watermark */} | |
| <div className="text-center mt-6 text-xs text-gray-400"> | |
| <p> | |
| {language === 'mr' | |
| ? 'मिर्ची ट्रेडिंग - व्यावसायिक सॉफ्टवेअर' | |
| : 'Mirchi Trading - Business Software'} | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| ); | |
| CustomerInvoice.displayName = 'CustomerInvoice'; | |