fix(rebar): 부재 fck 되돌리기가 교각도 기본값으로 돌아가고, 기본과 다르면 주황색 표시

- IMPORTED_FCK(기둥 40, 코핑 40, 확대기초 27)와 fckChanged 추가, 테스트 4건
- 되돌리기는 공통 fck가 아니라 교각도 기본값으로 돌아감 (기둥·코핑은 기존에 27로 돌아가던 문제)
- 기본과 다르면 입력 카드를 주황색으로 표시. "변경 n건"에는 넣지 않음 (배근에만 반영)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-27 16:47:06 +09:00
1 parent 9a338614a6
commit 70fa0be86f
3 files changed
+41 -8

No files matched your search

+9
View File
@@ -91,6 +91,15 @@ export const FCK_MEMBERS: readonly FckMember[] = ['column', 'coping', 'footing']
/** Per-member fck (MPa). A member without an entry uses the ③ 공통 설정 fck. */
export type FckByMember = Partial<Record<FckMember, number>>;
/** 교각도 기준 부재별 기본 fck (MPa). 되돌리기는 이 값으로 돌아가고, 이 값과 다르면 주황색으로 표시합니다. */
export const IMPORTED_FCK: Record<FckMember, number> = { column: 40, coping: 40, footing: 27 };
/** True when the member's fck is set and differs from its imported default. An unset member is not changed. */
export function fckChanged(byMember: FckByMember, member: FckMember): boolean {
const value = byMember[member];
return value !== undefined && value !== IMPORTED_FCK[member];
}
/** The material a member is designed with: its own fck when set, otherwise the 공통 value. fy stays common. */
export function materialFor(common: Material, byMember: FckByMember, member: FckMember): Material {
return { ...common, fck: byMember[member] ?? common.fck };
+9 -8
View File
@@ -14,7 +14,7 @@ import { pileGeometry } from './core/geometry';
import { rebarSummary, type RebarRow } from './core/rebar/summary';
import { shearSegments } from './core/rebar/coping';
import { buildRebar3D, type RebarColorMode, type RebarScene } from './view/rebar3d';
import { DEFAULT_MATERIAL, FCK_MEMBERS, fyOf, GRADE_CHOICES, isAssumedMaterial, materialFrom, type BendFactors, type FckByMember, type FckMember, type GradeKey, type Material } from './core/rebar/common';
import { DEFAULT_MATERIAL, FCK_MEMBERS, fckChanged, fyOf, GRADE_CHOICES, IMPORTED_FCK, isAssumedMaterial, materialFrom, type BendFactors, type FckByMember, type FckMember, type GradeKey, type Material } from './core/rebar/common';
import { buildQuantity, quantityRows, type Quantity, type QuantityLine } from './core/rebar/quantity';
import { parseDrawingRows } from './core/drawing-import';
import { groundElFor, parseGroundModel, strataRowsFor, type GroundModelFile } from './core/ground-model';
@@ -125,7 +125,7 @@ const state = {
/** ③ 공통 설정 (fck · fy). */
material: { ...DEFAULT_MATERIAL } as Material,
/** ② 부재별 fck. A member without an entry uses the 공통 fck (③). Initial values from 교각도 (025·026, 승인 2026-10-27): 기둥·코핑 40, 기초 27. */
fckByMember: { column: 40, coping: 40, footing: 27 } as FckByMember,
fckByMember: { ...IMPORTED_FCK } as FckByMember,
/** Bar highlighted on ③ after [보기] (시안 ③ 알림). */
/** Bars selected on ③ (철근표 checkboxes, 3D click, [보기]). */
rebarSelection: new Set<string>(),
@@ -1897,7 +1897,8 @@ function renderMaterial(): void {
function setMemberFck(member: FckMember, raw: string): void {
const next = { ...state.fckByMember };
if (raw.trim() === '') {
delete next[member];
// 되돌리기: back to the imported default, not to the 공통 value.
next[member] = IMPORTED_FCK[member];
} else {
const value = Number(raw);
if (!Number.isFinite(value) || value <= 0) {
@@ -1915,8 +1916,9 @@ function setMemberFck(member: FckMember, raw: string): void {
/** fck row on a ② card for a member that has its own fck: value, the 공통 value beside it, and a revert when set. */
function renderMemberFck(box: HTMLDivElement, member: FckMember): void {
const own = state.fckByMember[member];
// Not the orange "changed" style: fck changes only the rebar, so it is not a pending shape change (변경 n건).
const row = el('div', undefined, 'edit-row');
// Orange = differs from the imported default. fck changes only the rebar, so it is not counted in 변경 n건.
const changed = fckChanged(state.fckByMember, member);
const row = el('div', undefined, changed ? 'edit-row changed' : 'edit-row');
const id = `member-fck-${member}`;
const label = el('label', 'fck (MPa)');
label.htmlFor = id;
@@ -1926,9 +1928,8 @@ function renderMemberFck(box: HTMLDivElement, member: FckMember): void {
input.step = 'any';
input.value = String(own ?? state.material.fck);
input.addEventListener('change', () => setMemberFck(member, input.value));
const note = own === undefined ? `공통 ${state.material.fck}` : `공통 ${state.material.fck} · 배근에만 반영`;
row.append(label, input, el('span', note, 'muted small'));
if (own !== undefined) {
row.append(label, input, el('span', `기본 ${IMPORTED_FCK[member]} · 배근에만 반영`, 'muted small'));
if (changed) {
const revert = el('button', '되돌리기', 'btn small');
revert.type = 'button';
revert.addEventListener('click', () => setMemberFck(member, ''));
+23
View File
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import { fckChanged, IMPORTED_FCK } from '../src/core/rebar/common';
describe('IMPORTED_FCK (교각도 기준 부재별 기본 fck)', () => {
it('is 40 for 기둥·코핑 and 27 for 확대기초', () => {
expect(IMPORTED_FCK).toEqual({ column: 40, coping: 40, footing: 27 });
});
});
describe('fckChanged (기본과 다르면 주황색)', () => {
it('is false at the imported default', () => {
expect(fckChanged({ column: 40, coping: 40, footing: 27 }, 'coping')).toBe(false);
});
it('is true when the member differs from its imported default', () => {
expect(fckChanged({ coping: 42 }, 'coping')).toBe(true);
expect(fckChanged({ footing: 30 }, 'footing')).toBe(true);
});
it('is false when the member has no value yet', () => {
expect(fckChanged({}, 'column')).toBe(false);
});
});