diff --git a/src/apis/mbti.ts b/src/apis/mbti.ts index 9de8982..f4a3b27 100644 --- a/src/apis/mbti.ts +++ b/src/apis/mbti.ts @@ -10,14 +10,14 @@ export type ScalpMbtiSummary = { // 한국어 라벨 → 카드 키 const MBTI_KO_MAP: Record = { - '트러블 폭풍형': 'oily_trouble', '지성 민감형': 'oily_sensitive', - '지성 비듬형': 'oily_dandruff', - '깔끔 지성형': 'clean_oily', - '깐깐 지성형': 'clean_oily', - '건조 트러블형': 'dry_trouble', - '민감 건조형': 'dry_sensitive', - '건조 비듬형': 'dry_scaling', + '지성 비듬형': 'oily_scaling', + '지성 깔끔형': 'oily_clean', + '지성 트러블형': 'oily_trouble', + '건성 민감형': 'dry_sensitive', + '건성 비듬형': 'dry_scaling', + '건성 깔끔형': 'dry_clean', + '건성 트러블형': 'dry_trouble', 밸런스형: 'balanced', } diff --git a/src/components/Monthly/TrendChart.tsx b/src/components/Monthly/TrendChart.tsx index 8b0195a..abc1a3c 100644 --- a/src/components/Monthly/TrendChart.tsx +++ b/src/components/Monthly/TrendChart.tsx @@ -1,3 +1,135 @@ +// import { +// LineChart, +// Line, +// XAxis, +// YAxis, +// Tooltip, +// ResponsiveContainer, +// ReferenceLine, +// CartesianGrid, +// } from 'recharts' +// import { useMemo } from 'react' +// import type { MonthlyRecord, MetricKey } from '../../types/report' + +// // Recharts 이벤트에서 필요한 최소 속성 +// type ChartStateLite = { activeLabel?: string | number } +// const isChartState = (x: unknown): x is ChartStateLite => +// typeof x === 'object' && +// x !== null && +// 'activeLabel' in (x as Record) + +// interface Props { +// data: MonthlyRecord[] +// metricKey: MetricKey +// selectedLabel: string | null +// onSelectLabel: (label: string) => void +// } + +// export default function TrendChart({ +// data, +// metricKey, +// selectedLabel, +// onSelectLabel, +// }: Props) { +// const chartData = useMemo( +// () => +// data.map((d) => ({ +// month: d.month, +// label: d.month.slice(5), +// value: d.values[metricKey], // 0~100 +// })), +// [data, metricKey], +// ) + +// const current = useMemo(() => { +// if (!selectedLabel) return null +// return chartData.find((d) => d.label === selectedLabel) ?? null +// }, [chartData, selectedLabel]) + +// const displayLabel = current +// ? `${current.month.slice(0, 4)}년 ${current.label}월 평균 ${current.value.toFixed(1)}점` +// : '' + +// const pickLabel = (s: unknown) => { +// if (!isChartState(s) || s.activeLabel == null) return +// onSelectLabel(String(s.activeLabel).padStart(2, '0')) +// } + +// return ( +//
+// {/* 상단 중앙 라벨 */} +// {displayLabel && ( +//
+// {displayLabel} +//
+// )} + +// {/* 차트 */} +//
+// +// +// +// +// +// null} cursor={false} /> + +// {[0, 25, 50, 75, 100].map((y) => ( +// +// ))} + +// {selectedLabel && ( +// +// )} + +// +// +// +//
+//
+// ) +// } + import { LineChart, Line, @@ -23,6 +155,10 @@ interface Props { metricKey: MetricKey selectedLabel: string | null onSelectLabel: (label: string) => void + /** 한 화면에 보일 개수 (기본 3개월) */ + visibleCount?: number + /** 포인트 간 가로 간격(px) (기본 120px) */ + pointWidth?: number } export default function TrendChart({ @@ -30,12 +166,14 @@ export default function TrendChart({ metricKey, selectedLabel, onSelectLabel, + visibleCount = 3, + pointWidth = 120, }: Props) { const chartData = useMemo( () => data.map((d) => ({ month: d.month, - label: d.month.slice(5), + label: d.month.slice(5), // 'MM' value: d.values[metricKey], // 0~100 })), [data, metricKey], @@ -55,6 +193,12 @@ export default function TrendChart({ onSelectLabel(String(s.activeLabel).padStart(2, '0')) } + // 🔥 가로 스크롤 폭 설정: 데이터 개수 * 간격 (최소는 보이는 개수 * 간격) + const innerWidth = Math.max( + chartData.length * pointWidth, + visibleCount * pointWidth, + ) + return (
{/* 상단 중앙 라벨 */} @@ -68,63 +212,70 @@ export default function TrendChart({
)} - {/* 차트 */} -
- - - - - - null} cursor={false} /> - - {[0, 25, 50, 75, 100].map((y) => ( - + {/* 스크롤 대상의 실제 너비를 데이터에 맞춰 늘림 */} +
+ + + + {/* 눈금/축은 그대로. label은 'MM' 값 */} + - ))} + + null} cursor={false} /> + + {[0, 25, 50, 75, 100].map((y) => ( + + ))} - {selectedLabel && ( - + )} + + - )} - - - - + + +
) diff --git a/src/components/ProductRecommendation/MbtiCardList.tsx b/src/components/ProductRecommendation/MbtiCardList.tsx index 5d8fcc7..63e3e6d 100644 --- a/src/components/ProductRecommendation/MbtiCardList.tsx +++ b/src/components/ProductRecommendation/MbtiCardList.tsx @@ -1,3 +1,93 @@ +// src/components/ProductRecommendation/MbtiCardList.tsx +// import { useSearchParams } from 'react-router-dom' +// import { +// mbtiCardList, +// type MbtiCardKey, +// MBTI_KO_TO_KEY, +// } from '../../data/mbtiCardData' + +// interface MbtiCardProps { +// mbtiType?: MbtiCardKey | null +// } + +// // 문자열 -> MbtiCardKey (한글/하이픈/공백/*_type 허용) +// function toMbtiKey(v: string | null): MbtiCardKey | null { +// if (!v) return null +// const norm = v +// .trim() +// .toLowerCase() +// .replace(/[\s-]+/g, '_') +// .replace(/_type$/, '') +// const alias = +// (MBTI_KO_TO_KEY as Record)[v] || // 원문(한글 등) +// (MBTI_KO_TO_KEY as Record)[norm] || // 정규화 키 +// norm + +// return mbtiCardList.some((c) => c.type === alias) +// ? (alias as MbtiCardKey) +// : null +// } + +// const MbtiCardList = ({ mbtiType }: MbtiCardProps) => { +// const [params] = useSearchParams() +// const isDemo = params.get('demo') === '1' +// const paramType = toMbtiKey(params.get('mbti')) + +// // 우선순위: (데모면 URL 최우선) -> props -> URL -> (데모 기본값) -> null +// const resolvedType: MbtiCardKey | null = +// (isDemo && paramType) || +// mbtiType || +// paramType || +// (isDemo ? (mbtiCardList[0]?.type ?? null) : null) + +// if (!resolvedType) return null + +// const card = mbtiCardList.find((c) => c.type === resolvedType) +// if (!card) return null + +// return ( +//
+// {/* 내용 박스 */} +//
+//
+// {card.good && ( +//

