From b546db4a01364bc2496155e4bd9d68f70cca6a7d Mon Sep 17 00:00:00 2001 From: faiztyanirh Date: Fri, 27 Mar 2026 16:51:43 +0700 Subject: [PATCH] continue refnum threshold rule checking --- .../test/config/test-form-config.js | 14 ++ .../dictionary/test/page/edit-page.svelte | 10 ++ .../dictionary/test/page/tabs/ref-num.svelte | 164 ++++++++---------- 3 files changed, 98 insertions(+), 90 deletions(-) diff --git a/src/lib/components/dictionary/test/config/test-form-config.js b/src/lib/components/dictionary/test/config/test-form-config.js index 00d4d9e..979f267 100644 --- a/src/lib/components/dictionary/test/config/test-form-config.js +++ b/src/lib/components/dictionary/test/config/test-form-config.js @@ -146,6 +146,13 @@ export const refNumSchema = z path: ['LowSign'] }); } + if (data.LowSign && !['>', '>=', '='].includes(data.LowSign)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Low sign must be =, > or >=', + path: ['LowSign'], + }); + } if (data.High && !data.HighSign) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -153,6 +160,13 @@ export const refNumSchema = z path: ['HighSign'] }); } + if (data.HighSign && !['<', '<=', '='].includes(data.HighSign)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'High sign must be =, < or <=', + path: ['HighSign'], + }); + } if (data.LowSign && data.HighSign && data.LowSign === data.HighSign) { ctx.addIssue({ code: z.ZodIssueCode.custom, diff --git a/src/lib/components/dictionary/test/page/edit-page.svelte b/src/lib/components/dictionary/test/page/edit-page.svelte index 2ed9d8d..330cb6a 100644 --- a/src/lib/components/dictionary/test/page/edit-page.svelte +++ b/src/lib/components/dictionary/test/page/edit-page.svelte @@ -432,6 +432,16 @@ refTxtState.form.TxtRefType = value; } } + + $effect(() => { + const refType = formState.form.RefType; + if (refType === 'RANGE' || refType === 'THOLD') { + refNumState.form.NumRefType = refType; + } + if (refType === 'TEXT' || refType === 'VSET') { + refTxtState.form.TxtRefType = refType; + } + }); diff --git a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte index 3275775..41951a1 100644 --- a/src/lib/components/dictionary/test/page/tabs/ref-num.svelte +++ b/src/lib/components/dictionary/test/page/tabs/ref-num.svelte @@ -41,7 +41,7 @@ return false; }); -// $inspect(props.refNumState.form.NumRefType) + function snapshotForm() { const f = props.refNumState.form; return { @@ -91,10 +91,6 @@ row.RangeType === form.RangeType ); - console.log('peers:', JSON.stringify(peers)); - console.log('newLow:', form.Low, 'newLowSign:', form.LowSign); - console.log('newHigh:', form.High, 'newHighSign:', form.HighSign); - if (peers.length === 0) return null; const newLow = form.Low !== '' && form.Low != null ? Number(form.Low) : null; @@ -102,46 +98,36 @@ const newLowSign = form.LowSign ?? ''; const newHighSign = form.HighSign ?? ''; + const excludeIds = new Set(); + // Cari row yang high-nya paling dekat di bawah newLow const closestBelow = peers .filter(r => r.High !== '' && r.High != null) .map(r => ({ ...r, highNum: Number(r.High) })) .filter(r => r.highNum <= newLow) .sort((a, b) => b.highNum - a.highNum)[0]; + + if (closestBelow) { + excludeIds.add(closestBelow.id) + } + const closestAbove = peers + .filter(r => r.Low !== '' && r.Low != null) + .filter(r => !excludeIds.has(r.id)) + .map(r => ({ ...r, lowNum: Number(r.Low) })) + .filter(r => r.lowNum <= newHigh) + .sort((a, b) => a.lowNum - b.lowNum)[0]; + if (closestBelow) { const prevHigh = closestBelow.highNum; const prevHighSign = closestBelow.HighSign ?? ''; - if (prevHigh !== newLow) { - // Ada celah + if (newLow > prevHigh + 1) { return { field: 'Low', message: `Gap between intervals. Previous interval ends at ${prevHigh}, new interval starts at ${newLow}` }; } - } - - - - for (const row of peers) { - const rowHigh = row.High !== '' && row.High != null ? Number(row.High) : null; - const rowLow = row.Low !== '' && row.Low != null ? Number(row.Low) : null; - const rowHighSign = row.HighSign ?? ''; - const rowLowSign = row.LowSign ?? ''; - - console.log('rowHigh:', rowHigh, typeof rowHigh); - console.log('newLow:', newLow, typeof newLow); - console.log('equal?:', rowHigh === newLow); - - // No. 4 & 6 — tanpa celah & tanpa sign kontradiktif - // Cek apakah interval baru harus dimulai tepat setelah interval sebelumnya berakhir - if (rowHigh != null && newLow != null && rowHigh === newLow) { - console.log('boundary check:', rowHighSign, newLowSign); - - // rowHighSign <= 200, newLowSign harus > 200 - // rowHighSign < 200, newLowSign harus >= 200 - const prevEndInclusive = rowHighSign === '<='; + if (prevHigh === newLow) { + const prevEndInclusive = prevHighSign === '<='; const newStartInclusive = newLowSign === '>='; - console.log('prevEndInclusive:', prevEndInclusive, 'newStartInclusive:', newStartInclusive); - if (prevEndInclusive && newStartInclusive) { return { field: 'LowSign', message: 'Sign contradicts previous interval (overlap at boundary). Use > instead of >=' }; } @@ -149,68 +135,45 @@ return { field: 'LowSign', message: 'Gap between intervals. Use >= instead of >' }; } } + if (newLow < prevHigh) { + return { field: 'Low', message: 'This interval overlaps with an existing interval' }; + } + } - if (rowLow != null && newHigh != null && rowLow === newHigh) { - const prevStartInclusive = rowLowSign === '>='; + if (closestAbove) { + const nextLow = closestAbove.lowNum; + const nextLowSign = closestAbove.LowSign ?? ''; + + if (newHigh < nextLow - 1) { + return { field: 'High', message: `Gap between intervals. Next interval starts at ${nextLow}, new interval ends at ${newHigh}` }; + } + if (nextLow === newHigh) { + const nextStartInclusive = nextLowSign === '>='; const newEndInclusive = newHighSign === '<='; - - if (prevStartInclusive && newEndInclusive) { + if (nextStartInclusive && newEndInclusive) { return { field: 'HighSign', message: 'Sign contradicts next interval (overlap at boundary). Use < instead of <=' }; } - if (!prevStartInclusive && !newEndInclusive) { + if (!nextStartInclusive && !newEndInclusive) { return { field: 'HighSign', message: 'Gap between intervals. Use <= instead of <' }; } } - - // No. 5 — overlap: cek apakah ada nilai yang bisa memenuhi kedua interval - // Contoh: row adalah "< 100", new adalah "> 90" → overlap di 91–99 - if (rowHigh != null && newLow != null) { - const prevEnd = rowHigh; - const newStart = newLow; - - const prevInclusive = rowHighSign === '<='; - const newInclusive = newLowSign === '>='; - - const isOverlap = - newStart < prevEnd || - (newStart === prevEnd && prevInclusive && newInclusive); - - if (isOverlap) { - return { field: 'Low', message: 'This interval overlaps with an existing interval' }; - } + if (newHigh > nextLow) { + return { field: 'High', message: 'This interval overlaps with an existing interval' }; } - - if (rowLow != null && newHigh != null) { - const prevStart = rowLow; - const newEnd = newHigh; - - const prevInclusive = rowLowSign === '>='; - const newInclusive = newHighSign === '<='; - - const isOverlap = - newEnd > prevStart || - (newEnd === prevStart && prevInclusive && newInclusive); - - if (isOverlap) { - return { field: 'High', message: 'This interval overlaps with an existing interval' }; - } - } - - } return null; } -// $inspect(tempNumeric) + function handleInsert() { const newStart = toDays(props.refNumState.form.AgeStart); const newEnd = toDays(props.refNumState.form.AgeEnd); - const isOverlap = tempNumeric.some((row) => { - if (row.SpcType !== props.refNumState.form.SpcType) return false; - if (row.Sex !== props.refNumState.form.Sex) return false; - if (row.NumRefType !== props.refNumState.form.NumRefType) return false; - if (row.RangeType !== props.refNumState.form.RangeType) return false; + const isOverlap = props.refNumState.form.NumRefType === 'THOLD' ? false : tempNumeric.some((row) => { + if ((row.SpcType ?? '') !== (props.refNumState.form.SpcType ?? '')) return false; + if ((row.Sex ?? '') !== (props.refNumState.form.Sex ?? '')) return false; + if ((row.NumRefType ?? '') !== (props.refNumState.form.NumRefType ?? '')) return false; + if ((row.RangeType ?? '') !== (props.refNumState.form.RangeType ?? '')) return false; if (row.id === editingId) return false; @@ -222,12 +185,13 @@ return (newStart <= existingEnd && newEnd >= existingStart); }); + console.log(`isoverlap: ${isOverlap}`) if (isOverlap) { props.refNumState.errors.AgeEnd = 'Age range overlaps with existing data'; return; } - const tholdError = validateTholdContinuity(); + const tholdError = validateTholdContinuity(editingId); if (tholdError) { props.refNumState.errors[tholdError.field] = tholdError.message; return; @@ -276,6 +240,37 @@ } function handleUpdate() { + const newStart = toDays(props.refNumState.form.AgeStart); + const newEnd = toDays(props.refNumState.form.AgeEnd); + + const isOverlap = props.refNumState.form.NumRefType === 'THOLD' ? false : tempNumeric.some((row) => { + if ((row.SpcType ?? '') !== (props.refNumState.form.SpcType ?? '')) return false; + if ((row.Sex ?? '') !== (props.refNumState.form.Sex ?? '')) return false; + if ((row.NumRefType ?? '') !== (props.refNumState.form.NumRefType ?? '')) return false; + if ((row.RangeType ?? '') !== (props.refNumState.form.RangeType ?? '')) return false; + + if (row.id === editingId) return false; + + const existingStart = toDays(row.AgeStart); + const existingEnd = toDays(row.AgeEnd); + + if (existingStart == null || existingEnd == null) return false; + + return (newStart <= existingEnd && newEnd >= existingStart); + }); + + console.log(`isoverlap: ${isOverlap}`) + if (isOverlap) { + props.refNumState.errors.AgeEnd = 'Age range overlaps with existing data'; + return; + } + + const tholdError = validateTholdContinuity(editingId); + if (tholdError) { + props.refNumState.errors[tholdError.field] = tholdError.message; + return; + } + tempNumeric = tempNumeric.map((row) => row.id === editingId ? { id: row.id, ...snapshotForm() } : row ); @@ -396,7 +391,7 @@ Cancel {:else} - + {/if} @@ -439,17 +434,6 @@ - {/if} -
{#if row.Low && row.High}