260720_1
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
import { type FC, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { HtmlEvent } from '../App.types';
|
||||
import {
|
||||
notifyDrawingChangedByTable,
|
||||
requestDrawingNavigation,
|
||||
} from '../integration/aislo-drawing-bridge';
|
||||
import { getDesignMeta, setDesignQuantityTable } from '../state';
|
||||
|
||||
/** 편집 가능한 항목 키 (cut/fill 은 지반고·계획고에서 파생되는 읽기전용). */
|
||||
const EDITABLE_KEYS = [
|
||||
'ground',
|
||||
'planned',
|
||||
'cut_soil',
|
||||
'cut_soft_rock',
|
||||
'cut_rock',
|
||||
'tree_removal',
|
||||
'fill_slope_protection',
|
||||
'cut_slope_protection',
|
||||
'ditch_soil',
|
||||
'ditch_soft_rock',
|
||||
'ditch_rock',
|
||||
'embankment',
|
||||
'grubbing',
|
||||
'surface_grading',
|
||||
] as const;
|
||||
|
||||
function round2(value: number): number {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
|
||||
function formatValue(value: number | null | undefined): string {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? String(round2(value)) : '';
|
||||
}
|
||||
|
||||
function parseValue(raw: string): number | null {
|
||||
const text = raw.trim();
|
||||
if (!text) return null;
|
||||
const parsed = Number(text);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
/** 편집 값 문자열 → 저장용 수치 테이블 (파생 cut/fill 포함) */
|
||||
function toNumericTable(values: Record<string, string>): Record<string, number | null> {
|
||||
const table: Record<string, number | null> = {};
|
||||
for (const key of EDITABLE_KEYS) table[key] = parseValue(values[key] ?? '');
|
||||
const ground = table.ground;
|
||||
const planned = table.planned;
|
||||
if (typeof ground === 'number' && typeof planned === 'number') {
|
||||
table.cut = Math.max(ground - planned, 0);
|
||||
table.fill = Math.max(planned - ground, 0);
|
||||
} else {
|
||||
table.cut = null;
|
||||
table.fill = null;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
interface ValueInputProps {
|
||||
fieldKey: (typeof EDITABLE_KEYS)[number];
|
||||
colSpan: number;
|
||||
values: Record<string, string>;
|
||||
onEdit: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
const ValueInput: FC<ValueInputProps> = ({ fieldKey, colSpan, values, onEdit }) => (
|
||||
<td className="cad-qtable__value" colSpan={colSpan}>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
autoComplete="off"
|
||||
value={values[fieldKey] ?? ''}
|
||||
onChange={(event) => onEdit(fieldKey, event.target.value)}
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
|
||||
const DerivedCell: FC<{ value: string }> = ({ value }) => (
|
||||
<td className="cad-qtable__value cad-qtable__value--derived" colSpan={1}>
|
||||
<input type="text" readOnly value={value} tabIndex={-1} />
|
||||
</td>
|
||||
);
|
||||
|
||||
/**
|
||||
* 횡단도 수량 산출표 (CAD 화면 내부, 하단 중심 접이식 패널).
|
||||
* 헤더(제목·측점정보·확정상태·이전/다음)는 접어도 항상 보인다.
|
||||
*/
|
||||
export const QuantityPanel: FC = () => {
|
||||
const [meta, setMeta] = useState(getDesignMeta());
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const identityRef = useRef<string>('');
|
||||
|
||||
const refresh = useCallback(() => setMeta(getDesignMeta()), []);
|
||||
useEffect(() => {
|
||||
window.addEventListener(HtmlEvent.UPDATE_STATE, refresh);
|
||||
return () => window.removeEventListener(HtmlEvent.UPDATE_STATE, refresh);
|
||||
}, [refresh]);
|
||||
|
||||
// 새 도면(측점)으로 바뀌면 입력값을 원본값으로 초기화한다.
|
||||
const identity = meta ? `${meta.kind}:${meta.title}` : '';
|
||||
useEffect(() => {
|
||||
if (!meta) return;
|
||||
if (identityRef.current === identity) return;
|
||||
identityRef.current = identity;
|
||||
const next: Record<string, string> = {};
|
||||
for (const key of EDITABLE_KEYS) next[key] = formatValue(meta.quantityTable?.[key]);
|
||||
setValues(next);
|
||||
}, [identity, meta]);
|
||||
|
||||
if (!meta) return null;
|
||||
|
||||
const handleEdit = (key: string, value: string) => {
|
||||
const nextValues = { ...values, [key]: value };
|
||||
setValues(nextValues);
|
||||
setDesignQuantityTable(toNumericTable(nextValues));
|
||||
notifyDrawingChangedByTable();
|
||||
};
|
||||
|
||||
const ground = parseValue(values.ground ?? '');
|
||||
const planned = parseValue(values.planned ?? '');
|
||||
const cutText =
|
||||
ground !== null && planned !== null ? formatValue(Math.max(ground - planned, 0)) : '';
|
||||
const fillText =
|
||||
ground !== null && planned !== null ? formatValue(Math.max(planned - ground, 0)) : '';
|
||||
|
||||
return (
|
||||
<section className="cad-qtable controls" data-collapsed={collapsed}>
|
||||
<header className="cad-qtable__header">
|
||||
<button
|
||||
type="button"
|
||||
className="cad-qtable__nav"
|
||||
disabled={!meta.hasPrev}
|
||||
title="이전 도면"
|
||||
onClick={() => requestDrawingNavigation('prev')}
|
||||
>
|
||||
‹
|
||||
</button>
|
||||
<div className="cad-qtable__title">
|
||||
<strong>{meta.title}</strong>
|
||||
{meta.info && <span>{meta.info}</span>}
|
||||
<em
|
||||
className="cad-qtable__status"
|
||||
data-confirmed={meta.confirmed}
|
||||
title={meta.confirmed ? '설계 확정됨' : '미확정'}
|
||||
>
|
||||
{meta.confirmed ? '확정' : '미확정'}
|
||||
</em>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="cad-qtable__nav"
|
||||
disabled={!meta.hasNext}
|
||||
title="다음 도면"
|
||||
onClick={() => requestDrawingNavigation('next')}
|
||||
>
|
||||
›
|
||||
</button>
|
||||
{meta.kind === 'cross' && (
|
||||
<button
|
||||
type="button"
|
||||
className="cad-qtable__collapse"
|
||||
title={collapsed ? '표 펼치기' : '표 접기'}
|
||||
onClick={() => setCollapsed((value) => !value)}
|
||||
>
|
||||
{collapsed ? '▲' : '▼'}
|
||||
</button>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{meta.kind === 'cross' && !collapsed && (
|
||||
<div className="cad-qtable__body">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
지반고
|
||||
</th>
|
||||
<ValueInput fieldKey="ground" colSpan={1} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
계획고
|
||||
</th>
|
||||
<ValueInput fieldKey="planned" colSpan={1} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
절토고
|
||||
</th>
|
||||
<DerivedCell value={cutText} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
성토고
|
||||
</th>
|
||||
<DerivedCell value={fillText} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="cad-qtable__label cad-qtable__label--vertical" rowSpan={3}>
|
||||
흙깎기
|
||||
</th>
|
||||
<th className="cad-qtable__label">토사</th>
|
||||
<ValueInput fieldKey="cut_soil" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
지장목제거
|
||||
</th>
|
||||
<ValueInput
|
||||
fieldKey="tree_removal"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
<th className="cad-qtable__label cad-qtable__label--vertical" rowSpan={3}>
|
||||
옆도랑파기
|
||||
</th>
|
||||
<th className="cad-qtable__label">토사</th>
|
||||
<ValueInput fieldKey="ditch_soil" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="cad-qtable__label">연암</th>
|
||||
<ValueInput
|
||||
fieldKey="cut_soft_rock"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
<th className="cad-qtable__label cad-qtable__label--vertical" rowSpan={2}>
|
||||
비탈보호공
|
||||
</th>
|
||||
<th className="cad-qtable__label">성토면</th>
|
||||
<ValueInput
|
||||
fieldKey="fill_slope_protection"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
<th className="cad-qtable__label">연암</th>
|
||||
<ValueInput
|
||||
fieldKey="ditch_soft_rock"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="cad-qtable__label">보통암</th>
|
||||
<ValueInput fieldKey="cut_rock" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label">절토면</th>
|
||||
<ValueInput
|
||||
fieldKey="cut_slope_protection"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
<th className="cad-qtable__label">보통암</th>
|
||||
<ValueInput fieldKey="ditch_rock" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
흙쌓기
|
||||
</th>
|
||||
<ValueInput fieldKey="embankment" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
제근
|
||||
</th>
|
||||
<ValueInput fieldKey="grubbing" colSpan={2} values={values} onEdit={handleEdit} />
|
||||
<th className="cad-qtable__label" colSpan={2}>
|
||||
노면고르기
|
||||
</th>
|
||||
<ValueInput
|
||||
fieldKey="surface_grading"
|
||||
colSpan={2}
|
||||
values={values}
|
||||
onEdit={handleEdit}
|
||||
/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user