added weight and updated prices

This commit is contained in:
2026-06-03 15:49:38 -04:00
parent 130130a7ed
commit 4a623e76b4
8 changed files with 186 additions and 54 deletions

View File

@ -7,7 +7,7 @@ import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
files: ['src/**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
@ -26,4 +26,21 @@ export default defineConfig([
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
{
files: ['server.js', 'migrate.js'],
extends: [
js.configs.recommended,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.node,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
}
])

Binary file not shown.

BIN
public/itemsold.xlsx Normal file

Binary file not shown.

View File

@ -641,5 +641,54 @@
}
],
"shippingCost": 0
},
{
"id": 5690,
"date": "June 3, 2026",
"customer": {
"name": "PERFECT POST",
"contactName": "",
"address": "",
"city": "",
"province": "",
"postalCode": "",
"phone": "",
"email": ""
},
"items": [
{
"Item ID": "MDL-5HPR7",
"Description": "Earth Drill, 5.5HP Honda EnginRoll Cage and Flat Free Tires",
"Weight": 161,
"Price": 5342.1757875,
"quantity": 1,
"discount": 0
},
{
"Item ID": "10X42-SSC",
"Description": "Auger, Snap-on, Carbide Point",
"Weight": 28,
"Price": 855.1449449999998,
"quantity": 1,
"discount": 0
},
{
"Item ID": "4064",
"Description": "Rivet, 3/16\" Aluminum Pop Rivet1/4\" Grip",
"Weight": 0.01,
"Price": 0.9523655999999998,
"quantity": 1,
"discount": 0
},
{
"Item ID": "16X42-SSPF",
"Description": "Auger, Snap-On, Full Flighted,Pengo Style",
"Weight": 60,
"Price": 1410.691545,
"quantity": 1,
"discount": 0
}
],
"shippingCost": 0
}
]

View File

@ -10,8 +10,8 @@ import './index.css';
function App() {
const [catalogItems, setCatalogItems] = useState([]);
const [quoteItems, setQuoteItems] = useState([]);
const [quoteId, setQuoteId] = useState('');
const [quoteDate, setQuoteDate] = useState('');
const [quoteId, setQuoteId] = useState(() => Math.floor(Math.random() * 10000) + 1000);
const [quoteDate, setQuoteDate] = useState(() => new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }));
const [savedQuotes, setSavedQuotes] = useState([]);
const [shippingCost, setShippingCost] = useState(0);
const [isWorkOrderMode, setIsWorkOrderMode] = useState(false);
@ -27,9 +27,25 @@ function App() {
email: ''
});
const generateNewQuoteId = () => {
setQuoteId(Math.floor(Math.random() * 10000) + 1000);
setQuoteDate(new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }));
};
// Initialize quote ID and date
useEffect(() => {
generateNewQuoteId();
const loadSavedQuotes = async () => {
try {
const response = await fetch('/api/quotes');
if (response.ok) {
const data = await response.json();
setSavedQuotes(data);
}
} catch (error) {
console.error("Error loading quotes:", error);
}
};
loadSavedQuotes();
const handleAfterPrint = () => {
@ -47,23 +63,6 @@ function App() {
fetchCatalog();
}, []);
const generateNewQuoteId = () => {
setQuoteId(Math.floor(Math.random() * 10000) + 1000);
setQuoteDate(new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }));
};
const loadSavedQuotes = async () => {
try {
const response = await fetch('/api/quotes');
if (response.ok) {
const data = await response.json();
setSavedQuotes(data);
}
} catch (error) {
console.error("Error loading quotes:", error);
}
};
const handleAddItem = (item) => {
setQuoteItems([...quoteItems, item]);
};

View File

