Skip to content

Commit 9586d8b

Browse files
committed
Merge branch 'ADP-5313' into develop
2 parents 8839766 + 331e23b commit 9586d8b

18 files changed

Lines changed: 916 additions & 485 deletions

.claude/settings.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"enabledPlugins": {
33
"superpowers@claude-plugins-official": true
4-
},
5-
"permissions": {
6-
"allow": [
7-
"Bash(curl:*)"
8-
]
94
}
105
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import React, { useState } from 'react';
2+
import {
3+
type Variable,
4+
type RowData,
5+
type CalculationResult,
6+
sanitizeNumberInput,
7+
makeDefaultValues,
8+
useDarkMode,
9+
renderKatexInline,
10+
evaluateExpression,
11+
buildReadableExpression,
12+
buildOptionDisplayValues,
13+
resolveRawValue,
14+
initializeRows,
15+
createRow,
16+
evaluateRows,
17+
buildSumReadable,
18+
roundResult,
19+
useInitialCalculation,
20+
VariableInput,
21+
RowGrid,
22+
ResultSection,
23+
CalculatorWrapper,
24+
getLabelColor,
25+
getSubtextColor,
26+
} from './calculatorUtils';
27+
28+
interface CompoundCalculatorProps {
29+
heading?: string;
30+
formuLatex: string;
31+
variables: Variable[];
32+
rowFormula: string;
33+
resultFormula: string;
34+
defaultRows?: Array<Record<string, number>>;
35+
}
36+
37+
export const CompoundCalculator: React.FC<CompoundCalculatorProps> = ({
38+
heading,
39+
formuLatex,
40+
variables,
41+
rowFormula,
42+
resultFormula,
43+
defaultRows,
44+
}) => {
45+
const rowVariables = variables.filter(v => !v.global);
46+
const globalVariables = variables.filter(v => v.global);
47+
48+
const [rows, setRows] = useState<RowData[]>(() => initializeRows(rowVariables, defaultRows));
49+
const [globalValues, setGlobalValues] = useState<Record<string, number | string>>(() =>
50+
makeDefaultValues(globalVariables)
51+
);
52+
const [result, setResult] = useState<CalculationResult | null>(null);
53+
const isDark = useDarkMode();
54+
55+
const updateRowValue = (rowId: string, variableName: string, value: string) => {
56+
setRows(prev =>
57+
prev.map(r =>
58+
r.id === rowId
59+
? { ...r, values: { ...r.values, [variableName]: sanitizeNumberInput(value) } }
60+
: r
61+
)
62+
);
63+
};
64+
65+
const updateGlobalValue = (variableName: string, value: string) => {
66+
setGlobalValues(prev => ({ ...prev, [variableName]: sanitizeNumberInput(value) }));
67+
};
68+
69+
const addRow = () => setRows(prev => [...prev, createRow(rowVariables)]);
70+
const removeRow = (id: string) => setRows(prev => prev.filter(r => r.id !== id));
71+
72+
const handleCalculate = () => {
73+
// Evaluate per-row formula and sum
74+
const evalResult = evaluateRows(rows, rowVariables, rowFormula);
75+
if ('error' in evalResult) {
76+
setResult({ display: '', value: null, error: evalResult.error });
77+
return;
78+
}
79+
const sumValue = evalResult.results.reduce((s, r) => s + r.value, 0);
80+
81+
// Resolve global variable values
82+
const resolvedGlobals: Record<string, number> = {};
83+
for (const v of globalVariables) {
84+
resolvedGlobals[v.variableName] = resolveRawValue(globalValues[v.variableName], v.variableValue ?? 1);
85+
}
86+
87+
// Evaluate the outer formula with _sum + globals, build display
88+
try {
89+
const outerValues = { _sum: sumValue, ...resolvedGlobals };
90+
const finalValue = evaluateExpression(resultFormula, outerValues);
91+
92+
if (!isFinite(finalValue) || isNaN(finalValue)) {
93+
setResult({ display: '', value: null, error: 'No result: division by zero' });
94+
return;
95+
}
96+
97+
const rounded = roundResult(finalValue);
98+
const sumReadable = buildSumReadable(evalResult.results);
99+
const globalDisplayValues = buildOptionDisplayValues(globalVariables, resolvedGlobals);
100+
const outerDisplayValues: Record<string, string> = { _sum: sumReadable, ...globalDisplayValues };
101+
const display = buildReadableExpression(resultFormula, outerValues, outerDisplayValues) + ` = ${rounded}`;
102+
103+
setResult({ display, value: rounded, error: null });
104+
} catch {
105+
setResult({ display: '', value: null, error: 'Calculation error' });
106+
}
107+
};
108+
109+
useInitialCalculation(handleCalculate);
110+
111+
return (
112+
<CalculatorWrapper heading={heading} formuLatex={formuLatex} isDark={isDark}>
113+
<div className="mb-6">
114+
<RowGrid
115+
rows={rows}
116+
variables={rowVariables}
117+
isDark={isDark}
118+
onUpdateValue={updateRowValue}
119+
onAddRow={addRow}
120+
onRemoveRow={removeRow}
121+
/>
122+
123+
{/* Global variable inputs */}
124+
<div
125+
className="mt-4 pt-4 border-t space-y-3"
126+
style={{ borderColor: isDark ? 'rgba(63, 63, 70, 0.5)' : '#e5e7eb' }}
127+
>
128+
{globalVariables.map(v => (
129+
<div key={v.variableName} className="flex items-center justify-center gap-3">
130+
<VariableInput
131+
variable={v}
132+
value={globalValues[v.variableName]}
133+
onChange={(val) => updateGlobalValue(v.variableName, val)}
134+
isDark={isDark}
135+
className="w-24"
136+
/>
137+
<span
138+
className="calculator-formula text-sm font-medium shrink-0"
139+
style={{ color: getLabelColor(isDark) }}
140+
dangerouslySetInnerHTML={{ __html: renderKatexInline(v.nameInTheFormula) }}
141+
/>
142+
<span className="text-sm" style={{ color: getSubtextColor(isDark) }}>
143+
{v.variableDescription}
144+
</span>
145+
</div>
146+
))}
147+
</div>
148+
</div>
149+
<ResultSection result={result} isDark={isDark} onCalculate={handleCalculate} />
150+
</CalculatorWrapper>
151+
);
152+
};

0 commit comments

Comments
 (0)