add contact and workorder

This commit is contained in:
2026-04-22 14:22:42 -04:00
parent 4bebb04c5f
commit 8a5092c56d
4 changed files with 106 additions and 47 deletions

View File

@ -14,9 +14,11 @@ function App() {
const [quoteDate, setQuoteDate] = useState(''); const [quoteDate, setQuoteDate] = useState('');
const [savedQuotes, setSavedQuotes] = useState([]); const [savedQuotes, setSavedQuotes] = useState([]);
const [shippingCost, setShippingCost] = useState(0); const [shippingCost, setShippingCost] = useState(0);
const [isWorkOrderMode, setIsWorkOrderMode] = useState(false);
const [customer, setCustomer] = useState({ const [customer, setCustomer] = useState({
name: '', name: '',
contactName: '',
address: '', address: '',
city: '', city: '',
province: '', province: '',
@ -29,6 +31,12 @@ function App() {
useEffect(() => { useEffect(() => {
generateNewQuoteId(); generateNewQuoteId();
loadSavedQuotes(); loadSavedQuotes();
const handleAfterPrint = () => {
setIsWorkOrderMode(false);
};
window.addEventListener('afterprint', handleAfterPrint);
return () => window.removeEventListener('afterprint', handleAfterPrint);
}, []); }, []);
useEffect(() => { useEffect(() => {
@ -66,8 +74,18 @@ function App() {
setQuoteItems(newItems); setQuoteItems(newItems);
}; };
const handlePrint = () => { const handlePrintQuote = () => {
setIsWorkOrderMode(false);
setTimeout(() => {
window.print(); window.print();
}, 100);
};
const handlePrintWorkOrder = () => {
setIsWorkOrderMode(true);
setTimeout(() => {
window.print();
}, 100);
}; };
const handleSaveQuote = async () => { const handleSaveQuote = async () => {
@ -144,7 +162,7 @@ function App() {
const handleNewQuote = () => { const handleNewQuote = () => {
generateNewQuoteId(); generateNewQuoteId();
setCustomer({ name: '', address: '', city: '', province: '', postalCode: '', phone: '', email: '' }); setCustomer({ name: '', contactName: '', address: '', city: '', province: '', postalCode: '', phone: '', email: '' });
setQuoteItems([]); setQuoteItems([]);
setShippingCost(0); setShippingCost(0);
}; };
@ -155,7 +173,7 @@ function App() {
<div className="header"> <div className="header">
<img src="/Logo.jpg" alt="Company Logo" style={{ height: '80px', marginBottom: '1rem' }} /> <img src="/Logo.jpg" alt="Company Logo" style={{ height: '80px', marginBottom: '1rem' }} />
<h1>Little Beaver Earth Augers</h1> <h1>Little Beaver Earth Augers</h1>
<p>Official Quote Generator</p> <p>{isWorkOrderMode ? 'Work Order' : 'Official Quote Generator'}</p>
</div> </div>
@ -165,15 +183,16 @@ function App() {
<div> <div>
<h3>Quote For:</h3> <h3>Quote For:</h3>
<p>{customer.name || 'N/A'}</p> <p>{customer.name || 'N/A'}</p>
{customer.contactName && <p><strong>Attn:</strong> {customer.contactName}</p>}
<p>{customer.address || 'N/A'}</p> <p>{customer.address || 'N/A'}</p>
<p>{customer.city ? `${customer.city}, ` : ''}{customer.province} {customer.postalCode}</p> <p>{customer.city ? `${customer.city}, ` : ''}{customer.province} {customer.postalCode}</p>
<p>{customer.phone || 'N/A'}</p> <p>{customer.phone || 'N/A'}</p>
<p>{customer.email || 'N/A'}</p> <p>{customer.email || 'N/A'}</p>
</div> </div>
<div style={{ textAlign: 'right' }}> <div style={{ textAlign: 'right' }}>
<h3>Quote Details:</h3> <h3>{isWorkOrderMode ? 'Work Order Details:' : 'Quote Details:'}</h3>
<p><strong>Date:</strong> {quoteDate}</p> <p><strong>Date:</strong> {quoteDate}</p>
<p><strong>Quote #:</strong> {quoteId}</p> <p><strong>{isWorkOrderMode ? 'Work Order #:' : 'Quote #:'}</strong> {quoteId}</p>
</div> </div>
</div> </div>
</div> </div>
@ -181,7 +200,7 @@ function App() {
<div className="no-print" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}> <div className="no-print" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h2 style={{ margin: 0 }}>Current Quote: #{quoteId}</h2> <h2 style={{ margin: 0 }}>{isWorkOrderMode ? 'Current Work Order' : 'Current Quote'}: #{quoteId}</h2>
<button className="btn" style={{ backgroundColor: 'white', border: '1px solid var(--border-color)', color: 'var(--text-main)' }} onClick={handleNewQuote}> <button className="btn" style={{ backgroundColor: 'white', border: '1px solid var(--border-color)', color: 'var(--text-main)' }} onClick={handleNewQuote}>
<FilePlus size={18} /> New Quote <FilePlus size={18} /> New Quote
</button> </button>
@ -203,6 +222,7 @@ function App() {
shippingCost={shippingCost} shippingCost={shippingCost}
onShippingChange={setShippingCost} onShippingChange={setShippingCost}
onRemoveItem={handleRemoveItem} onRemoveItem={handleRemoveItem}
isWorkOrderMode={isWorkOrderMode}
/> />
{/* Action Buttons */} {/* Action Buttons */}
@ -211,9 +231,12 @@ function App() {
<button className="btn btn-primary" onClick={handleSaveQuote} style={{ padding: '1rem 2rem', fontSize: '1.1rem', backgroundColor: '#10b981' }}> <button className="btn btn-primary" onClick={handleSaveQuote} style={{ padding: '1rem 2rem', fontSize: '1.1rem', backgroundColor: '#10b981' }}>
<Save style={{ marginRight: '0.5rem' }} /> Save Quote <Save style={{ marginRight: '0.5rem' }} /> Save Quote
</button> </button>
<button className="btn btn-primary" onClick={handlePrint} style={{ padding: '1rem 2rem', fontSize: '1.1rem' }}> <button className="btn btn-primary" onClick={handlePrintQuote} style={{ padding: '1rem 2rem', fontSize: '1.1rem' }}>
<Printer style={{ marginRight: '0.5rem' }} /> Print Quote <Printer style={{ marginRight: '0.5rem' }} /> Print Quote
</button> </button>
<button className="btn btn-primary" onClick={handlePrintWorkOrder} style={{ padding: '1rem 2rem', fontSize: '1.1rem', backgroundColor: '#3b82f6' }}>
<Printer style={{ marginRight: '0.5rem' }} /> Print Work Order
</button>
</div> </div>
)} )}

View File

@ -53,13 +53,24 @@ export default function CustomerForm({ customer, onChange, savedQuotes = [] }) {
</div> </div>
<div className="form-grid"> <div className="form-grid">
<div className="form-group"> <div className="form-group">
<label htmlFor="name">Name</label> <label htmlFor="name">Company / Name</label>
<input <input
type="text" type="text"
id="name" id="name"
name="name" name="name"
value={customer.name} value={customer.name}
onChange={handleChange} onChange={handleChange}
placeholder="ABC Corp"
/>
</div>
<div className="form-group">
<label htmlFor="contactName">Contact Name (Optional)</label>
<input
type="text"
id="contactName"
name="contactName"
value={customer.contactName || ''}
onChange={handleChange}
placeholder="John Doe" placeholder="John Doe"
/> />
</div> </div>
@ -87,14 +98,27 @@ export default function CustomerForm({ customer, onChange, savedQuotes = [] }) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label htmlFor="province">Province / State</label> <label htmlFor="province">Province / State</label>
<input <select
type="text"
id="province" id="province"
name="province" name="province"
value={customer.province} value={customer.province}
onChange={handleChange} onChange={handleChange}
placeholder="ON" >
/> <option value="">-- Select --</option>
<option value="AB">Alberta (AB)</option>
<option value="BC">British Columbia (BC)</option>
<option value="MB">Manitoba (MB)</option>
<option value="NB">New Brunswick (NB)</option>
<option value="NL">Newfoundland and Labrador (NL)</option>
<option value="NS">Nova Scotia (NS)</option>
<option value="NT">Northwest Territories (NT)</option>
<option value="NU">Nunavut (NU)</option>
<option value="ON">Ontario (ON)</option>
<option value="PE">Prince Edward Island (PE)</option>
<option value="QC">Quebec (QC)</option>
<option value="SK">Saskatchewan (SK)</option>
<option value="YT">Yukon (YT)</option>
</select>
</div> </div>
<div className="form-group"> <div className="form-group">
<label htmlFor="postalCode">Postal Code</label> <label htmlFor="postalCode">Postal Code</label>

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
export default function QuoteSummary({ items, customer, shippingCost, onShippingChange, onRemoveItem }) { export default function QuoteSummary({ items, customer, shippingCost, onShippingChange, onRemoveItem, isWorkOrderMode }) {
const provinceTaxRates = { const provinceTaxRates = {
'ON': 0.13, // Ontario (HST) 'ON': 0.13, // Ontario (HST)
'QC': 0.05, // Quebec (GST) 'QC': 0.05, // Quebec (GST)
@ -51,17 +51,18 @@ export default function QuoteSummary({ items, customer, shippingCost, onShipping
return ( return (
<div className="glass-card"> <div className="glass-card">
<h2>Quote Summary</h2> <h2>{isWorkOrderMode ? 'Work Order Summary' : 'Quote Summary'}</h2>
<div className="table-container"> <div className="table-container">
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Item ID</th> <th>Item ID</th>
<th>Description</th> <th>Description</th>
<th>Unit Price</th> {!isWorkOrderMode && <th>Unit Price</th>}
<th>Discount</th> {!isWorkOrderMode && <th>Discount</th>}
<th>Quantity</th> <th>Quantity</th>
<th>Total</th> {!isWorkOrderMode && <th>Total</th>}
{isWorkOrderMode && <th style={{ width: '80px', textAlign: 'center' }}>Done</th>}
<th className="no-print">Action</th> <th className="no-print">Action</th>
</tr> </tr>
</thead> </thead>
@ -74,10 +75,16 @@ export default function QuoteSummary({ items, customer, shippingCost, onShipping
<tr key={index}> <tr key={index}>
<td>{item['Item ID']}</td> <td>{item['Item ID']}</td>
<td>{item.Description}</td> <td>{item.Description}</td>
<td>{formatCurrency(item.Price)}</td> {!isWorkOrderMode && <td>{formatCurrency(item.Price)}</td>}
<td>{item.discount ? `${item.discount}%` : '-'}</td> {!isWorkOrderMode && <td>{item.discount ? `${item.discount}%` : '-'}</td>}
<td>{item.quantity}</td> <td>{item.quantity}</td>
<td>{formatCurrency(finalPrice)}</td> {!isWorkOrderMode && <td>{formatCurrency(finalPrice)}</td>}
{isWorkOrderMode && (
<td style={{ textAlign: 'center' }}>
<div style={{ width: '20px', height: '20px', border: '1px solid black', margin: '0 auto' }} className="print-only-block" />
<div style={{ width: '20px', height: '20px', border: '1px solid #ccc', margin: '0 auto' }} className="no-print" />
</td>
)}
<td className="no-print"> <td className="no-print">
<button <button
className="btn btn-icon btn-danger" className="btn btn-icon btn-danger"
@ -94,6 +101,7 @@ export default function QuoteSummary({ items, customer, shippingCost, onShipping
</table> </table>
</div> </div>
{!isWorkOrderMode && (
<div className="totals"> <div className="totals">
<div className="total-row"> <div className="total-row">
<span>Subtotal:</span> <span>Subtotal:</span>
@ -121,6 +129,7 @@ export default function QuoteSummary({ items, customer, shippingCost, onShipping
<span>{formatCurrency(total)}</span> <span>{formatCurrency(total)}</span>
</div> </div>
</div> </div>
)}
</div> </div>
); );
} }

View File

@ -35,7 +35,10 @@ export default function SavedQuotesList({ savedQuotes, onLoadQuote, onDeleteQuot
<tr key={quote.id}> <tr key={quote.id}>
<td>{quote.id}</td> <td>{quote.id}</td>
<td>{quote.date}</td> <td>{quote.date}</td>
<td>{quote.customer.name || 'N/A'}</td> <td>
{quote.customer.name || 'N/A'}
{quote.customer.contactName && <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>Attn: {quote.customer.contactName}</div>}
</td>
<td>{quote.items.length}</td> <td>{quote.items.length}</td>
<td>{formatCurrency(total)}</td> <td>{formatCurrency(total)}</td>
<td> <td>