@ -39,6 +39,16 @@ export default function QuoteSummary({ items, customer, quoteId, quoteDate, ship
const tax = (subtotal + shipping) * taxRate;
const total = subtotal + shipping + tax;
const calculateTotalWeight = () => {
return items.reduce((total, item) => {
const qty = parseInt(item.quantity, 10) || 0;
const weight = parseFloat(item.Weight) || 0;
return total + (weight * qty);
}, 0);
};
const totalWeight = calculateTotalWeight();
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
};
@ -174,8 +184,13 @@ export default function QuoteSummary({ items, customer, quoteId, quoteDate, ship
</table>
</div>
{!isWorkOrderMode && (
<div className="totals">
<div className="total-row weight-row">
<span>Total Weight:</span>
<span>{totalWeight.toFixed(1)} lbs</span>
</div>
{!isWorkOrderMode && (
<>
<div className="total-row">
<span>Subtotal:</span>
<span>{formatCurrency(subtotal)}</span>
@ -201,8 +216,9 @@ export default function QuoteSummary({ items, customer, quoteId, quoteDate, ship
<span>Total:</span>
<span>{formatCurrency(total)}</span>
</div>
</div>
</>
)}
</div>
</div>
);
}

View File

@ -6,6 +6,22 @@ export default function SavedQuotesList({ savedQuotes, onLoadQuote, onDeleteQuot
return null;
}
const provinceTaxRates = {
'ON': 0.13, // Ontario (HST)
'QC': 0.05, // Quebec (GST)
'NS': 0.15, // Nova Scotia (HST)
'NB': 0.15, // New Brunswick (HST)
'MB': 0.05, // Manitoba (GST)
'BC': 0.05, // British Columbia (GST)
'PE': 0.15, // Prince Edward Island (HST)
'SK': 0.05, // Saskatchewan (GST)
'AB': 0.05, // Alberta (GST)
'NL': 0.15, // Newfoundland and Labrador (HST)
'NT': 0.05, // Northwest Territories (GST)
'YT': 0.05, // Yukon (GST)
'NU': 0.05 // Nunavut (GST)
};
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
};
@ -21,25 +37,48 @@ export default function SavedQuotesList({ savedQuotes, onLoadQuote, onDeleteQuot
<th>Date</th>
<th>Customer Name</th>
<th>Items</th>
<th>Weight</th>
<th>Total</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{savedQuotes.map((quote) => {
// Calculate total for display
const subtotal = quote.items.reduce((total, item) => total + (item.Price * item.quantity), 0);
const total = subtotal + (subtotal * 0.08); // Assuming 8% tax as in QuoteSummary
// Calculate subtotal with item discounts
const subtotal = quote.items.reduce((total, item) => {
const qty = parseInt(item.quantity, 10) || 0;
const price = parseFloat(item.Price) || 0;
const itemTotal = price * qty;
const discountAmount = itemTotal * ((item.discount || 0) / 100);
return total + (itemTotal - discountAmount);
}, 0);
// Calculate shipping
const shipping = parseFloat(quote.shippingCost) || 0;
// Calculate tax based on province
const province = quote.customer?.province?.toUpperCase().trim();
const taxRate = provinceTaxRates[province] || 0.13;
const tax = (subtotal + shipping) * taxRate;
const total = subtotal + shipping + tax;
// Calculate total weight
const totalWeight = quote.items.reduce((total, item) => {
const qty = parseInt(item.quantity, 10) || 0;
const weight = parseFloat(item.Weight) || 0;
return total + (weight * qty);
}, 0);
return (
<tr key={quote.id}>
<td>{quote.id}</td>
<td>{quote.date}</td>
<td>
{quote.customer.name || 'N/A'}
{quote.customer.contactName && <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>Attn: {quote.customer.contactName}</div>}
{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>{totalWeight.toFixed(1)} lbs</td>
<td>{formatCurrency(total)}</td>
<td>
<div style={{ display: 'flex', gap: '0.5rem' }}>

View File

@ -217,6 +217,18 @@ td {
font-size: 1.1rem;
}
/* New rule for weight row alignment */
.weight-row {
justify-content: flex-start;
align-self: flex-start;
width: auto;
}
.weight-row span:first-child {
margin-right: 0.5rem;
}
.total-row.grand-total {
font-size: 1.5rem;
font-weight: 700;