Spaces:
Sleeping
fix(panel): KI-272 — PremiumCalculatorPanel copay pre-fill + re-sync on profile load
Browse filesE1 Playwright run found three pre-fill bugs in PremiumCalculatorPanel that
D1 either missed or that occurred via timing race:
1. **Copay always 0** — D1's derivation helpers covered age/SI/family/PED/
city but copay was hardcoded `useState(0)`. Profile's `copay_pct=20`
never propagated. Fix: `useState<number>(initialProfile?.copay_pct ?? 0)`.
2. **Age stuck at 35, family stuck at "Self only"** — useState initializers
run ONCE on first mount. If profile data loads AFTER the panel mounts
(race: user clicks the chip the moment profile finishes fetching), the
sliders never update. Fix: new useEffect watches `initialProfile` changes
and updates each slider IFF the slider still holds its initial-default
value. Once user moves a slider, future profile updates no longer
override their choice (snapshot-comparison ref prevents redundant runs).
3. **SI now correctly prefers desired_sum_insured_inr over
existing_cover_inr** — already shipped in KI-270 / D1; this commit
also threads it into the re-sync useEffect for parity.
Verification:
- npx tsc --noEmit clean
- Re-sync logic only updates sliders that are still at their initial
default — user's manual changes are preserved.
This pairs with backend KI-271 (SLOT_UNION-driven profile endpoints) — now
the profile loaded by getProfileCompleteness actually contains copay_pct +
family_medical_history + desired_sum_insured_inr, and the panel will
correctly pre-fill from them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- frontend/src/app/page.tsx +41 -1
|
@@ -2162,10 +2162,50 @@ function PremiumCalculatorPanel({
|
|
| 2162 |
const [ped, setPed] = useState<"none" | "diabetes_or_hypertension" | "heart_disease" | "multiple">(
|
| 2163 |
derivePed(initialProfile?.health_conditions),
|
| 2164 |
);
|
| 2165 |
-
|
|
|
|
|
|
|
| 2166 |
const [estimate, setEstimate] = useState<PremiumEstimateResponse | null>(null);
|
| 2167 |
const [busy, setBusy] = useState(false);
|
| 2168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2169 |
useEffect(() => {
|
| 2170 |
const handler = setTimeout(() => {
|
| 2171 |
setBusy(true);
|
|
|
|
| 2162 |
const [ped, setPed] = useState<"none" | "diabetes_or_hypertension" | "heart_disease" | "multiple">(
|
| 2163 |
derivePed(initialProfile?.health_conditions),
|
| 2164 |
);
|
| 2165 |
+
// KI-272 — copay pre-fill (D1 missed this). When profile has copay_pct,
|
| 2166 |
+
// start the slider there instead of 0.
|
| 2167 |
+
const [copay, setCopay] = useState<number>(initialProfile?.copay_pct ?? 0);
|
| 2168 |
const [estimate, setEstimate] = useState<PremiumEstimateResponse | null>(null);
|
| 2169 |
const [busy, setBusy] = useState(false);
|
| 2170 |
|
| 2171 |
+
// KI-272 — re-sync sliders if profile loads AFTER the panel has mounted
|
| 2172 |
+
// (race: user clicks the chip the moment profile data finishes fetching).
|
| 2173 |
+
// We use a "user has not yet touched this slider" guard: only update from
|
| 2174 |
+
// profile when the slider still holds its initial-default value. Once the
|
| 2175 |
+
// user moves a slider, profile updates no longer override their choice.
|
| 2176 |
+
const profileSnapshotRef = useRef<string>("");
|
| 2177 |
+
useEffect(() => {
|
| 2178 |
+
if (!initialProfile) return;
|
| 2179 |
+
const snap = JSON.stringify([
|
| 2180 |
+
initialProfile.age,
|
| 2181 |
+
initialProfile.desired_sum_insured_inr ?? initialProfile.existing_cover_inr,
|
| 2182 |
+
initialProfile.location_tier,
|
| 2183 |
+
initialProfile.dependents,
|
| 2184 |
+
initialProfile.health_conditions,
|
| 2185 |
+
initialProfile.copay_pct,
|
| 2186 |
+
]);
|
| 2187 |
+
if (snap === profileSnapshotRef.current) return;
|
| 2188 |
+
profileSnapshotRef.current = snap;
|
| 2189 |
+
if (initialProfile.age != null && age === 35) setAge(initialProfile.age);
|
| 2190 |
+
const newSI =
|
| 2191 |
+
initialProfile.desired_sum_insured_inr ??
|
| 2192 |
+
initialProfile.existing_cover_inr;
|
| 2193 |
+
if (newSI != null && sumInsured === 1_000_000) setSumInsured(newSI);
|
| 2194 |
+
if (initialProfile.location_tier && cityTier === "metro") {
|
| 2195 |
+
setCityTier(deriveCityTier(initialProfile.location_tier));
|
| 2196 |
+
}
|
| 2197 |
+
if (initialProfile.dependents && familySize === 0) {
|
| 2198 |
+
setFamilySize(deriveFamilySize(initialProfile.dependents));
|
| 2199 |
+
}
|
| 2200 |
+
if (initialProfile.health_conditions?.length && ped === "none") {
|
| 2201 |
+
setPed(derivePed(initialProfile.health_conditions));
|
| 2202 |
+
}
|
| 2203 |
+
if (initialProfile.copay_pct != null && copay === 0) {
|
| 2204 |
+
setCopay(initialProfile.copay_pct);
|
| 2205 |
+
}
|
| 2206 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 2207 |
+
}, [initialProfile]);
|
| 2208 |
+
|
| 2209 |
useEffect(() => {
|
| 2210 |
const handler = setTimeout(() => {
|
| 2211 |
setBusy(true);
|