From a5d2964e8e7b4d763ed832fb06a4b4b7ee4c76be Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 6 May 2026 16:44:26 +0900 Subject: [PATCH] =?UTF-8?q?[2089]=20=EC=A0=80=EA=B5=AC=EB=B0=B0+=ED=8A=B9?= =?UTF-8?q?=EC=A0=95=EA=B8=B0=EC=99=80=20=EC=8B=9C=EA=B3=B5=20=EB=A7=A4?= =?UTF-8?q?=EB=89=B4=EC=96=BC=20=EC=95=88=EB=82=B4=20confirm=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../placementShape/PlacementShapeSetting.jsx | 19 +++++ .../roofcover/useRoofAllocationSetting.js | 38 ++++++++- src/locales/ja.json | 1 + src/locales/ko.json | 1 + src/util/roof-pitch-warning.js | 80 +++++++++++++++++++ 5 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 src/util/roof-pitch-warning.js diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 9ba4e342..ee75ea20 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -23,6 +23,11 @@ import { useRoofFn } from '@/hooks/common/useRoofFn' import { usePlan } from '@/hooks/usePlan' import { normalizeDecimal } from '@/util/input-utils' import { CalculatorInput } from '@/components/common/input/CalcInput' +// [LOW-PITCH-WARN 2026-05-06] 저구배 + 특정 기와 사용 시 시공 매뉴얼 안내 alert +import { useSwal } from '@/hooks/useSwal' +import { notifyLowPitchRestrictionForRoofs, isLowPitchRestricted, LOW_PITCH_RESTRICTED_ROOF_IDS } from '@/util/roof-pitch-warning' +// [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 import + 호출 함께 제거 +import { debugCapture } from '@/util/debugCapture' /** * 지붕 레이아웃 @@ -69,6 +74,8 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla hajebichi: useRef(null), } const { saveCanvas } = usePlan() + // [LOW-PITCH-WARN 2026-05-06] + const { swalFire } = useSwal() /** * 치수 입력방법(복시도입력/실측값입력/육지붕) */ @@ -244,6 +251,18 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla index: 0, } + // [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 제거 + debugCapture.log('LOW-PITCH-DIAG handleSaveBtn', { + roofMatlCd: roofInfo.roofMatlCd, + pitch: roofInfo.pitch, + angle: roofInfo.angle, + idMatched: LOW_PITCH_RESTRICTED_ROOF_IDS.has(roofInfo.roofMatlCd), + restricted: isLowPitchRestricted(roofInfo), + }) + + // [LOW-PITCH-WARN 2026-05-06] 6종 기와 + (2 ≤ 寸 < 2.5 OR 11.31 ≤ 度 < 14.04) 일 때 안내 alert. OK 누르면 저장 진행 + await notifyLowPitchRestrictionForRoofs([roofInfo], swalFire, getMessage) + const newAddedRoofs = [...addedRoofs] newAddedRoofs[0] = { ...roofInfo } setAddedRoofs(newAddedRoofs) diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index cd0fc1a6..e99c122b 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -32,6 +32,10 @@ import { roofsState } from '@/store/roofAtom' import { useText } from '@/hooks/useText' import { QLine } from '@/components/fabric/QLine' import { calcLineActualSize2 } from '@/util/qpolygon-utils' +// [LOW-PITCH-WARN 2026-05-06] 저구배 + 특정 기와 사용 시 시공 매뉴얼 안내 alert +import { notifyLowPitchRestrictionForRoofs, isLowPitchRestricted, LOW_PITCH_RESTRICTED_ROOF_IDS } from '@/util/roof-pitch-warning' +// [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 import + 호출 함께 제거 +import { debugCapture } from '@/util/debugCapture' export function useRoofAllocationSetting(id) { const canvas = useRecoilValue(canvasState) @@ -306,7 +310,22 @@ export function useRoofAllocationSetting(id) { /** * 선택한 지붕재로 할당 */ - const handleSave = () => { + const handleSave = async () => { + // [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 제거 + debugCapture.log('LOW-PITCH-DIAG handleSave', { + count: currentRoofList?.length, + list: currentRoofList?.map((r) => ({ + roofMatlCd: r.roofMatlCd, + pitch: r.pitch, + angle: r.angle, + idMatched: LOW_PITCH_RESTRICTED_ROOF_IDS.has(r.roofMatlCd), + restricted: isLowPitchRestricted(r), + })), + }) + + // [LOW-PITCH-WARN 2026-05-06] 등록된 모든 지붕재에 대해 6종+저구배 검사 후 안내 alert. OK 누르면 저장 진행 + await notifyLowPitchRestrictionForRoofs(currentRoofList, swalFire, getMessage) + /** * 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정 */ @@ -345,7 +364,22 @@ export function useRoofAllocationSetting(id) { /** * 지붕재 오른쪽 마우스 클릭 후 단일로 지붕재 변경 필요한 경우 */ - const handleSaveContext = () => { + const handleSaveContext = async () => { + // [LOW-PITCH-DIAG 2026-05-06 TEMP] 진단용 — 검증 후 제거 + debugCapture.log('LOW-PITCH-DIAG handleSaveContext', { + count: currentRoofList?.length, + list: currentRoofList?.map((r) => ({ + roofMatlCd: r.roofMatlCd, + pitch: r.pitch, + angle: r.angle, + idMatched: LOW_PITCH_RESTRICTED_ROOF_IDS.has(r.roofMatlCd), + restricted: isLowPitchRestricted(r), + })), + }) + + // [LOW-PITCH-WARN 2026-05-06] 등록된 모든 지붕재에 대해 6종+저구배 검사 후 안내 alert. OK 누르면 저장 진행 + await notifyLowPitchRestrictionForRoofs(currentRoofList, swalFire, getMessage) + const newRoofList = currentRoofList.map((roof, idx) => { if (roof.index !== idx) { // 기존 저장된 지붕재의 index 수정 diff --git a/src/locales/ja.json b/src/locales/ja.json index dee07342..443b1d8f 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -38,6 +38,7 @@ "modal.roof.shape.setting.patten.a": "Aパターン", "modal.roof.shape.setting.patten.b": "Bパターン", "modal.roof.shape.setting.side": "個別に設定", + "modal.roof.material.low.pitch.warning": "2寸以上2.5寸未満の瓦を使用する場合、適用可能な瓦製品および構造物に制限があります。必ず施工マニュアルをご確認ください。", "plan.menu.roof.cover": "伏せ図入力", "plan.menu.roof.cover.outline.drawing": "外壁線を描く", "plan.menu.roof.cover.roof.shape.setting": "屋根形状の設定", diff --git a/src/locales/ko.json b/src/locales/ko.json index 79f691ad..3e5ed786 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -38,6 +38,7 @@ "modal.roof.shape.setting.patten.a": "A 패턴", "modal.roof.shape.setting.patten.b": "B 패턴", "modal.roof.shape.setting.side": "변별로 설정", + "modal.roof.material.low.pitch.warning": "2寸 이상 2.5寸 미만의 기와를 사용하는 경우, 적용 가능한 기와 제품 및 구조물에 제한이 있습니다. 반드시 시공 매뉴얼을 확인해 주세요.", "plan.menu.roof.cover": "지붕덮개", "plan.menu.roof.cover.outline.drawing": "외벽선 그리기", "plan.menu.roof.cover.roof.shape.setting": "지붕형상 설정", diff --git a/src/util/roof-pitch-warning.js b/src/util/roof-pitch-warning.js new file mode 100644 index 00000000..17099f91 --- /dev/null +++ b/src/util/roof-pitch-warning.js @@ -0,0 +1,80 @@ +/** + * [LOW-PITCH-WARN 2026-05-06] + * 저구배 + 특정 기와 제품 조합 사용 시 시공 매뉴얼 안내 alert. + * + * 트리거 (호출 위치): + * 1) PlacementShapeSetting handleSaveBtn — 배치면 초기설정 저장 + * 2) useRoofAllocationSetting handleSave — 屋根面の割り当て + * 3) useRoofAllocationSetting handleSaveContext — 屋根材変更 후 割り当て + * + * 조건: + * roofMatlCd ∈ 6종 AND (2 ≤ pitch < 2.5 OR 11.31 ≤ angle < 14.04) + * + * 동작: + * 안내 alert (OK 만). 중단 흐름 없음 — OK 누르면 저장 진행. + * 등록된 지붕재 여러 개일 경우, 해당하는 지붕재마다 순차로 alert. + * 세션 캐시 없음 — 트리거마다 매번 표시. + */ + +/** 경고 대상 屋根材 ID 6종 */ +export const LOW_PITCH_RESTRICTED_ROOF_IDS = new Set([ + 'ROOF_ID_WA_53A', + 'ROOF_ID_WA_53B', + 'ROOF_ID_HIRA_C', + 'ROOF_ID_HIRA_D', + 'ROOF_ID_HIRA_S', + 'ROOF_ID_HIRA_SEME', +]) + +/** 寸 하한(포함) ~ 寸 상한(제외) */ +const PITCH_LOW_INCLUSIVE = 2 +const PITCH_HIGH_EXCLUSIVE = 2.5 +/** 度 하한(포함) ~ 度 상한(제외) — 2寸≈11.31°, 2.5寸≈14.04° */ +const ANGLE_LOW_INCLUSIVE = 11.31 +const ANGLE_HIGH_EXCLUSIVE = 14.04 + +/** + * 단일 지붕재가 경고 대상인지 판정. + * pitch / angle 둘 중 하나라도 범위에 들면 true. + * (roofAngleSet 이 'slope' 면 pitch 만, 'flat' 이면 angle 만 갱신될 수 있어 OR.) + */ +export const isLowPitchRestricted = (roof) => { + if (!roof) return false + if (!LOW_PITCH_RESTRICTED_ROOF_IDS.has(roof.roofMatlCd)) return false + + const pitch = Number(roof.pitch) + const angle = Number(roof.angle) + + const pitchInRange = + !Number.isNaN(pitch) && pitch >= PITCH_LOW_INCLUSIVE && pitch < PITCH_HIGH_EXCLUSIVE + const angleInRange = + !Number.isNaN(angle) && angle >= ANGLE_LOW_INCLUSIVE && angle < ANGLE_HIGH_EXCLUSIVE + + return pitchInRange || angleInRange +} + +/** + * 지붕재 배열에 대해 순차로 안내 alert 띄우기. + * OK 만 있는 단순 안내 — 중단 흐름 없음. await 로 모달 닫힘까지 대기. + * + * @param {Array} roofs - 지붕재 배열 (단일이면 [roof]) + * @param {Function} swalFire - useSwal().swalFire + * @param {Function} getMessage - useMessage().getMessage + * @returns {Promise} + */ +export const notifyLowPitchRestrictionForRoofs = async (roofs, swalFire, getMessage) => { + if (!Array.isArray(roofs) || roofs.length === 0) return + + for (const roof of roofs) { + if (!isLowPitchRestricted(roof)) continue + + await new Promise((resolve) => { + swalFire({ + type: 'alert', + icon: 'warning', + text: getMessage('modal.roof.material.low.pitch.warning'), + confirmFn: () => resolve(), + }) + }) + } +}