+// +// {card.good} +//

+// )} +// {card.bad && ( +//

+// +// {card.bad} +//

+// )} +// {card.tips && ( +//

+// 💡 +// {card.tips} +//

+// )} +//
+//
+ +// {/* MBTI pill */} +//
+// {card.title} +//
+ +// {/* 데모 배지 */} +// {isDemo && ( +//
+// DEMO +//
+// )} +//
+// ) +// } + +// export default MbtiCardList + import { mbtiCardList, type MbtiCardKey } from '../../data/mbtiCardData' interface MbtiCardProps { @@ -14,7 +104,6 @@ const MbtiCardList = ({ mbtiType }: MbtiCardProps) => { {/* 내용 박스 */}
- {card.description &&

{card.description}

} {card.good && (

@@ -27,6 +116,12 @@ const MbtiCardList = ({ mbtiType }: MbtiCardProps) => { {card.bad}

)} + {card.tips && ( +

+ 💡 + {card.tips} +

+ )}
diff --git a/src/components/Recommendation/BarChart.tsx b/src/components/Recommendation/BarChart.tsx index 2db5bdd..f69b065 100644 --- a/src/components/Recommendation/BarChart.tsx +++ b/src/components/Recommendation/BarChart.tsx @@ -1,17 +1,38 @@ interface BarChartProps { - data: { label: string; value: number }[] + data: { label: string; value: number }[] // value: 0~100 + minPercent?: number // 막대 최소 높이(보이기용). 기본 8(%) + showValue?: boolean // 수치 표시 여부 } -const BarChart = ({ data }: BarChartProps) => { +const BarChart = ({ + data, + minPercent = 8, + showValue = false, +}: BarChartProps) => { return ( -
- {data.map(({ value }, index) => ( -
- ))} +
+ {data.map(({ label, value }, idx) => { + const pct = Math.max(value, value === 0 ? 0 : minPercent) + // 값이 0이면 진짜 0, 0보다 크면 최소 높이 보정 + return ( +
+ {/* 트랙 */} +
+ {/* 채움 */} +
+
+ {showValue && ( + + {Math.round(value)} + + )} + {label} +
+ ) + })}
) } diff --git a/src/components/Recommendation/ScalpMbtiCard.tsx b/src/components/Recommendation/ScalpMbtiCard.tsx index 64baa54..3d95127 100644 --- a/src/components/Recommendation/ScalpMbtiCard.tsx +++ b/src/components/Recommendation/ScalpMbtiCard.tsx @@ -1,20 +1,181 @@ +// import { useSearchParams } from 'react-router-dom' +// import type { ScalpMbtiType } from '../../types/scalp-mbti' +// import BarChart from './BarChart' +// import { mbtiData } from '../../data/mbtiData' + +// interface ScalpMbtiCardProps { +// mbtiType?: ScalpMbtiType | null +// displayName: string +// diagnosed?: boolean +// onClickDiagnose?: () => void +// } + +// const DIAG_FLAG_KEY = 'scalp_diagnosed' + +// // 문자열 -> ScalpMbtiType 안전 변환 +// function toScalpKey(v: string | null): ScalpMbtiType | null { +// if (!v) return null +// return Object.prototype.hasOwnProperty.call(mbtiData, v) +// ? (v as ScalpMbtiType) +// : null +// } + +// function toChartData( +// ratings: { +// oil: number | [number, number] +// sensitivity: number | [number, number] +// scaling: number | [number, number] +// }, +// { toPercent = true } = {}, +// ) { +// const pick = (v: number | [number, number]) => +// Array.isArray(v) ? Math.round((v[0] + v[1]) / 2) : v + +// const lvl = { +// oil: pick(ratings.oil), +// sensitivity: pick(ratings.sensitivity), +// scaling: pick(ratings.scaling), +// } +// const map = (x: number) => (toPercent ? Math.round((x / 4) * 100) : x) + +// return [ +// { label: '유분', value: map(lvl.oil) }, +// { label: '민감도', value: map(lvl.sensitivity) }, +// { label: '각질', value: map(lvl.scaling) }, +// ] +// } + +// const ScalpMbtiCard = ({ +// mbtiType, +// displayName, +// diagnosed, +// onClickDiagnose, +// }: ScalpMbtiCardProps) => { +// const [params] = useSearchParams() +// const isDemo = params.get('demo') === '1' +// const paramType = toScalpKey(params.get('mbti')) + +// // 진단 여부 우선순위: props -> 로컬스토리지 -> 데모 강제 true +// const localDiag = +// typeof window !== 'undefined' && localStorage.getItem(DIAG_FLAG_KEY) === '1' +// const isDiagnosed = +// isDemo || (typeof diagnosed === 'boolean' ? diagnosed : localDiag) +// // 타입 우선순위: props -> URL 파라미터 -> 데모 기본값(첫 키) +// const resolvedType: ScalpMbtiType | null = +// mbtiType ?? +// paramType ?? +// (isDemo ? (Object.keys(mbtiData)[0] as ScalpMbtiType) : null) + +// // 진단 전 / 타입 없음 → 안내 카드 +// if (!isDiagnosed || !resolvedType) { +// return ( +//
+//

