Untitled
unknown
plain_text
11 days ago
36 kB
2
Indexable
import React, { useState, useMemo, useCallback, useEffect, FC, } from "react"; // --- Global Styles for Theming, Animations, and Responsive Design --- const GlobalStyles: FC = () => ( <style> {` :root { --primary-color: #4CAF50; --secondary-color: #FFC107; --light-bg: #f5f7fa; --light-surface: #ffffff; --light-text: #333333; --dark-bg: #2c3e50; --dark-surface: #34495e; --dark-text: #ecf0f1; --accent-bg: #1abc9c; --card-bg-light: #ffffff; --card-bg-dark: #34495e; --error-color: #e74c3c; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } body { font-family: 'Roboto', sans-serif; margin: 0; } /* Global button styling */ .button { padding: 10px 16px; font-size: 0.95rem; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s ease, transform 0.3s ease; } .button:hover, .button:focus { transform: scale(1.05); outline: 2px solid var(--primary-color); } /* Toast styles */ .toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 24px; border-radius: 6px; animation: fadeIn 0.5s ease; z-index: 1000; } /* Keypad button hover */ .keypad-button:hover { transform: scale(1.1); } /* Quote card hover */ .quote-card:hover { transform: translateY(-4px); } /* Responsive adjustments */ @media (max-width: 768px) { .main-wrapper { padding: 12px; } } `} </style> ); // --- Type Definitions --- interface FormData { goldRate: string; goldWeight: string; labourCost: string; labourPercentage: string; diamondPricePerCarat: string; diamondWeight: string; gstTax: string; miscCost: string; stone1Price: string; stone1Weight: string; stone2Price: string; stone2Weight: string; goldPercentage14K: string; goldPercentage18K: string; goldPercentage22K: string; // New fields for Diamond Options diamondColor: string; diamondClarity: string; } interface Breakdown { baseGoldCost: number; labourPercentageCost: number; fixedLabourCost: number; additionalFixedCosts: number; diamondCost: number; totalBeforeGST: number; finalTotal: number; } interface SavedCalculation { id: string; timestamp: number; formData: FormData; } const GOLD_RATE_DIVISOR = 10; // --- Helper Functions --- const toNum = (val: string): number => parseFloat(val) || 0; export const calculateBreakdown = ( formData: FormData, purityFactor: number ): Breakdown => { const { goldRate, goldWeight, labourCost, labourPercentage, diamondPricePerCarat, diamondWeight, gstTax, miscCost, stone1Price, stone1Weight, stone2Price, stone2Weight, } = formData; const numGoldRate = toNum(goldRate); const numGoldWeight = toNum(goldWeight); const numLabourCost = toNum(labourCost); const numLabourPercentage = toNum(labourPercentage); const numDiamondPricePerCarat = toNum(diamondPricePerCarat); const numDiamondWeight = toNum(diamondWeight); const numGSTTax = toNum(gstTax); const numMiscCost = toNum(miscCost); const numStone1Price = toNum(stone1Price); const numStone1Weight = toNum(stone1Weight); const numStone2Price = toNum(stone2Price); const numStone2Weight = toNum(stone2Weight); const stone1Cost = numStone1Price * numStone1Weight; const stone2Cost = numStone2Price * numStone2Weight; const additionalFixedCosts = stone1Cost + stone2Cost + numMiscCost; const totalDiamondCost = numDiamondPricePerCarat * numDiamondWeight; const perGramPrice = numGoldRate / GOLD_RATE_DIVISOR; const baseGoldCost = perGramPrice * purityFactor * numGoldWeight; const labourPercentageCost = baseGoldCost * (numLabourPercentage / 100); const fixedLabourCostTotal = numLabourCost * numGoldWeight; const totalBeforeGST = baseGoldCost + labourPercentageCost + fixedLabourCostTotal + additionalFixedCosts + totalDiamondCost; const finalTotal = totalBeforeGST * (1 + numGSTTax / 100); return { baseGoldCost, labourPercentageCost, fixedLabourCost: fixedLabourCostTotal, additionalFixedCosts, diamondCost: totalDiamondCost, totalBeforeGST, finalTotal, }; }; // --- Dynamic Styles Generator --- const getStyles = (darkMode: boolean): { [key: string]: React.CSSProperties } => ({ pageWrapper: { backgroundColor: darkMode ? "var(--dark-bg)" : "var(--light-bg)", minHeight: "100vh", padding: "20px", boxSizing: "border-box", overflowY: "auto", color: darkMode ? "var(--dark-text)" : "var(--light-text)", transition: "background-color 0.3s ease, color 0.3s ease", }, mainWrapper: { width: "100%", maxWidth: "1200px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "20px", }, topSection: { display: "flex", flexWrap: "wrap", gap: "20px", }, container: { flex: 3, backgroundColor: darkMode ? "var(--dark-surface)" : "var(--light-surface)", borderRadius: "8px", boxShadow: darkMode ? "0 3px 10px rgba(0,0,0,0.3)" : "0 3px 10px rgba(0,0,0,0.1)", padding: "20px", overflow: "hidden", transition: "all 0.3s ease", }, sidebar: { flex: 1, display: "flex", flexDirection: "column", gap: "20px", alignItems: "center", justifyContent: "flex-start", }, bottomSection: { backgroundColor: darkMode ? "var(--dark-surface)" : "var(--light-surface)", borderRadius: "8px", padding: "16px", boxShadow: darkMode ? "0 3px 10px rgba(0,0,0,0.3)" : "0 3px 10px rgba(0,0,0,0.1)", overflow: "hidden", transition: "all 0.3s ease", }, title: { textAlign: "center", marginBottom: "20px", fontSize: "1.8rem", fontWeight: 700, }, columns: { display: "flex", gap: "20px", flexWrap: "wrap", }, card: { flex: 1, backgroundColor: darkMode ? "var(--card-bg-dark)" : "var(--card-bg-light)", borderRadius: "8px", padding: "16px", boxShadow: darkMode ? "0 2px 6px rgba(0,0,0,0.3)" : "0 2px 6px rgba(0,0,0,0.1)", transition: "all 0.3s ease", }, cardTitle: { marginBottom: "12px", fontSize: "1.1rem", fontWeight: 600, borderBottom: darkMode ? "1px solid #555" : "1px solid #e0e0e0", paddingBottom: "6px", }, grid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))", gap: "16px", }, label: { display: "block", marginBottom: "6px", fontSize: "0.8rem", fontWeight: 500, }, input: { width: "100%", padding: "8px", border: "1px solid #ccc", borderRadius: "4px", fontSize: "0.85rem", backgroundColor: darkMode ? "#555" : "#fff", color: darkMode ? "#fff" : "#333", transition: "border-color 0.3s ease", }, toggleButton: { backgroundColor: darkMode ? "var(--accent-bg)" : "var(--secondary-color)", color: "#fff", padding: "10px 16px", border: "none", borderRadius: "4px", cursor: "pointer", transition: "background-color 0.3s ease", }, keypadContainer: { minWidth: "140px", backgroundColor: darkMode ? "var(--dark-surface)" : "var(--light-surface)", borderRadius: "8px", boxShadow: darkMode ? "0 3px 10px rgba(0,0,0,0.3)" : "0 3px 10px rgba(0,0,0,0.1)", padding: "16px", transition: "all 0.3s ease", }, keypadRow: { display: "flex", justifyContent: "space-between", marginBottom: "10px", }, keypadButton: { flex: 1, margin: "0 5px", padding: "10px", fontSize: "0.95rem", border: "1px solid #ccc", borderRadius: "4px", backgroundColor: darkMode ? "#555" : "#f3f4f6", cursor: "pointer", color: darkMode ? "#fff" : "#333", transition: "background-color 0.3s ease, transform 0.3s ease", }, sectionHeading: { marginTop: "16px", marginBottom: "8px", fontSize: "1rem", fontWeight: 600, }, tableWrapper: { overflowX: "auto", marginTop: "16px", }, table: { width: "100%", borderCollapse: "collapse", fontSize: "0.85rem", }, th: { textAlign: "left", borderBottom: "1px solid #ddd", padding: "8px", backgroundColor: darkMode ? "#555" : "#f3f4f6", fontWeight: 600, }, td: { padding: "8px", borderBottom: "1px solid #eee", }, note: { marginTop: "8px", fontStyle: "italic", textAlign: "center", fontSize: "0.75rem", }, quoteCard: { backgroundColor: darkMode ? "var(--card-bg-dark)" : "var(--card-bg-light)", borderRadius: "8px", padding: "20px", width: "320px", boxShadow: darkMode ? "0 2px 6px rgba(0,0,0,0.3)" : "0 2px 6px rgba(0,0,0,0.1)", animation: "fadeIn 0.5s ease", transition: "transform 0.3s ease", }, }); // --- Input Field Component --- interface InputFieldProps { label: string; name: keyof FormData; value: string; onChange: (name: keyof FormData, value: string) => void; onFocus: (name: keyof FormData) => void; styles: ReturnType<typeof getStyles>; } const InputField: FC<InputFieldProps> = React.memo( ({ label, name, value, onChange, onFocus, styles }) => { const inputId = `input-${name}`; return ( <div> <label htmlFor={inputId} style={styles.label}> {label} </label> <input id={inputId} type="number" value={value} onChange={(e) => onChange(name, e.target.value)} onFocus={() => onFocus(name)} style={styles.input} aria-label={label} /> </div> ); } ); // --- New Select Field Component --- interface SelectFieldProps { label: string; name: keyof FormData; value: string; options: string[]; onChange: (name: keyof FormData, value: string) => void; onFocus: (name: keyof FormData) => void; styles: ReturnType<typeof getStyles>; } const SelectField: FC<SelectFieldProps> = React.memo( ({ label, name, value, options, onChange, onFocus, styles }) => { const selectId = `select-${name}`; return ( <div> <label htmlFor={selectId} style={styles.label}> {label} </label> <select id={selectId} value={value} onChange={(e) => onChange(name, e.target.value)} onFocus={() => onFocus(name)} style={styles.input} aria-label={label} > {options.map((opt) => ( <option key={opt} value={opt}> {opt} </option> ))} </select> </div> ); } ); // --- Keypad Component --- interface KeypadProps { onKeyPress: (key: string) => void; styles: ReturnType<typeof getStyles>; } const Keypad: FC<KeypadProps> = React.memo(({ onKeyPress, styles }) => { const keypadRows = [ ["7", "8", "9"], ["4", "5", "6"], ["1", "2", "3"], ["0", ".", "⌫"], ]; return ( <div style={styles.keypadContainer} role="group" aria-label="Custom Keypad"> <h3 style={{ textAlign: "center", marginBottom: "10px", fontSize: "0.95rem" }}> Keypad </h3> {keypadRows.map((row, rowIndex) => ( <div key={rowIndex} style={styles.keypadRow}> {row.map((key) => ( <button key={key} className="keypad-button button" style={styles.keypadButton} onClick={() => onKeyPress(key)} aria-label={`Key ${key}`} > {key} </button> ))} </div> ))} </div> ); }); // --- Cost Breakdown Table Component --- interface CostBreakdownTableProps { breakdown14K: Breakdown; breakdown18K: Breakdown; breakdown22K: Breakdown; styles: ReturnType<typeof getStyles>; formData: FormData; onGoldPercentageChange: ( key: "goldPercentage14K" | "goldPercentage18K" | "goldPercentage22K", value: string ) => void; } const CostBreakdownTable: FC<CostBreakdownTableProps> = React.memo( ({ breakdown14K, breakdown18K, breakdown22K, styles, formData, onGoldPercentageChange }) => ( <div style={styles.tableWrapper}> <h3 style={{ textAlign: "center", marginBottom: "12px", fontSize: "1rem" }}> Cost Breakdown </h3> <table style={styles.table}> <thead> <tr> <th style={styles.th}>Karat</th> <th style={styles.th}>Gold %</th> <th style={styles.th}>Base Gold</th> <th style={styles.th}>Labour %</th> <th style={styles.th}>Fixed Labour</th> <th style={styles.th}>Add. Costs</th> <th style={styles.th}>Diamond</th> <th style={styles.th}>Total (GST)</th> </tr> </thead> <tbody> {[ { karat: "14K", breakdown: breakdown14K, key: "goldPercentage14K" as const }, { karat: "18K", breakdown: breakdown18K, key: "goldPercentage18K" as const }, { karat: "22K", breakdown: breakdown22K, key: "goldPercentage22K" as const }, ].map(({ karat, breakdown, key }) => ( <tr key={karat}> <td style={styles.td}>{karat}</td> <td style={styles.td}> <input type="number" value={formData[key]} onChange={(e) => onGoldPercentageChange(key, e.target.value)} style={{ width: "50px", fontSize: "0.85rem" }} aria-label={`Gold percentage for ${karat}`} /> </td> <td style={styles.td}>{breakdown.baseGoldCost.toFixed(2)}</td> <td style={styles.td}>{breakdown.labourPercentageCost.toFixed(2)}</td> <td style={styles.td}>{breakdown.fixedLabourCost.toFixed(2)}</td> <td style={styles.td}>{breakdown.additionalFixedCosts.toFixed(2)}</td> <td style={styles.td}>{breakdown.diamondCost.toFixed(2)}</td> <td style={styles.td}> <strong>{breakdown.finalTotal.toFixed(2)}</strong> </td> </tr> ))} </tbody> </table> <p style={styles.note}> Note: Set Fixed Labour Cost to 0 if applying only percentage-based labour. </p> </div> ) ); // --- Quote Card Component --- interface QuoteCardProps { karat: string; breakdown: Breakdown; formData: FormData; styles: ReturnType<typeof getStyles>; onToast: (msg: string) => void; } const QuoteCard: FC<QuoteCardProps> = React.memo( ({ karat, breakdown, formData, styles, onToast }) => { const quoteDate = new Date().toLocaleDateString(); const quoteNumber = useMemo( () => Math.floor(100000 + Math.random() * 900000), [] ); const copyQuote = useCallback(() => { const text = ` Jewellery Quote - ${karat} Date: ${quoteDate} | Quote #: ${quoteNumber} Name: Jewellery Karat: ${karat} Gold Weight: ${formData.goldWeight} g Diamond Weight: ${formData.diamondWeight} ct Diamond Clarity: ${formData.diamondClarity} Diamond Color: ${formData.diamondColor} Total: ${breakdown.finalTotal.toFixed(2)} `; navigator.clipboard.writeText(text); onToast(`Quote for ${karat} copied to clipboard!`); }, [karat, quoteDate, quoteNumber, formData, breakdown, onToast]); return ( <div className="quote-card" style={styles.quoteCard}> <div style={{ borderBottom: "1px solid", marginBottom: "12px", paddingBottom: "8px" }}> <h4 style={{ textAlign: "center", margin: 0 }}> Jewellery Quote - {karat} </h4> <p style={{ textAlign: "center", fontSize: "0.8rem", margin: 0 }}> Date: {quoteDate} | Quote #: {quoteNumber} </p> </div> <table style={{ width: "100%", fontSize: "0.85rem", marginBottom: "12px" }}> <tbody> <tr> <td><strong>Name:</strong></td> <td>Jewellery</td> </tr> <tr> <td><strong>Karat:</strong></td> <td>{karat}</td> </tr> <tr> <td><strong>Gold Weight:</strong></td> <td>{formData.goldWeight} g</td> </tr> <tr> <td><strong>Diamond Weight:</strong></td> <td>{formData.diamondWeight} ct</td> </tr> <tr> <td><strong>Diamond Clarity:</strong></td> <td>{formData.diamondClarity}</td> </tr> <tr> <td><strong>Diamond Color:</strong></td> <td>{formData.diamondColor}</td> </tr> </tbody> </table> <div style={{ borderTop: "1px solid", paddingTop: "8px", textAlign: "center" }}> <p style={{ fontWeight: "bold", fontSize: "1rem", margin: 0 }}> Total: {breakdown.finalTotal.toFixed(2)} </p> </div> <div style={{ textAlign: "center", marginTop: "12px" }}> <button className="button" onClick={copyQuote} style={{ backgroundColor: "var(--primary-color)", color: "#fff" }} aria-label="Copy Quote" > Copy Quote </button> </div> </div> ); } ); // --- Quote Section Component --- interface QuoteSectionProps { breakdown14K: Breakdown; breakdown18K: Breakdown; breakdown22K: Breakdown; formData: FormData; styles: ReturnType<typeof getStyles>; onToast: (msg: string) => void; } const QuoteSection: FC<QuoteSectionProps> = ({ breakdown14K, breakdown18K, breakdown22K, formData, styles, onToast, }) => ( <div style={{ marginTop: "20px" }}> <h3 style={{ textAlign: "center", marginBottom: "12px" }}>Quote Summary</h3> <div style={{ display: "flex", justifyContent: "center", gap: "20px", flexWrap: "wrap" }}> <QuoteCard karat="14K" breakdown={breakdown14K} formData={formData} styles={styles} onToast={onToast} /> <QuoteCard karat="18K" breakdown={breakdown18K} formData={formData} styles={styles} onToast={onToast} /> <QuoteCard karat="22K" breakdown={breakdown22K} formData={formData} styles={styles} onToast={onToast} /> </div> </div> ); // --- Toast Component --- interface ToastProps { message: string; } const Toast: FC<ToastProps> = ({ message }) => ( <div className="toast" role="alert"> {message} </div> ); const JewelleryPriceCalculator: FC = () => { const [darkMode, setDarkMode] = useState(false); const styles = useMemo(() => getStyles(darkMode), [darkMode]); const [formData, setFormData] = useState<FormData>({ goldRate: "60000", goldWeight: "0", labourCost: "0", labourPercentage: "0", diamondPricePerCarat: "10000", diamondWeight: "0", gstTax: "0", miscCost: "0", stone1Price: "0", stone1Weight: "0", stone2Price: "0", stone2Weight: "0", goldPercentage14K: "60", goldPercentage18K: "75", goldPercentage22K: "92", // New fields default values diamondColor: "F-G", diamondClarity: "VVS1", }); const [activeInput, setActiveInput] = useState<keyof FormData | null>(null); const [savedCalculations, setSavedCalculations] = useState<SavedCalculation[]>([]); const [selectedCalcIds, setSelectedCalcIds] = useState<string[]>([]); const [toastMessage, setToastMessage] = useState(""); // --- Toast handler --- const showToast = useCallback((msg: string) => { setToastMessage(msg); setTimeout(() => setToastMessage(""), 3000); }, []); // --- Load saved calculations on mount --- useEffect(() => { const storedCalculations = localStorage.getItem("pastCalculations"); if (storedCalculations) { setSavedCalculations(JSON.parse(storedCalculations)); } }, []); const handleChange = useCallback((name: keyof FormData, value: string) => { setFormData((prev) => ({ ...prev, [name]: value })); }, []); const handleActiveInput = useCallback((name: keyof FormData) => { setActiveInput(name); }, []); const handleKeypadInput = useCallback( (key: string) => { if (!activeInput) return; let currentVal = formData[activeInput]; if (key === ".") { if (currentVal.includes(".")) return; currentVal = currentVal || "0"; } else if (currentVal === "0") { currentVal = ""; } handleChange(activeInput, currentVal + key); }, [activeInput, formData, handleChange] ); const handleBackspace = useCallback(() => { if (!activeInput) return; const currentVal = formData[activeInput]; const newValue = currentVal.slice(0, -1) || "0"; handleChange(activeInput, newValue); }, [activeInput, formData, handleChange]); const handleKeypadKey = useCallback( (key: string) => (key === "⌫" ? handleBackspace() : handleKeypadInput(key)), [handleBackspace, handleKeypadInput] ); const handleGoldPercentageChange = useCallback( ( key: "goldPercentage14K" | "goldPercentage18K" | "goldPercentage22K", value: string ) => { setFormData((prev) => ({ ...prev, [key]: value })); }, [] ); const breakdown14K = useMemo( () => calculateBreakdown(formData, toNum(formData.goldPercentage14K) / 100), [formData] ); const breakdown18K = useMemo( () => calculateBreakdown(formData, toNum(formData.goldPercentage18K) / 100), [formData] ); const breakdown22K = useMemo( () => calculateBreakdown(formData, toNum(formData.goldPercentage22K) / 100), [formData] ); const saveCalculation = useCallback(() => { const newCalc: SavedCalculation = { id: Date.now().toString(), timestamp: Date.now(), formData: { ...formData }, }; const updatedCalculations = [...savedCalculations, newCalc]; setSavedCalculations(updatedCalculations); localStorage.setItem("pastCalculations", JSON.stringify(updatedCalculations)); }, [formData, savedCalculations]); const loadCalculation = useCallback((calc: SavedCalculation) => { setFormData(calc.formData); }, []); const deleteCalculation = useCallback( (id: string) => { const updatedCalculations = savedCalculations.filter((calc) => calc.id !== id); setSavedCalculations(updatedCalculations); localStorage.setItem("pastCalculations", JSON.stringify(updatedCalculations)); setSelectedCalcIds((prev) => prev.filter((calcId) => calcId !== id)); }, [savedCalculations] ); const deleteSelectedCalculations = useCallback(() => { const updatedCalculations = savedCalculations.filter( (calc) => !selectedCalcIds.includes(calc.id) ); setSavedCalculations(updatedCalculations); localStorage.setItem("pastCalculations", JSON.stringify(updatedCalculations)); setSelectedCalcIds([]); }, [savedCalculations, selectedCalcIds]); const clearAllCalculations = useCallback(() => { setSavedCalculations([]); localStorage.removeItem("pastCalculations"); setSelectedCalcIds([]); }, []); const toggleSelectCalculation = useCallback((id: string) => { setSelectedCalcIds((prev) => prev.includes(id) ? prev.filter((calcId) => calcId !== id) : [...prev, id] ); }, []); const toggleSelectAll = useCallback(() => { setSelectedCalcIds((prev) => prev.length === savedCalculations.length ? [] : savedCalculations.map((calc) => calc.id) ); }, [savedCalculations]); // Options for diamond color and clarity const diamondColorOptions = ["D", "E", "F", "G", "H", "D-E", "E-F", "F-G", "G-H", "H-I"]; const diamondClarityOptions = [ "FL", "IF", "VVS1", "VVS1-2", "VVS2", "VVS-VS", "VS1", "VS1-VS2", "VS2", "VS-SI", "SI1", "SI1-2", "SI2", "SI", "SI3" ]; return ( <> <GlobalStyles /> <div style={styles.pageWrapper}> <div style={styles.mainWrapper} className="main-wrapper"> {/* Top Section: Form and Keypad */} <header style={styles.topSection}> <div style={styles.container}> <h2 style={styles.title}>Jewellery Price Calculator</h2> <div style={styles.columns}> <div style={styles.card}> <div style={styles.cardTitle}>Gold & Diamond Info</div> <div style={styles.grid}> <InputField label="24K GOLD RATE (per 10g):" name="goldRate" value={formData.goldRate} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Gold Weight (g):" name="goldWeight" value={formData.goldWeight} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Fixed Labour (per g):" name="labourCost" value={formData.labourCost} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Labour %:" name="labourPercentage" value={formData.labourPercentage} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Diamond Price/Carat:" name="diamondPricePerCarat" value={formData.diamondPricePerCarat} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Diamond Weight (ct):" name="diamondWeight" value={formData.diamondWeight} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> {/* New Select Fields for Diamond Options */} <SelectField label="Diamond Color:" name="diamondColor" value={formData.diamondColor} options={diamondColorOptions} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <SelectField label="Diamond Clarity:" name="diamondClarity" value={formData.diamondClarity} options={diamondClarityOptions} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> </div> </div> <div style={styles.card}> <div style={styles.cardTitle}>Additional Costs</div> <div style={styles.grid}> <InputField label="GST Tax (%):" name="gstTax" value={formData.gstTax} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Misc Cost:" name="miscCost" value={formData.miscCost} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> </div> <div style={styles.sectionHeading}>Stone 1</div> <div style={styles.grid}> <InputField label="Price/Carat:" name="stone1Price" value={formData.stone1Price} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Weight (ct):" name="stone1Weight" value={formData.stone1Weight} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> </div> <div style={styles.sectionHeading}>Stone 2</div> <div style={styles.grid}> <InputField label="Price/Carat:" name="stone2Price" value={formData.stone2Price} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> <InputField label="Weight (ct):" name="stone2Weight" value={formData.stone2Weight} onChange={handleChange} onFocus={handleActiveInput} styles={styles} /> </div> </div> </div> </div> <aside style={styles.sidebar}> <button className="button" style={styles.toggleButton} onClick={() => setDarkMode((prev) => !prev)} aria-label="Toggle dark/light mode" > {darkMode ? "☀️ Light Mode" : "🌙 Dark Mode"} </button> <Keypad onKeyPress={handleKeypadKey} styles={styles} /> </aside> </header> {/* Bottom Section: Cost Breakdown */} <section style={styles.bottomSection}> <CostBreakdownTable breakdown14K={breakdown14K} breakdown18K={breakdown18K} breakdown22K={breakdown22K} styles={styles} formData={formData} onGoldPercentageChange={handleGoldPercentageChange} /> </section> {/* Quote Summary */} <section> <QuoteSection breakdown14K={breakdown14K} breakdown18K={breakdown18K} breakdown22K={breakdown22K} formData={formData} styles={styles} onToast={showToast} /> </section> {/* Saved Calculations Section */} <section style={{ marginTop: "20px", padding: "16px", backgroundColor: darkMode ? "var(--dark-surface)" : "var(--light-surface)", borderRadius: "8px", }} > <h3 style={{ textAlign: "center", marginBottom: "12px" }}> Past Calculations </h3> <div style={{ marginBottom: "12px", display: "flex", gap: "12px", justifyContent: "center", flexWrap: "wrap", }} > <button onClick={saveCalculation} className="button"> Save Current Calculation </button> <button onClick={clearAllCalculations} className="button"> Clear All </button> <button onClick={toggleSelectAll} className="button"> {selectedCalcIds.length === savedCalculations.length ? "Deselect All" : "Select All"} </button> <button onClick={deleteSelectedCalculations} className="button" disabled={selectedCalcIds.length === 0} > Delete Selected </button> </div> {savedCalculations.length > 0 ? ( <ul style={{ listStyleType: "none", padding: 0 }}> {savedCalculations.map((calc) => ( <li key={calc.id} style={{ marginBottom: "8px", borderBottom: "1px solid", paddingBottom: "4px", display: "flex", alignItems: "center", justifyContent: "space-between", }} > <div style={{ display: "flex", alignItems: "center" }}> <input type="checkbox" checked={selectedCalcIds.includes(calc.id)} onChange={() => toggleSelectCalculation(calc.id)} aria-label="Select calculation" /> <span style={{ marginLeft: "8px" }}> {new Date(calc.timestamp).toLocaleString()} </span> </div> <div> <button onClick={() => loadCalculation(calc)} className="button"> Load </button> <button onClick={() => deleteCalculation(calc.id)} className="button" style={{ marginLeft: "10px" }} > Delete </button> </div> </li> ))} </ul> ) : ( <p style={{ textAlign: "center" }}>No saved calculations.</p> )} </section> </div> {toastMessage && <Toast message={toastMessage} />} </div> </> ); }; export default JewelleryPriceCalculator;
Editor is loading...
Leave a Comment