Untitled
unknown
plain_text
9 months ago
36 kB
11
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