import React, { useState, useMemo, useEffect } from 'react';
// Mock Icons using simple SVG or text for lucide-react (since we can't import it)
const Check = () => ;
const Info = () => ;
const Send = () => ;
const Home = () =>
// FIX: Closing tag was incorrect. Changed to
const Settings = () =>
const Package = () =>
const CalculatorIcon = () => ;
// --- UI Component Mocks for Single File ---
const Card = ({ children, className = '' }) => (
);
const SelectItem = ({ value, children, disabled = false }) => (
);
const Modal = ({ isOpen, onClose, title, children }) => {
if (!isOpen) return null;
return (
{children}
);
};
// --- End UI Component Mocks ---
// Utility function to format number to Korean currency style
const formatCurrency = (amount) => {
if (amount === null || isNaN(amount)) return '0 원';
return `${Math.round(amount).toLocaleString('ko-KR')}`;
};
// Main Application Component
export default function App() {
// 견적 관련 상태
const [area, setArea] = useState(30); // 면적 (평)
const [structure, setStructure] = useState("경량목구조"); // 구조 방식
const [finish, setFinish] = useState("기본형"); // 마감 등급
const [performance, setPerformance] = useState("저에너지 하우스"); // 성능 수준
const [exterior, setExterior] = useState("세라믹사이딩"); // 외장 마감
const [roof, setRoof] = useState("아스팔트슁글"); // 지붕 마감
const [ventilation, setVentilation] = useState("국산 열회수환기장치"); // 환기 장치
const [insulation, setInsulation] = useState("글라스울"); // 단열재
const [wallFinish, setWallFinish] = useState("벽지"); // 내부 벽 마감
const [floorFinish, setFloorFinish] = useState("강마루"); // 내부 바닥 마감
const [shadingSystem, setShadingSystem] = useState(""); // 전동 외부 차양 시스템
const [options, setOptions] = useState({ attic: false }); // 다락방
const [optionalItems, setOptionalItems] = useState({ pointWall: false, openCeiling: false }); // 특화 옵션
// 상담 신청 관련 상태
const [clientName, setClientName] = useState('');
const [clientPhone, setClientPhone] = useState('');
const [clientEmail, setClientEmail] = useState('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalContent, setModalContent] = useState('');
const [phoneError, setPhoneError] = useState('');
// 구조 방식 변경 핸들러: 단열재 및 성능 수준 연동 로직 포함
const handleStructureChange = (val) => {
setStructure(val);
// 단열재 옵션 강제 재설정
if (val === "철근콘크리트") {
setInsulation("EPS");
} else if (val === "중목구조" || val === "경량목구조") {
setInsulation("글라스울");
}
};
// 성능 수준 변경 핸들러: 차양 시스템 초기화
const handlePerformanceChange = (val) => {
setPerformance(val);
if (val !== "패시브인증주택") {
setShadingSystem("");
}
}
// 전화번호 입력 시 숫자만 허용하도록 유효성 검사
useEffect(() => {
const cleanedPhone = clientPhone.replace(/[^0-9]/g, '');
if (clientPhone && clientPhone !== cleanedPhone) {
setPhoneError('숫자만 입력해 주세요.');
} else {
setPhoneError('');
}
}, [clientPhone]);
// --- 핵심 견적 계산 로직 (상세 Breakdown 반환) ---
const detailedEstimate = useMemo(() => {
if (area <= 0 || isNaN(area)) {
return { totalCost: null, costPerPyeong: null, breakdown: [] };
}
let totalCost = 0;
const roofAreaPyeong = area * 1.2; // 지붕 면적 가정
const breakdown = [];
// 1. 기본 구조 및 마감 비용 (평당 가격 기준, 단위: 원)
let basePricePerPyeong = 0;
let baseCostDetails = [];
switch (structure) {
case "경량목구조": basePricePerPyeong = 7700000; break;
case "중목구조": basePricePerPyeong = 8200000; break;
case "철근콘크리트": basePricePerPyeong = 9000000; break;
default: return { totalCost: null, costPerPyeong: null, breakdown: [] };
}
// 1-1. 기본 구조 비용
const structuralBaseCost = basePricePerPyeong * area;
totalCost += structuralBaseCost;
baseCostDetails.push({ item: `${structure} (기본단가: ${formatCurrency(basePricePerPyeong)}/평)`, cost: structuralBaseCost });
// 1-2. 마감 등급 추가
let finishPremium = 0;
if (finish === "고급형") {
finishPremium = 1000000 * area;
totalCost += finishPremium;
baseCostDetails.push({ item: "마감 등급: 고급형 (+100만원/평)", cost: finishPremium });
} else {
baseCostDetails.push({ item: "마감 등급: 기본형", cost: 0 });
}
breakdown.push({ category: "1. 기본 구조 및 마감 (면적: " + area + "평)", details: baseCostDetails, subTotal: structuralBaseCost + finishPremium });
// 2. 단열 및 에너지 성능 옵션 (평당/총액)
let insulationDetails = [];
let insulationTotal = 0;
let insulationPremiumPerPyeong = 0;
switch (insulation) {
case "EPS": break; // 철콘 기본
case "글라스울": break; // 목구조 기본
case "분사식 미네랄울": insulationPremiumPerPyeong = 500000; break;
case "셀룰로오스": insulationPremiumPerPyeong = 1200000; break;
case "경질 우레탄폼": insulationPremiumPerPyeong = 800000; break;
}
if (insulationPremiumPerPyeong > 0) {
insulationTotal = insulationPremiumPerPyeong * area;
totalCost += insulationTotal;
insulationDetails.push({ item: `단열재: ${insulation} (+${formatCurrency(insulationPremiumPerPyeong)}/평)`, cost: insulationTotal });
} else {
insulationDetails.push({ item: `단열재: ${insulation} (기본 사양)`, cost: 0 });
}
// 환기 장치 비용 추가
let ventilationCost = 0;
if (ventilation === "유럽산 열회수환기장치") ventilationCost = 3000000;
if (ventilationCost > 0) {
insulationTotal += ventilationCost;
totalCost += ventilationCost;
insulationDetails.push({ item: "열회수 환기 장치: 유럽산 (+300만원)", cost: ventilationCost });
} else {
insulationDetails.push({ item: "열회수 환기 장치: 국산 (기본)", cost: 0 });
}
breakdown.push({ category: "2. 단열 및 에너지 성능", details: insulationDetails, subTotal: insulationTotal });
// 3. 외부 마감 및 지붕
let exteriorDetails = [];
let exteriorTotal = 0;
// 3-1. 외장재 변경 비용
let exteriorPremiumPerPyeong = 0;
if (exterior === "벽돌타일") exteriorPremiumPerPyeong = 700000;
if (exterior === "시멘트사이딩") exteriorPremiumPerPyeong = -500000;
if (exteriorPremiumPerPyeong !== 0) {
const cost = exteriorPremiumPerPyeong * area;
exteriorTotal += cost;
totalCost += cost;
exteriorDetails.push({
item: `외장재: ${exterior} (${exteriorPremiumPerPyeong > 0 ? '+' : ''}${formatCurrency(exteriorPremiumPerPyeong)}/평)`,
cost: cost
});
} else {
exteriorDetails.push({ item: `외장재: ${exterior} (기본 사양)`, cost: 0 });
}
// 3-2. 지붕 마감 변경 비용
let roofPremiumPerPyeong = 0;
if (roof === "징크") roofPremiumPerPyeong = 900000;
if (roof === "기와") roofPremiumPerPyeong = 600000;
if (roofPremiumPerPyeong > 0) {
const cost = roofPremiumPerPyeong * roofAreaPyeong;
exteriorTotal += cost;
totalCost += cost;
exteriorDetails.push({
item: `지붕: ${roof} (+${formatCurrency(roofPremiumPerPyeong)}/지붕 평당 ${roofAreaPyeong.toFixed(1)}평)`,
cost: cost
});
} else {
exteriorDetails.push({ item: `지붕: ${roof} (기본 사양)`, cost: 0 });
}
breakdown.push({ category: "3. 외부 마감 및 지붕 (지붕면적: " + roofAreaPyeong.toFixed(1) + "평)", details: exteriorDetails, subTotal: exteriorTotal });
// 4. 내부 마감
let interiorDetails = [];
let interiorTotal = 0;
// 4-1. 내부 벽 마감
if (wallFinish === "도장") {
const cost = 400000 * area;
interiorTotal += cost;
totalCost += cost;
interiorDetails.push({ item: "내부 벽: 도장 마감 (+40만원/평)", cost: cost });
} else {
interiorDetails.push({ item: `내부 벽: ${wallFinish} (기본)`, cost: 0 });
}
// 4-2. 내부 바닥 마감
let floorPremium = 0;
if (floorFinish === "원목마루") floorPremium = 700000;
if (floorFinish === "타일") floorPremium = 100000;
if (floorPremium > 0) {
const cost = floorPremium * area;
interiorTotal += cost;
totalCost += cost;
interiorDetails.push({ item: `내부 바닥: ${floorFinish} (+${formatCurrency(floorPremium)}/평)`, cost: cost });
} else {
interiorDetails.push({ item: `내부 바닥: ${floorFinish} (기본)`, cost: 0 });
}
breakdown.push({ category: "4. 내부 마감", details: interiorDetails, subTotal: interiorTotal });
// 5. 기타 공간 및 특화 옵션 (총액)
let optionalDetails = [];
let optionalTotal = 0;
if (options.attic) {
optionalTotal += 15000000;
optionalDetails.push({ item: "다락방 추가", cost: 15000000 });
}
if (optionalItems.pointWall) {
optionalTotal += 2000000;
optionalDetails.push({ item: "특화 디자인 포인트 월", cost: 2000000 });
}
if (optionalItems.openCeiling) {
optionalTotal += 5000000;
optionalDetails.push({ item: "오픈 천장", cost: 5000000 });
}
// 5-1. 차양 시스템 (패시브인증주택일 경우에만)
if (performance === "패시브인증주택" && shadingSystem) {
let shadingCost = 0;
if (shadingSystem === "루버식 블라인드 EVB") {
shadingCost = 8000000;
optionalDetails.push({ item: "전동 외부 차양: 루버식 블라인드 EVB", cost: shadingCost });
}
else if (shadingSystem === "롤링 셔터") {
shadingCost = 10000000;
optionalDetails.push({ item: "전동 외부 차양: 롤링 셔터", cost: shadingCost });
}
optionalTotal += shadingCost;
}
totalCost += optionalTotal;
if (optionalDetails.length > 0) {
breakdown.push({ category: "5. 기타 공간 및 특화 옵션", details: optionalDetails, subTotal: optionalTotal });
}
// 6. 성능 수준에 따른 최종 조정 (비율 증가)
let performanceAdjustment = 0;
if (performance === "패시브인증주택") {
performanceAdjustment = totalCost * 0.08; // 8% 증가
totalCost += performanceAdjustment;
breakdown.push({
category: "6. 최종 성능 수준 조정",
details: [{ item: "패시브인증주택을 위한 최종 8% 조정 (고성능 창호 등)", cost: performanceAdjustment }],
subTotal: performanceAdjustment
});
}
// 최종 금액 및 평당 단가 계산
const finalRoundedCost = Math.round(totalCost / 100000) * 100000; // 10만원 단위로 반올림
const finalCostPerPyeong = finalRoundedCost / area;
// 최종 반환 객체
return {
totalCost: finalRoundedCost,
costPerPyeong: finalCostPerPyeong,
breakdown
};
}, [area, structure, finish, performance, exterior, roof, ventilation, insulation, wallFinish, floorFinish, options, optionalItems, shadingSystem]);
const { totalCost, costPerPyeong, breakdown } = detailedEstimate;
// 유효성 검사
const isConsultationValid = clientName && clientPhone && clientEmail && totalCost !== null && !phoneError;
// 상담 신청 핸들러
const handleConsultationRequest = () => {
// 유효성 검사
if (!clientName || !clientPhone || !clientEmail) {
setModalContent('이름, 연락처, 이메일 주소는 모두 필수 입력 항목입니다.');
setIsModalOpen(true);
return;
}
if (phoneError) {
setModalContent('연락처는 숫자만 입력 가능합니다. 다시 확인해주세요.');
setIsModalOpen(true);
return;
}
// 메일 본문 내용 생성
const specifications = {
'총 예상 건축비 (VAT 별도)': formatCurrency(totalCost) + ' 원',
'평당 최종 건축비 (VAT 별도)': formatCurrency(costPerPyeong) + ' 원',
'건축 면적': area + ' 평',
'구조 방식': structure,
'마감 등급': finish,
'성능 수준': performance,
'주요 단열재': insulation,
'열회수 환기 장치': ventilation,
'외장 마감': exterior,
'지붕 마감': roof,
'내부 벽 마감': wallFinish,
'내부 바닥 마감': floorFinish,
'전동 외부 차양': performance === '패시브인증주택' ? (shadingSystem || '미선택') : '해당 없음',
'다락방 추가': options.attic ? '예' : '아니오',
'특화 디자인 포인트 월': optionalItems.pointWall ? '예' : '아니오',
'오픈 천장': optionalItems.openCeiling ? '예' : '아니오',
};
const details = Object.entries(specifications)
.map(([key, value]) => `* ${key}: ${value}`)
.join('\n');
// 요청하신 이메일 주소로 변경: MY.HOME.PLACE@GMAIL.COM
const emailReceiver = 'MY.HOME.PLACE@GMAIL.COM';
const emailSubject = `[주택 건축 상세 견적 기반 상담 신청] ${clientName}님 (${area}평)`;
const emailBody = `
${clientName}님, 안녕하세요. 상세 견적 계산 내역을 기반으로 상담을 신청합니다.
*--- 고객 정보 ---*
성함: ${clientName}
연락처: ${clientPhone}
이메일: ${clientEmail}
*--- 견적 요청 스펙 ---*
${details}
편하신 상담 가능 일정을 회신해 주시면 감사하겠습니다.
`;
const mailtoLink = `mailto:${emailReceiver}?subject=${encodeURIComponent(emailSubject)}&body=${encodeURIComponent(emailBody.trim())}`;
// 클라이언트 메일 앱 실행
window.location.href = mailtoLink;
// 모달을 사용하여 사용자에게 안내
setModalContent(`상담 이메일 (${emailReceiver}) 작성이 시작됩니다. 메일 앱을 확인해 주세요.\n\n[주의: 실제 발송은 메일 앱에서 '보내기'를 누르셔야 완료됩니다.]`);
setIsModalOpen(true);
};
// 옵션 체크박스 컴포넌트
const OptionCheckbox = ({ id, label, cost, checked, onChange }) => (
);
// 셀렉트 그룹 컴포넌트
const SelectGroup = ({ label, value, onValueChange, children, hint }) => (
);
// 단열재 SelectGroup을 구조 방식에 따라 다르게 렌더링
const renderInsulationSelect = () => {
if (structure === "철근콘크리트") {
return (
EPS (기본)
경질 우레탄폼 (+80만원/평)
);
} else if (structure === "경량목구조" || structure === "중목구조") {
const glassWoolLabel = structure === "중목구조"
? "글라스울(지붕), 미네랄울(벽) (기본)"
: "글라스울 (기본)";
return (
{glassWoolLabel}
분사식 미네랄울 (+50만원/평)
셀룰로오스 (+120만원/평)
);
} else {
return (
)
}
}
// 상세 견적 내역 렌더링 컴포넌트
const EstimateDetail = ({ breakdown }) => {
if (!breakdown || breakdown.length === 0 || breakdown.every(b => b.details.length === 0)) {
return (
setIsModalOpen(false)} title="안내">
);
}
{children}
);
const Label = ({ children, htmlFor, className = '' }) => (
);
const Input = ({ type = 'text', value, onChange, id, className = '', ...props }) => (
);
const Button = ({ children, onClick, className = '', disabled = false }) => (
);
const Checkbox = ({ id, checked, onCheckedChange }) => (
onCheckedChange(e.target.checked)}
className="w-5 h-5 text-blue-600 bg-gray-100 border-gray-300 rounded-md focus:ring-blue-500 transition duration-150 transform scale-100 hover:scale-105"
/>
);
const Select = ({ value, onValueChange, placeholder, children }) => (
{title}
onChange(!checked)}
>
{cost}
);
// 섹션 타이틀 컴포넌트
const SectionTitle = ({ icon, title, description }) => (
{icon} {title}
{description}
구조 방식을 먼저 선택해주세요.
건축 면적을 입력하고 옵션을 선택해주세요.
);
}
return (
{breakdown.filter(b => b.details.length > 0).map((section, sectionIndex) => (
))}
);
};
return (
{section.category}
-
{section.details.map((detail, detailIndex) => (
- {detail.item} {formatCurrency(detail.cost)} 원 ))}
소계
{formatCurrency(section.subTotal)} 원
)}
{/* 헤더 */}
{/* 메인 컨텐츠 영역: PC에서 2열 레이아웃 */}
{/* 알림 모달 */}
주택 건축 상세 견적 시뮬레이터
내집 마련의 꿈, 전문가와 함께 합리적인 예산을 설계하세요.
{/* 1. 좌측: 옵션 선택 영역 (PC: 2/3 너비) */}
{/* 1. 기본 정보 및 구조 선택 */}
}
title="1. 기본 정보 및 구조 선택"
description="건축 면적을 입력하고, 건물의 뼈대가 되는 구조 방식과 마감 등급을 선택합니다."
/>
{/* 2. 성능 및 단열 상세 설정 */}
}
title="2. 성능 및 단열 상세 설정"
description="주택의 에너지 효율과 단열 사양을 결정합니다. 고성능 창호 및 열회수 환기 장치가 기본 포함됩니다."
/>
{/* 3. 마감재 옵션 */}
}
title="3. 마감재 옵션"
description="건물의 외부 및 내부 디자인을 완성하는 마감재를 선택합니다."
/>
{/* 4. 기타 공간 및 특화 옵션 */}
}
title="4. 기타 공간 및 특화 옵션"
description="추가 공간이나 디자인적 요소를 위한 옵션들을 선택합니다."
/>
setOptions(prev => ({ ...prev, attic: checked }))}
/>
setOptionalItems(prev => ({ ...prev, pointWall: checked }))}
/>
setOptionalItems(prev => ({ ...prev, openCeiling: checked }))}
/>
{/* 2. 우측: 견적 결과 및 상담 신청 영역 (PC: 1/3 너비) */}
{/* 건축 면적 */}
경량목구조 (기본)
중목구조 (+50만원/평)
철근콘크리트 (+130만원/평)
{/* 마감 등급 */}
기본형
고급형 (+100만원/평)
setArea(Number(e.target.value))}
className="text-lg font-bold"
/>
{area > 0 &&
{/* 구조 방식 */}
약 {Math.round(area * 3.30578 * 10) / 10} m²
}
{/* 성능 수준 */}
저에너지 하우스 (기본)
패시브인증주택 (+8% 최종 비용 조정)
{/* 조건부 설명 문구 */}
{performance === "패시브인증주택" && (
한국패시브건축협회 인증을 위한 최고 성능 사양입니다. (외부 전동 차양 시스템 필수 선택)
)}
차양 시스템 선택
루버식 블라인드 EVB (+800만원)
롤링 셔터 (+1,000만원)
)}
{/* 단열재 선택 항목 (구조 방식에 따라 조건부 렌더링) */}
{renderInsulationSelect()}
{/* 환기 장치 */}
국산 (기본)
유럽산 (+300만원)
{/* '패시브인증주택' 선택 시 '전동 외부 차양 시스템' 조건부 렌더링 */}
{performance === "패시브인증주택" && (
{/* 외장 마감 */}
세라믹사이딩 (기본)
벽돌타일 (+70만원/평)
시멘트사이딩 (-50만원/평)
{/* 지붕 마감 */}
아스팔트슁글 (기본)
기와 (+60만원/지붕 평당)
징크 (+90만원/지붕 평당)
{/* 내부 벽 마감 */}
벽지 (기본)
도장 (+40만원/평)
{/* 내부 바닥 마감 */}
강마루 (기본)
타일 (+10만원/평)
원목마루 (+70만원/평)
{/* 최종 견적 결과 카드 */}
>
) : (
{/* 상담 신청 카드 */}
최종 예상 견적 결과
{totalCost !== null && totalCost > 0 ? ( <>
총 건축 면적
{area} 평
평당 건축비 (VAT 별도)
{formatCurrency(costPerPyeong)}
총 예상 건축비 (VAT 별도)
{formatCurrency(totalCost)}
* 상기 금액은 VAT가 별도이며, 대지 구입 비용, 토목/부대 공사 비용, 설계비용 등은 포함되지 않은 순수 건축비 견적입니다.
상세 견적 내역 (Breakdown)
건축 면적을 1평 이상 입력하고 옵션을 선택해 주세요.
)}
견적 기반 상담 신청
setClientName(e.target.value)} />
setClientPhone(e.target.value)}
/>
{phoneError &&
{phoneError}
}
setClientEmail(e.target.value)} />
상담 요청 시, 설정된 견적 내역이 메일 본문으로 자동 생성됩니다.
{modalContent}