Files
saju-web/app/result/page.tsx
gahusb d513c063cf feat: 절기 정밀화, AI 해석 추가, 공유 기능 개선
절기 날짜 정밀화:
- solarlunar 라이브러리 추가
- 천문학적 계산 기반 정확한 절기 날짜 계산
- 각 절기별 정확한 날짜 범위 검색
- 폴백 메커니즘으로 안정성 확보

AI 상세 해석 시스템:
- ai-interpretation.ts 라이브러리 생성
- 일간 기반 성격 분석 (10개 천간별 상세 해석)
- 오행 균형 분석 및 점수 계산
- 십성 기반 다차원 분석
  - 직업 운세 (오행 + 십성 조합)
  - 대인 관계 (십성 기반)
  - 재물 운세 (정재/편재 분석)
  - 건강 운세 (오행 균형)
- 맞춤형 조언 생성

결과 페이지 AI 해석 섹션:
- 오행 균형 시각화 (막대 그래프)
- 장점/주의할 점 구분 표시
- 4가지 운세 카드 (직업/대인/재물/건강)
- AI 조언 그리드 레이아웃
- 전문가 상담 권장 안내

공유 기능 개선:
- localhost 감지 로직 추가
- localhost인 경우:
  - 카카오톡: 텍스트 형식으로 사주 정보 공유
  - 링크 복사: 사주 정보 텍스트 복사
  - 사용자에게 개발 환경임을 안내
- 배포 환경: 기존대로 URL 공유
- 더 나은 사용자 경험 제공

기술 개선:
- solarlunar 라이브러리 (정밀 절기 계산)
- 타입 안전성 강화
- 모듈화된 해석 로직
- 성능 최적화

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-12 00:26:12 +09:00