+// {displayName}님의 두피 MBTI +//

+//

두피 MBTI가 아직 없습니다!

+// {onClickDiagnose && ( +// +// )} +//
+// ) +// } + +// const info = mbtiData[resolvedType] +// if (!info) { +// return ( +//
+//

+// {displayName}님의 두피 MBTI +//

+//

+// 알 수 없는 MBTI 타입입니다. 다시 시도해 주세요. +//

+//
+// ) +// } + +// const { title, description, ratings } = info +// const chartData = toChartData(ratings, { toPercent: true }) + +// return ( +//
+//
+//

+// {displayName}님의 두피 MBTI +//

+//

{title}

+//
+// {description.split('\n').map((line, i) => ( +//

+// {line} +//

+// ))} +//
+//
+//
+// +//
+ +// {isDemo && ( +//
+// DEMO +//
+// )} +//
+// ) +// } + +// export default ScalpMbtiCard + import type { ScalpMbtiType } from '../../types/scalp-mbti' import BarChart from './BarChart' import { mbtiData } from '../../data/mbtiData' interface ScalpMbtiCardProps { - /** 서버/부모에서 내려주는 진단 타입. 진단 전이면 null/undefined 로 넘겨주세요. */ mbtiType?: ScalpMbtiType | null - /** 상단에 보여줄 사용자명 */ displayName: string - /** (선택) 진단 완료 여부를 부모가 명시적으로 제어하고 싶을 때 */ diagnosed?: boolean - /** (선택) 진단하기 버튼을 쓸 경우 콜백 (없으면 버튼 안 보여줌) */ onClickDiagnose?: () => void } const DIAG_FLAG_KEY = 'scalp_diagnosed' +function toChartData( + ratings: { + oil: number | [number, number] + sensitivity: number | [number, number] + scaling: number | [number, number] + }, + { toPercent = true } = {}, +) { + const pick = (v: number | [number, number]) => { + if (Array.isArray(v)) return Math.round((v[0] + v[1]) / 2) + return v + } + const lvl = { + oil: pick(ratings.oil), + sensitivity: pick(ratings.sensitivity), + scaling: pick(ratings.scaling), + } + const map = (x: number) => (toPercent ? Math.round((x / 4) * 100) : x) + + return [ + { label: '유분', value: map(lvl.oil) }, + { label: '민감도', value: map(lvl.sensitivity) }, + { label: '각질', value: map(lvl.scaling) }, + ] +} + const ScalpMbtiCard = ({ mbtiType, displayName, @@ -64,7 +225,8 @@ const ScalpMbtiCard = ({ ) } - const { title, description, radarValues } = info + const { title, description, ratings } = info + const chartData = toChartData(ratings, { toPercent: true }) return (
-
- +
+
) diff --git a/src/data/mbtiCardData.ts b/src/data/mbtiCardData.ts index 7e6d8f3..cce1330 100644 --- a/src/data/mbtiCardData.ts +++ b/src/data/mbtiCardData.ts @@ -1,177 +1,98 @@ -// export type MbtiCardKey = -// | 'oily_trouble' -// | 'dry_trouble' -// | 'oily_sensitive' -// | 'dry_sensitive' -// | 'balanced' -// | 'clean_oily' -// | 'dry_mild' -// | 'oily_dandruff' - -// export interface MbtiCardInfo { -// type: string -// title: string -// description: string -// good: string -// bad: string -// } - -// export const mbtiCardList: MbtiCardInfo[] = [ -// { -// type: 'oily_trouble', -// title: '🔥 트러블 폭풍형', -// description: -// '과도한 유분, 고염증, 고각질로 인해 두피가 민감하고 비듬이 심해질 수 있어요.', -// good: '티트리오일, 살리실산, 녹차추출물 등의 진정 & 피지 조절 성분이 포함된 제품을 사용해보세요.', -// bad: '실리콘, 향료, 무거운 오일 성분은 피해주세요.', -// }, -// { -// type: 'dry_trouble', -// title: '🌋 건조 트러블형', -// description: -// '과도한 유분, 고염증, 고각질로 인해 두피가 건조하고 비듬이 심해질 수 있어요.', -// good: '티트리오일, 살리실산, 녹차추출물 등의 진정 & 피지 조절 성분이 포함된 제품을 사용해보세요.', -// bad: '실리콘, 향료, 무거운 오일 성분은 피해 주세요.', -// }, -// { -// type: 'oily_sensitive', -// title: '🌊 지성 민감형', -// description: -// '과도한 피지와 민감한 두피로 인해 트러블이 자주 올라올 수 있어요.', -// good: '병풀, 시카, 캐모마일 등 자극을 완화하는 진정 성분을 챙겨주세요.', -// bad: '멘톨, 알코올, 합성향료는 오히려 자극이 될 수 있어요.', -// }, -// { -// type: 'dry_sensitive', -// title: '❄️ 건조 비듬형', -// description: -// '유분은 적고 각질이 두드러지는 두피예요. 비듬과 간지러움이 고민이시죠?', -// good: '살리실산, 징크피리치온, 티트리 성분이 들어간 비듬 케어 샴푸를 사용해보세요.', -// bad: '실리콘, 무거운 오일 성분은 비듬을 더 악화시킬 수 있어요.', -// }, -// { -// type: 'balanced', -// title: '⚖️ 밸런스형', -// description: '두피 유분, 염증, 각질 상태가 전반적으로 균형 잡혀 있어요.', -// good: '비오틴, 케라틴, 콜라겐, 아르간오일로 영양을 더해주세요.', -// bad: '특별히 피해야 할 성분은 없지만, 자극적인 성분은 가급적 피해 주세요.', -// }, -// { -// type: 'clean_oily', -// title: '💧 깔끔 지성형', -// description: -// '피지 분비는 많지만 각질과 염증은 적어 상대적으로 깔끔한 두피예요.', -// good: '티트리, 녹차, 라벤더오일처럼 가벼운 지성 케어 성분이 좋아요.', -// bad: '과한 오일, 실리콘 계열은 피지가 더 늘어날 수 있어요.', -// }, -// { -// type: 'dry_mild', -// title: '🌿 민감 건조형', -// description: '외부 자극에 약하고 쉽게 당기거나 따가운 두피예요.', -// good: '시카, 캐모마일, 알란토인 같은 민감성 진정 성분이 좋아요.', -// bad: '멘톨, 알코올, 향료는 피하고 저자극 제품을 선택해보세요.', -// }, -// { -// type: 'oily_dandruff', -// title: '💥 지성 비듬형', -// description: -// '유분은 많고 각질이 두드러지는 두피예요. 비듬과 간지러움이 고민이시죠?', -// good: '살리실산, 징크피리치온, 티트리 성분이 들어간 비듬 케어 샴푸를 사용해보세요.', -// bad: '실리콘, 무거운 오일 성분은 비듬을 더 악화시킬 수 있어요.', -// }, -// ] export type MbtiCardKey = - | 'oily_trouble' // 트러블 폭풍형 | 'oily_sensitive' // 지성 민감형 - | 'oily_dandruff' // 지성 비듬형(= oily_scaling) - | 'clean_oily' // 깔끔/깐깐 지성형 - | 'dry_trouble' // 건조 트러블형 - | 'dry_sensitive' // 민감 건조형 - | 'dry_scaling' // 건조 비듬형 + | 'oily_scaling' // 지성 비듬형 + | 'oily_clean' // 지성 깔끔형 + | 'oily_trouble' // 지성 트러블형 + | 'dry_sensitive' // 건성 민감형 + | 'dry_scaling' // 건성 비듬형 + | 'dry_clean' //건성 깔끔형 + | 'dry_trouble' // 건성 트러블형 | 'balanced' // 밸런스형 export interface MbtiCardInfo { type: MbtiCardKey title: string - description: string good: string bad: string + tips: string } -/** (참고용) 백엔드 라벨 → 카드 키 */ export const MBTI_KO_TO_KEY: Record = { - '트러블 폭풍형': 'oily_trouble', '지성 민감형': 'oily_sensitive', - '지성 비듬형': 'oily_dandruff', - '깔끔 지성형': 'clean_oily', - '깐깐 지성형': 'clean_oily', - '건조 트러블형': 'dry_trouble', - '민감 건조형': 'dry_sensitive', - '건조 비듬형': 'dry_scaling', + '지성 비듬형': 'oily_scaling', + '지성 깔끔형': 'oily_clean', + '지성 트러블형': 'oily_trouble', + '건성 민감형': 'dry_sensitive', + '건성 비듬형': 'dry_scaling', + '건성 깔끔형': 'dry_clean', + '건성 트러블형': 'dry_trouble', 밸런스형: 'balanced', } export const mbtiCardList: MbtiCardInfo[] = [ - { - type: 'oily_trouble', - title: '🔥 트러블 폭풍형', - description: - '과도한 유분, 고염증, 고각질로 인해 두피가 민감하고 비듬이 심해질 수 있어요.', - good: '티트리오일, 살리실산, 녹차추출물 등 진정 & 피지 조절 성분이 포함된 제품을 사용해보세요.', - bad: '실리콘, 향료, 무거운 오일 성분은 피해주세요.', - }, { type: 'oily_sensitive', - title: '🌊 지성 민감형', - description: - '과도한 피지와 민감한 두피로 인해 트러블이 자주 올라올 수 있어요.', - good: '병풀/시카, 캐모마일, 판테놀 등 자극을 완화하는 진정 성분을 챙겨주세요.', - bad: '멘톨, 알코올, 합성향료는 오히려 자극이 될 수 있어요.', + title: '🚨 지성 민감형', + good: '티트리 오일, 녹차 추출물 등 피지 조절 성분\n카모마일, 판테놀 등 진정 효과가 있는 성분', + bad: '멘톨, 알코올, 합성향료 등 자극 유발 성분', + tips: '혈행이 원활하도록 목의 근육을 풀어주고\n두피를 진정시킬 수 있는\n저자극성 샴푸 사용이 필요합니다.', }, { - type: 'oily_dandruff', - title: '💥 지성 비듬형', - description: - '유분은 많고 각질이 두드러지는 두피예요. 비듬과 간지러움이 고민이시죠?', - good: '살리실산, 징크피리치온, 티트리 성분이 들어간 비듬 케어 샴푸를 사용해보세요.', - bad: '실리콘, 무거운 오일 성분은 비듬을 더 악화시킬 수 있어요.', + type: 'oily_scaling', + title: '💧 지성 비듬형', + good: '티트리 오일, 녹차 추출물 등 피지 조절 성분\n살리실산, 레조르신 등 각질 용해 성분', + bad: '실리콘, 무거운 오일 성분', + tips: '약알칼리성 샴푸나 향진균제 비듬 샴푸 사용을\n권장합니다. 지성 비듬형은 청결이 중요하며,\n뜨거운 물이나 바람도 두피의 유분율을 촉진하므로 주의해야 합니다.', }, + { - type: 'clean_oily', - title: '💧 깔끔 지성형', - description: - '피지 분비는 많지만 각질과 염증은 적어 상대적으로 깔끔한 두피예요.', - good: '티트리, 녹차, 라벤더오일처럼 가벼운 지성 케어 성분이 좋아요.', - bad: '과한 오일, 실리콘 계열은 피지가 더 늘어날 수 있어요.', + type: 'oily_clean', + title: '🫧 지성 깔끔형', + good: '티트리 오일, 녹차 추출물 등 피지 조절 성분\n라벤더 오일 등 가벼운 식물성 오일', + bad: '과한 오일, 실리콘 계열', + tips: '샴푸 전에 브러싱을 통해 노폐물을 제거하고 혈행을 촉진시키는 것이 중요합니다. 또한 지성 샴푸를 사용하여 세정 후 가볍게 두피와 모발을 말려주세요.', }, { - type: 'dry_trouble', - title: '🌋 건조 트러블형', - description: - '유분은 보통이지만 염증/각질이 높아 건조 트러블과 비듬이 동반될 수 있어요.', - good: '세라마이드, 판테놀, 오트밀 등 진정·보습 성분 위주로 케어하세요.', - bad: '강력 세정, 강한 쿨링 제품은 자극이 될 수 있어요.', + type: 'oily_trouble', + title: '🔥 지성 트러블형', + good: '실리콘, 미네랄오일, 합성향료', + bad: '실리콘, 향료, 무거운 오일 성분은 피해주세요.', + tips: '살균, 세정에 신경을 쓰는 것이 중요합니다.\n지나치게 자극적인 제품으로 샴푸를 하게 되면\n피지선에 악영향을 끼치므로, 저자극성 샴푸를\n사용하세요.', }, { type: 'dry_sensitive', - title: '🌿 민감 건조형', - description: '외부 자극에 약하고 쉽게 당기거나 따가운 두피예요.', - good: '시카/병풀, 캐모마일, 알란토인 등 저자극 진정·보습 성분이 좋아요.', - bad: '멘톨, 알코올, 향료는 피하고 저자극 제품을 선택해보세요.', + title: '🍀 건성 민감형', + good: ' 알로에베라, 히알루론산 등 보습 성분\n카모마일, 판테놀 등 진정 효과가 있는 성분', + bad: '멘톨, 알코올, 합성향료 등 자극 유발 성분', + tips: '과다한 스타일링제의 접촉은 피하고, 모발 건조 시 드라이기 이용이나 사우나 등을 피하는 것이\n좋습니다.', }, { type: 'dry_scaling', - title: '❄️ 건조 비듬형', - description: '유분은 적고 각질이 두드러져 비듬과 간지러움이 동반돼요.', - good: '징크피리치온, 피록톤올아민, 알로에베라 등 비듬/진정/보습 성분을 추천해요.', - bad: '황산염, 에탄올, 강한 멘톨 계열은 자극을 줄 수 있어요.', + title: '❄️ 건성 비듬형', + good: ' 알로에베라, 히알루론산 등 보습 성분\n살리실산, 레조르신 등 각질 용해 성분', + bad: '실리콘', + tips: '유분이 많은 트리트먼트를 사용해서 유수분을\n보충하고 건조를 방지하는 관리가 필요합니다.\n약산성 샴푸나 향진균제 샴푸를 사용하세요.', + }, + { + type: 'dry_clean', + title: '🧡 건성 깔끔형', + good: '알로에베라, 히알루론산 등 보습 성분\n가벼운 아미노산계 계면활성제', + bad: '실리콘, 무거운 오일 성분', + tips: '피지 조절에 포인트를 두어 마사지와 앰플을\n사용하고, 드라이기의 과도한 열사용을 자제하는\n것이 중요합니다.', }, + { + type: 'dry_trouble', + title: '🌸 건성 트러블형', + good: '알로에베라, 히알루론산 등 보습 성분', + bad: ' 실리콘, 향료, 무거운 오일 성분', + tips: '오래된 각질 및 비듬의 제거와 피지분비가 원활하게\n이루어지도록 일반 샴푸보다는 보습성이 함유된\n샴푸 사용을 권장합니다.', + }, + { type: 'balanced', - title: '⚖️ 밸런스형', - description: - '수분/유분/자극도가 전반적으로 균형 잡힌 이상적인 컨디션이에요.', - good: '비오틴, 케라틴, 아르간오일 등 영양을 가볍게 더해보세요.', - bad: '특별히 피할 성분은 없지만 과한 관리나 자극적인 성분은 지양하세요.', + title: '🗓️ 밸런스형', + good: '비오틴, 케라틴, 콜라겐, 아르간오일', + bad: '특별히 피해야 할 성분은 없지만,\n자극적인 성분은 가급적 피해주세요.', + tips: '현상태를 유지할 수 있도록 각질제거, 유수분\n밸런스 유지에 중점을 둔 관리가 필요합니다.', }, ] diff --git a/src/data/mbtiData.ts b/src/data/mbtiData.ts index c36743e..0783e23 100644 --- a/src/data/mbtiData.ts +++ b/src/data/mbtiData.ts @@ -1,149 +1,139 @@ import type { ScalpMbtiType } from '../types/scalp-mbti' +export type Level = 1 | 2 | 3 | 4 +export type LevelRange = Level | [Level, Level] + +// 3축 구조: 유분/민감도/각질 */ +export type MbtiRatings = { + oil: LevelRange // 유분 + sensitivity: LevelRange // 민감도(자극/염증) + scaling: LevelRange // 각질/비듬 +} + +// 카드 공통 데이터(차트 값은 ratings로 통일) +export type MbtiCardCore = { + title: string + description: string + // avoidIngredients: string[] + // recommendedIngredients: string[] + // tips: string[] + ratings: MbtiRatings +} + export const mbtiData: Record< ScalpMbtiType, { title: string description: string - avoidIngredients: string[] - recommendedIngredients: string[] - tips: string[] - radarValues: { label: string; value: number }[] + // avoidIngredients: string[] + // recommendedIngredients: string[] + // tips: string[] + ratings: MbtiRatings } > = { - oily_trouble_type: { - title: '🔥 트러블 폭풍형', - description: - '과도한 유분 + 고염증 + 고각질\n → 모공 막힘 + 피지 폭발 + 비듬 습격', - avoidIngredients: ['실리콘', '미네랄오일', '합성향료'], - recommendedIngredients: ['살리실산', '녹차 추출물', '마데카소사이드'], - tips: ['두피 스크럽 주 1회', '피지 조절 샴푸 사용', '밤에는 모자 피하기'], - radarValues: [ - { label: '유분', value: 85 }, - { label: '염증', value: 80 }, - { label: '각질', value: 70 }, - { label: '수분', value: 40 }, - { label: '자극', value: 75 }, - ], - }, - oily_sensitive_type: { - title: '🔥 지성 민감형', - description: - '과도한 유분 + 고염증 + 자극성\n → 피지 + 트러블 + 잦지만 각질은 적음', - avoidIngredients: ['에탄올', '합성 계면활성제', '멘톨'], - recommendedIngredients: ['티트리', '판테놀', '병풀 추출물'], - tips: ['두피 쿨링 미스트 활용', '샴푸는 저자극 제품', '뜨거운 바람 최소화'], - radarValues: [ - { label: '유분', value: 80 }, - { label: '염증', value: 75 }, - { label: '각질', value: 40 }, - { label: '수분', value: 45 }, - { label: '자극', value: 80 }, - ], + title: '🚨 지성 민감형', + description: '기름지고 예민한 두피\n진정과 균형이 동시에 필요', + // avoidIngredients: ['멘톨', '에탄올', '합성향료'], + // recommendedIngredients: ['티트리', '판테놀', '병풀 추출물'], + // tips: ['두피 쿨링 미스트 활용', '샴푸는 저자극 제품', '뜨거운 바람 최소화'], + ratings: { oil: 3, sensitivity: 4, scaling: 2 }, }, oily_scaling_type: { title: '💧 지성 비듬형', - description: - '과도한 유분 + 저염증 + 고각질\n → 기름 + 각질 + 비듬이 주 증상', - avoidIngredients: ['실리콘', '중금속 염료', '파라벤'], - recommendedIngredients: ['징크피리치온', '피록톤올아민', '로즈마리 추출물'], - tips: ['하루 1회 이상 샴푸', '각질 제거 팩 주 1회', '정수리 중심 마사지'], - radarValues: [ - { label: '유분', value: 85 }, - { label: '염증', value: 30 }, - { label: '각질', value: 75 }, - { label: '수분', value: 45 }, - { label: '자극', value: 50 }, - ], + description: '유분이 많고 각질이 두드러짐\n비듬케어가 필요', + // avoidIngredients: ['실리콘', '중금속 염료', '파라벤'], + // recommendedIngredients: ['징크피리치온', '피록톤올아민', '로즈마리 추출물'], + // tips: ['하루 1회 이상 샴푸', '각질 제거 팩 주 1회', '정수리 중심 마사지'], + ratings: { oil: 4, sensitivity: 2, scaling: 4 }, }, - clean_oily_type: { - title: '🔥 깔끔 지성형', + oily_clean_type: { + title: '🫧 지성 깔끔형', description: - '과도한 유분 + 저염증 + 자극적\n → 기름 + 민감한 자극 + 간지러움 적응', - avoidIngredients: ['알코올', '향료', '황산염'], - recommendedIngredients: ['라벤더 오일', '알란토인', '카모마일'], - tips: [ - '아침/저녁 수분 스프레이', - '가벼운 보습 제품', - '모자 착용 시간 줄이기', - ], - radarValues: [ - { label: '유분', value: 85 }, - { label: '염증', value: 35 }, - { label: '각질', value: 40 }, - { label: '수분', value: 55 }, - { label: '자극', value: 70 }, - ], + '피지분비는 많고 각질/염증이 적음\n꼼꼼한 세정과 가벼운 각질 케어', + // avoidIngredients: ['과한 오일', '실리콘 계열'], + // recommendedIngredients: ['라벤더 오일', '티트리 오일', '녹차 추출물'], + // tips: [ + // '아침/저녁 수분 스프레이', + // '가벼운 보습 제품', + // '모자 착용 시간 줄이기', + // ], + ratings: { oil: 3, sensitivity: 1, scaling: 2 }, }, - dry_trouble_type: { - title: '🔥 건조 트러블형', - description: '정상 유분 + 고염증 + 고각질\n → 건조 + 자극 + 비듬 동반', - avoidIngredients: ['에탄올', '황산염', '합성 계면활성제'], - recommendedIngredients: ['세라마이드', '판테놀', '오트밀'], - tips: [ - '뜨거운 물 X, 미지근한 물로 샴푸', - '샴푸 후 두피 보습제 사용', - '드라이 시 찬바람', - ], - radarValues: [ - { label: '유분', value: 40 }, - { label: '염증', value: 80 }, - { label: '각질', value: 75 }, - { label: '수분', value: 30 }, - { label: '자극', value: 70 }, - ], + oily_trouble_type: { + title: '🔥 지성 트러블형', + description: + '민감하고 트러블이 생기는 두피\n예민한 두피에는 진정 케어가 필요', + // avoidIngredients: ['실리콘', '미네랄오일', '합성향료'], + // recommendedIngredients: ['티트리 오일', '녹차 추출물'], + // tips: ['두피 스크럽 주 1회', '피지 조절 샴푸 사용', '밤에는 모자 피하기'], + ratings: { oil: 4, sensitivity: 4, scaling: 4 }, }, dry_sensitive_type: { - title: '🌱 민감 건조형', - description: '정상 유분 + 고자극 + 고건조\n → 당김 + 민감한 두피', - avoidIngredients: ['에탄올', '합성향료', 'SLS'], - recommendedIngredients: ['병풀 추출물', '히알루론산', '마데카소사이드'], - tips: [ - '하루 1회 샴푸 유지', - '수분 케어 제품 위주 사용', - '두피 미스트 활용', - ], - radarValues: [ - { label: '유분', value: 40 }, - { label: '염증', value: 30 }, - { label: '각질', value: 50 }, - { label: '수분', value: 25 }, - { label: '자극', value: 85 }, - ], + title: '🍀 건성 민감형', + description: '외부 자극에 약한 두피\n자극없이 순한 케어가 필요', + // avoidIngredients: ['에탄올', '합성향료', '멘톨'], + // recommendedIngredients: ['알로에베라', '히알루론산', '카모마일', '판테놀'], + // tips: [ + // '하루 1회 샴푸 유지', + // '수분 케어 제품 위주 사용', + // '두피 미스트 활용', + // ], + ratings: { oil: 1, sensitivity: 4, scaling: 1 }, }, - dry_scaling_type: { - title: '💧 건조 비듬형', - description: '정상 유분 + 고각질 + 비정상\n → 각질 + 비듬이 주 증상', - avoidIngredients: ['황산염', '에탄올', '멘톨'], - recommendedIngredients: ['징크피리치온', '알로에베라', '시어버터'], - tips: ['비듬 전용 샴푸 사용', '1일 1회 샴푸 유지', '드라이 시 찬바람'], - radarValues: [ - { label: '유분', value: 35 }, - { label: '염증', value: 25 }, - { label: '각질', value: 80 }, - { label: '수분', value: 35 }, - { label: '자극', value: 50 }, - ], + title: '❄️ 건성 비듬형', + description: '유분이 적고 각질이 두드러짐\n비듬과 간지러움 케어가 필요', + // avoidIngredients: ['실리콘'], + // recommendedIngredients: [ + // '히알루론산', + // '알로에베라', + // '살리실산', + // '레조르신', + // ], + // tips: ['비듬 전용 샴푸 사용', '1일 1회 샴푸 유지', '드라이 시 찬바람'], + ratings: { oil: 1, sensitivity: 1, scaling: 4 }, + }, + dry_clean_type: { + title: '🧡 건성 깔끔형', + description: '각질과 염증이 적지만 유분 부족\n수분이 많은 제품을 사용', + // avoidIngredients: ['실리콘', '무거운 오일 성분'], + // recommendedIngredients: [ + // '알로에베라', + // '히알루론산', + // '아미노산계 계면활성제', + // ], + // tips: [ + // '피지 조절에 포인트를 두어 마사지와 앰플 사용', + // '과한 드라이기 사용 자제', + // ], + ratings: { oil: 1, sensitivity: 1, scaling: 1 }, + }, + + dry_trouble_type: { + title: '🌸 건성 트러블형', + description: + '유분은 적고, 고각질의 민감한 두피\n진정과 비듬 케어가 필요합니다', + // avoidIngredients: ['실리콘', '향료', '무거운 오일 성분'], + // recommendedIngredients: ['알로에베라', '히알루론산'], + // tips: [ + // '뜨거운 물 X, 미지근한 물로 샴푸', + // '샴푸 후 두피 보습제 사용', + // '드라이 시 찬바람', + // ], + ratings: { oil: [1, 2], sensitivity: [3, 4], scaling: [3, 4] }, }, balanced_type: { - title: '🍀 밸런스형', - description: '수분, 유분, 자극도 안정적\n → 이상적인 두피 컨디션', - avoidIngredients: ['-'], - recommendedIngredients: ['-'], - tips: ['현재 루틴 유지', '과도한 관리 피하기', '스트레스 관리'], - radarValues: [ - { label: '유분', value: 50 }, - { label: '염증', value: 30 }, - { label: '각질', value: 30 }, - { label: '수분', value: 70 }, - { label: '자극', value: 30 }, - ], + title: '🗓️ 밸런스형', + description: '균형 잡혀 있는 두피 타입.\n 지금처럼 유지해주세요.', + // avoidIngredients: ['-'], + // recommendedIngredients: ['비오틴', '케라틴', '콜라겐', '아르간오일'], + // tips: ['현재 루틴 유지', '과도한 관리 피하기', '스트레스 관리'], + ratings: { oil: 2, sensitivity: 1, scaling: 1 }, }, } diff --git a/src/types/scalp-mbti.ts b/src/types/scalp-mbti.ts index 5be9803..7ee18ab 100644 --- a/src/types/scalp-mbti.ts +++ b/src/types/scalp-mbti.ts @@ -1,24 +1,25 @@ import type { MbtiCardKey } from '../data/mbtiCardData' export type ScalpMbtiType = - | 'oily_trouble_type' - | 'oily_sensitive_type' - | 'oily_scaling_type' - | 'clean_oily_type' - | 'dry_trouble_type' - | 'dry_sensitive_type' - | 'dry_scaling_type' - | 'balanced_type' + | 'oily_sensitive_type' //지성 민감형 + | 'oily_scaling_type' //지성 비듬형 + | 'oily_clean_type' // 지성 깔끔형 + | 'oily_trouble_type' // 지성 트러블형 + | 'dry_sensitive_type' //건성 민감형 + | 'dry_scaling_type' //건성 비듬형 + | 'dry_clean_type' // 건성 깔끔형 + | 'dry_trouble_type' // 건성 트러블형 + | 'balanced_type' // 밸런스형 export const LABEL_TO_TYPE: Record = { - '트러블 폭풍형': 'oily_trouble_type', '지성 민감형': 'oily_sensitive_type', '지성 비듬형': 'oily_scaling_type', - '깔끔 지성형': 'clean_oily_type', - '깐깐 지성형': 'clean_oily_type', - '건조 트러블형': 'dry_trouble_type', - '민감 건조형': 'dry_sensitive_type', - '건조 비듬형': 'dry_scaling_type', + '지성 깔끔형': 'oily_clean_type', + '지성 트러블형': 'oily_trouble_type', + '건성 민감형': 'dry_sensitive_type', + '건성 비듬형': 'dry_scaling_type', + '건성 깔끔형': 'dry_clean_type', + '건성 트러블형': 'dry_trouble_type', 밸런스형: 'balanced_type', } @@ -34,13 +35,14 @@ export function labelToType(label?: string | null): ScalpMbtiType | null { } export const TYPE_TO_CARDKEY: Record = { - oily_trouble_type: 'oily_trouble', oily_sensitive_type: 'oily_sensitive', - oily_scaling_type: 'oily_dandruff', // (= oily_scaling) - clean_oily_type: 'clean_oily', - dry_trouble_type: 'dry_trouble', + oily_scaling_type: 'oily_scaling', // (= oily_scaling) + oily_clean_type: 'oily_clean', + oily_trouble_type: 'oily_trouble', dry_sensitive_type: 'dry_sensitive', dry_scaling_type: 'dry_scaling', + dry_clean_type: 'dry_clean', + dry_trouble_type: 'dry_trouble', balanced_type: 'balanced', } diff --git a/src/types/scalp.ts b/src/types/scalp.ts index ef94614..7c0001a 100644 --- a/src/types/scalp.ts +++ b/src/types/scalp.ts @@ -1,9 +1,10 @@ export type ScalpMbtiType = - | 'oily_trouble_type' // 트러블 폭풍형 | 'oily_sensitive_type' // 지성 민감형 | 'oily_scaling_type' // 지성 비듬형 - | 'seborrheic_type' // 깐깐 지성형 - | 'dry_trouble_type' // 건조 트러블형 - | 'dry_sensitive_type' // 민감 건조형 - | 'dry_scaling_type' // 건조 비듬형 + | 'oily_clean_type' // 지성 깔끔형 + | 'oily_trouble_type' // 지성 트러블형 + | 'dry_sensitive_type' // 건성 민감형 + | 'dry_scaling_type' // 건성 비듬형 + | 'dry_clean_type' //건성 깔끔형 + | 'dry_trouble_type' // 건성 트러블형 | 'balanced_type' // 밸런스형