552 lines
26 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { calculateSaju } from '@/lib/saju-calculator';
import Link from 'next/link';
import PDFButton from '../components/PDFButton';
import ShareButtons from '../components/ShareButtons';
import { calculateDaeun, getCurrentDaeun, getDaeunDescription } from '@/lib/daeun-calculator';
import { getCurrentSolarTerm, getSolarTermName, getSolarTermMonthBranch } from '@/lib/solar-terms';
import { EARTHLY_BRANCHES_KR } from '@/lib/saju-calculator';
import { generateInterpretation, calculateElementScore } from '@/lib/ai-interpretation';
interface PageProps {
searchParams: Promise<{
year: string;
month: string;
day: string;
hour?: string;
gender: 'male' | 'female';
calendarType: 'solar' | 'lunar';
}>;
}
export default async function ResultPage({ searchParams }: PageProps) {
const params = await searchParams;
const { year, month, day, hour, gender } = params;
const yearNum = parseInt(year);
const monthNum = parseInt(month);
const dayNum = parseInt(day);
const hourNum = hour ? parseInt(hour) : null;
const sajuData = calculateSaju(yearNum, monthNum, dayNum, hourNum, gender);
// 절기 정보
const solarTermIndex = getCurrentSolarTerm(yearNum, monthNum, dayNum);
const solarTermName = getSolarTermName(solarTermIndex);
const monthBranchIndex = getSolarTermMonthBranch(yearNum, monthNum, dayNum);
const monthBranchName = EARTHLY_BRANCHES_KR[monthBranchIndex];
// AI 해석 생성
const interpretation = generateInterpretation(sajuData);
const elementScores = calculateElementScore(sajuData);
// 대운 계산
const daeunList = calculateDaeun(
yearNum,
monthNum,
dayNum,
gender,
sajuData.month.stem,
sajuData.month.branch
);
const currentYear = new Date().getFullYear();
const currentDaeun = getCurrentDaeun(daeunList, currentYear);
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50">
{/* Navigation */}
<nav className="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<Link href="/" className="text-2xl font-bold bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
🔮
</Link>
<Link
href="/"
className="text-gray-700 hover:text-indigo-600 transition font-medium"
>
</Link>
</div>
</div>
</nav>
{/* Result Content */}
<div id="pdf-content" className="max-w-6xl mx-auto px-4 py-12">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
</h1>
<p className="text-xl text-gray-600">
{yearNum} {monthNum} {dayNum} {hourNum !== null && `${hourNum}`}
{gender === 'male' ? ' 남성' : ' 여성'}
</p>
</div>
{/* 사주팔자 표 */}
<div className="bg-white rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center"> ()</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
<th className="py-4 px-6 text-center font-bold text-lg"></th>
{sajuData.hour && <th className="py-4 px-6 text-center font-bold text-lg"> ()</th>}
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
<th className="py-4 px-6 text-center font-bold text-lg"> ()</th>
</tr>
</thead>
<tbody>
{/* 천간 */}
<tr className="border-b border-gray-200 hover:bg-indigo-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.hour.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.hour.stemKr}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-3xl font-bold text-blue-600">{sajuData.day.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.day.stemKr}</div>
<div className="text-xs text-blue-600 font-semibold mt-1"> ()</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.month.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.month.stemKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-indigo-600">{sajuData.year.stem}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.year.stemKr}</div>
</td>
</tr>
{/* 지지 */}
<tr className="border-b border-gray-200 hover:bg-purple-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.hour.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.hour.branchKr}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-3xl font-bold text-blue-600">{sajuData.day.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.day.branchKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.month.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.month.branchKr}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-3xl font-bold text-purple-600">{sajuData.year.branch}</div>
<div className="text-sm text-gray-600 mt-1">{sajuData.year.branchKr}</div>
</td>
</tr>
{/* 십성 */}
<tr className="border-b border-gray-200 hover:bg-emerald-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"> ()</td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.hour.tenGod}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-lg font-semibold text-blue-600">{sajuData.day.tenGod}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.month.tenGod}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-emerald-600">{sajuData.year.tenGod}</div>
</td>
</tr>
{/* 십이운성 */}
<tr className="hover:bg-pink-50 transition">
<td className="py-4 px-6 text-center font-semibold text-gray-700"></td>
{sajuData.hour && (
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.hour.fortune}</div>
</td>
)}
<td className="py-4 px-6 text-center bg-blue-50">
<div className="text-lg font-semibold text-blue-600">{sajuData.day.fortune}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.month.fortune}</div>
</td>
<td className="py-4 px-6 text-center">
<div className="text-lg font-semibold text-pink-600">{sajuData.year.fortune}</div>
</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-6 space-y-3">
<div className="p-4 bg-blue-50 rounded-xl">
<p className="text-sm text-gray-700">
<strong className="text-blue-600"> ():</strong> {sajuData.day.stem}({sajuData.day.stemKr}) - .
</p>
</div>
<div className="p-4 bg-green-50 rounded-xl">
<p className="text-sm text-gray-700">
<strong className="text-green-600"> ():</strong> {solarTermName} -
{monthBranchName}.
</p>
<p className="text-xs text-gray-600 mt-1">
* 24 .
</p>
</div>
</div>
</div>
{/* 사주 해석 */}
<div className="grid md:grid-cols-2 gap-8 mb-8">
{/* 성격 */}
<div className="bg-white rounded-2xl shadow-lg p-8">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-3xl mr-3">👤</span>
</h3>
<div className="space-y-3 text-gray-700">
<p className="leading-relaxed">
<strong className="text-indigo-600">{sajuData.day.stem}({sajuData.day.stemKr})</strong>
{sajuData.day.element === '木' && ' 나무처럼 성장하고 발전하려는 의지가 강합니다. 창의적이고 진취적인 성향을 가지고 있습니다.'}
{sajuData.day.element === '火' && ' 불처럼 열정적이고 활동적입니다. 리더십이 있고 사교성이 뛰어납니다.'}
{sajuData.day.element === '土' && ' 흙처럼 안정적이고 신뢰감 있습니다. 포용력이 있고 책임감이 강합니다.'}
{sajuData.day.element === '金' && ' 금속처럼 강인하고 원칙적입니다. 결단력 있고 의리를 중시합니다.'}
{sajuData.day.element === '水' && ' 물처럼 유연하고 지혜롭습니다. 적응력이 뛰어나고 사려 깊습니다.'}
</p>
</div>
</div>
{/* 운세 */}
<div className="bg-white rounded-2xl shadow-lg p-8">
<h3 className="text-2xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-3xl mr-3">🌟</span>
</h3>
<div className="space-y-3 text-gray-700">
<p className="leading-relaxed">
<strong className="text-purple-600">{sajuData.day.fortune}</strong>,
{sajuData.day.fortune === '장생' && ' 새로운 시작과 성장의 시기입니다.'}
{sajuData.day.fortune === '목욕' && ' 정화와 준비의 시기입니다.'}
{sajuData.day.fortune === '관대' && ' 사회적으로 인정받는 시기입니다.'}
{sajuData.day.fortune === '건록' && ' 안정되고 왕성한 활동의 시기입니다.'}
{sajuData.day.fortune === '제왕' && ' 최고의 전성기를 맞이하는 시기입니다.'}
{sajuData.day.fortune === '쇠' && ' 조금씩 힘이 약해지는 시기입니다.'}
{sajuData.day.fortune === '병' && ' 어려움이 있을 수 있는 시기입니다.'}
{sajuData.day.fortune === '사' && ' 끝과 새 시작을 준비하는 시기입니다.'}
{sajuData.day.fortune === '묘' && ' 잠시 휴식이 필요한 시기입니다.'}
{sajuData.day.fortune === '절' && ' 극복과 인내가 필요한 시기입니다.'}
{sajuData.day.fortune === '태' && ' 새로운 기운이 싹트는 시기입니다.'}
{sajuData.day.fortune === '양' && ' 성장을 준비하는 시기입니다.'}
</p>
</div>
</div>
</div>
{/* AI 상세 해석 */}
<div className="bg-gradient-to-br from-purple-50 to-indigo-50 rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2 text-center flex items-center justify-center">
<span className="text-4xl mr-3">🤖</span>
AI
</h2>
<p className="text-center text-gray-600 mb-8"> </p>
{/* 오행 균형 */}
<div className="bg-white rounded-2xl p-6 mb-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2"></span>
</h3>
<div className="grid grid-cols-5 gap-3">
{Object.entries(elementScores).map(([element, score]) => (
<div key={element} className="text-center">
<div className="text-2xl font-bold mb-1">{element}</div>
<div className="text-sm text-gray-600 mb-2">
{element === '木' && '목'}
{element === '火' && '화'}
{element === '土' && '토'}
{element === '金' && '금'}
{element === '水' && '수'}
</div>
<div className="w-full bg-gray-200 rounded-full h-2 mb-1">
<div
className={`h-2 rounded-full ${
element === sajuData.day.element
? 'bg-gradient-to-r from-indigo-500 to-purple-500'
: 'bg-gray-400'
}`}
style={{ width: `${score}%` }}
></div>
</div>
<div className="text-xs font-semibold text-gray-700">{score}%</div>
</div>
))}
</div>
</div>
{/* 장단점 */}
<div className="grid md:grid-cols-2 gap-6 mb-6">
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">💪</span>
</h3>
<ul className="space-y-2">
{interpretation.strengths.map((strength, i) => (
<li key={i} className="flex items-start text-gray-700">
<span className="text-green-600 mr-2"></span>
<span>{strength}</span>
</li>
))}
</ul>
</div>
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2"></span>
</h3>
<ul className="space-y-2">
{interpretation.weaknesses.map((weakness, i) => (
<li key={i} className="flex items-start text-gray-700">
<span className="text-orange-600 mr-2">!</span>
<span>{weakness}</span>
</li>
))}
</ul>
</div>
</div>
{/* 직업, 대인관계, 재물, 건강 */}
<div className="grid md:grid-cols-2 gap-6">
{/* 직업 */}
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">💼</span>
</h3>
<ul className="space-y-2">
{interpretation.career.map((item, i) => (
<li key={i} className="flex items-start text-gray-700 text-sm">
<span className="text-blue-600 mr-2"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
{/* 대인관계 */}
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">👥</span>
</h3>
<ul className="space-y-2">
{interpretation.relationships.map((item, i) => (
<li key={i} className="flex items-start text-gray-700 text-sm">
<span className="text-pink-600 mr-2"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
{/* 재물 */}
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">💰</span>
</h3>
<ul className="space-y-2">
{interpretation.wealth.map((item, i) => (
<li key={i} className="flex items-start text-gray-700 text-sm">
<span className="text-yellow-600 mr-2"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
{/* 건강 */}
<div className="bg-white rounded-2xl p-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">🏥</span>
</h3>
<ul className="space-y-2">
{interpretation.health.map((item, i) => (
<li key={i} className="flex items-start text-gray-700 text-sm">
<span className="text-red-600 mr-2"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
</div>
{/* 조언 */}
<div className="bg-white rounded-2xl p-6 mt-6">
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center">
<span className="text-2xl mr-2">💡</span>
AI의
</h3>
<div className="grid md:grid-cols-2 gap-3">
{interpretation.advice.map((item, i) => (
<div key={i} className="flex items-start text-gray-700 text-sm bg-indigo-50 p-3 rounded-lg">
<span className="text-indigo-600 mr-2"></span>
<span>{item}</span>
</div>
))}
</div>
</div>
<div className="mt-6 p-4 bg-purple-100 rounded-xl">
<p className="text-xs text-gray-700 text-center">
💡 AI . ,
.
</p>
</div>
</div>
{/* 대운 (大運) */}
<div className="bg-white rounded-3xl shadow-2xl p-8 md:p-12 mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-8 text-center">
🔄 () - 10
</h2>
{/* 현재 대운 */}
{currentDaeun && (
<div className="bg-gradient-to-r from-indigo-500 to-purple-500 rounded-2xl p-6 mb-8 text-white">
<h3 className="text-2xl font-bold mb-4 text-center"> </h3>
<div className="text-center mb-4">
<div className="text-5xl font-bold mb-2">
{currentDaeun.stem}{currentDaeun.branch}
</div>
<div className="text-xl mb-2">
{currentDaeun.stemKr}{currentDaeun.branchKr}
</div>
<div className="text-lg opacity-90">
{currentDaeun.age} ~ {currentDaeun.age + 9} ({currentDaeun.startYear} ~ {currentDaeun.endYear})
</div>
</div>
<p className="text-center leading-relaxed">
{getDaeunDescription(currentDaeun, sajuData.day.stem)}
</p>
</div>
)}
{/* 전체 대운 목록 */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
{daeunList.map((daeun, index) => {
const isCurrent = currentDaeun &&
daeun.startYear === currentDaeun.startYear &&
daeun.endYear === currentDaeun.endYear;
return (
<div
key={index}
className={`rounded-xl p-4 border-2 transition ${
isCurrent
? 'bg-indigo-50 border-indigo-400'
: 'bg-gray-50 border-gray-200 hover:border-indigo-300'
}`}
>
<div className="text-center">
<div className="text-3xl font-bold text-gray-900 mb-1">
{daeun.stem}{daeun.branch}
</div>
<div className="text-sm text-gray-600 mb-2">
{daeun.stemKr}{daeun.branchKr}
</div>
<div className="text-xs text-gray-500">
{daeun.age} ~ {daeun.age + 9}
</div>
<div className="text-xs text-gray-400">
{daeun.startYear} ~ {daeun.endYear}
</div>
{isCurrent && (
<div className="mt-2">
<span className="inline-block bg-indigo-600 text-white text-xs px-3 py-1 rounded-full font-semibold">
</span>
</div>
)}
</div>
</div>
);
})}
</div>
<div className="mt-6 p-4 bg-indigo-50 rounded-xl">
<p className="text-sm text-gray-700 mb-2">
<strong className="text-indigo-600">():</strong> 10 .
, .
</p>
{daeunList.length > 0 && (
<p className="text-xs text-gray-600">
* {daeunList[0].age} . (3 = 1)
</p>
)}
</div>
</div>
{/* 추가 기능 버튼 */}
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<Link
href={`/fortune?${new URLSearchParams(params as any).toString()}`}
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">🌟</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<Link
href="/compatibility"
className="bg-white rounded-xl p-6 shadow-lg hover:shadow-xl transition text-center group"
>
<div className="text-4xl mb-3">💕</div>
<h3 className="text-xl font-bold text-gray-900 mb-2"> </h3>
<p className="text-gray-600 text-sm"> </p>
</Link>
<PDFButton
elementId="pdf-content"
filename={`사주팔자_${yearNum}${monthNum}${dayNum}.pdf`}
buttonText="사주 PDF 저장"
/>
<ShareButtons
title={`내 사주팔자 - ${yearNum}년생 ${gender === 'male' ? '남성' : '여성'}`}
description={`일간: ${sajuData.day.stem}(${sajuData.day.stemKr}) | ${sajuData.day.element}`}
/>
</div>
</div>
{/* Footer */}
<footer className="bg-gray-900 text-white py-12 px-4 mt-20">
<div className="max-w-7xl mx-auto text-center">
<div className="text-2xl font-bold mb-4 bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
🔮
</div>
<p className="text-gray-400 mb-6">
</p>
<div className="text-sm text-gray-500">
<p>문의: bgg8988@gmail.com | <a href="https://jaengseung-made.com" target="_blank" rel="noopener noreferrer" className="hover:text-indigo-400"></a></p>
<p className="mt-2">&copy; 2025 . All rights reserved.</p>
</div>
</div>
</footer>
</div>
);
}