diff --git a/frontend/src/app/components/HealthReport.tsx b/frontend/src/app/components/HealthReport.tsx index fc32eb0..a13d131 100644 --- a/frontend/src/app/components/HealthReport.tsx +++ b/frontend/src/app/components/HealthReport.tsx @@ -136,10 +136,9 @@ export default function HealthReport() {
{[ { label: '산책', value: '하루 평균 25분', compare: '권장 대비 83%', compareColor: '#1B4B8C', compareBg: '#E8F0FA', emoji: '🚶' }, - { label: '음수량', value: '하루 평균 180ml', compare: '권장 대비 72%', compareColor: '#F57C00', compareBg: '#FFF8E1', emoji: '💧' }, { label: '체중', value: '4.2 kg', compare: '지난달 대비 +0.1kg', compareColor: '#FFA726', compareBg: '#FFF8E1', emoji: '⚖️' }, ].map((m, i) => ( -
+
{m.emoji}

{m.label}

diff --git a/frontend/src/app/components/PetProfileScreen.tsx b/frontend/src/app/components/PetProfileScreen.tsx index 2ef7573..748e4ca 100644 --- a/frontend/src/app/components/PetProfileScreen.tsx +++ b/frontend/src/app/components/PetProfileScreen.tsx @@ -1,42 +1,146 @@ +import { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router'; import MobileFrame from './MobileFrame'; import NavHeader from './NavHeader'; import BottomNav from './BottomNav'; -import { Edit2, RefreshCw, Heart, Scale, Syringe, Calendar, ChevronRight, Shield } from 'lucide-react'; +import { Edit2, RefreshCw, Heart, Scale, Calendar, ChevronRight, ChevronDown, Check, X, Camera } from 'lucide-react'; -const petStats = [ - { icon: '🏃', label: '이번 주 산책', value: '3회', sub: '총 85분', color: '#E8F0FA', iconBg: '#C5D8EE' }, - { icon: '🍖', label: '오늘 식사', value: '2회', sub: '권장량 100%', color: '#E8F5E9', iconBg: '#C8E6C9' }, - { icon: '💧', label: '오늘 음수량', value: '420ml', sub: '목표 대비 84%', color: '#E3F2FD', iconBg: '#BBDEFB' }, - { icon: '🏥', label: '최근 진료', value: '2주 전', sub: '피부과 진료', color: '#FFF8E1', iconBg: '#FFE082' }, -]; +type PetType = 'DOG' | 'CAT'; +type PetGender = 'MALE' | 'FEMALE'; +type VaccineCode = 'DHPPL' | 'RABIES' | 'KENNEL_COUGH' | 'CORONA_ENTERITIS' | 'HEARTWORM' | 'PARASITE'; -const vaccineRecords = [ - { name: '광견병', date: '2024.10.15', dNext: '2025.10.15', status: '정상', color: '#2E7D32', bg: '#E8F5E9' }, - { name: '종합백신', date: '2024.10.15', dNext: '2025.10.15', status: '정상', color: '#2E7D32', bg: '#E8F5E9' }, - { name: '심장사상충', date: '2025.03.01', dNext: '2025.04.01', status: '투약 필요', color: '#E65100', bg: '#FFF3E0' }, +const VACCINE_LIST: { code: VaccineCode; label: string }[] = [ + { code: 'DHPPL', label: '종합백신 (DHPPL)' }, + { code: 'RABIES', label: '광견병' }, + { code: 'KENNEL_COUGH', label: '켄넬코프 (기관지염)' }, + { code: 'CORONA_ENTERITIS', label: '코로나 장염' }, + { code: 'HEARTWORM', label: '심장사상충 예방' }, + { code: 'PARASITE', label: '외부기생충 구제' }, ]; +const DOG_BREEDS = ['골든 리트리버', '래브라도 리트리버', '말티즈', '포메라니안', '시츄', '비숑 프리제', '푸들', '진돗개', '프렌치 불독', '웰시 코기', '비글', '사모예드', '기타']; +const CAT_BREEDS = ['아메리칸 숏헤어', '스코티시 폴드', '페르시안', '러시안 블루', '메인쿤', '브리티시 숏헤어', '벵갈', '샴', '코리안 숏헤어', '노르웨이 숲속 고양이', '기타']; + +interface PetForm { + petType: PetType; + name: string; + breed: string; + birthdate: string; + weight: string; + gender: PetGender; + isNeutered: boolean; + vaccines: Record; + lastCheckup: string; + diseases: string; +} + +const INITIAL_FORM: PetForm = { + petType: 'DOG', + name: '코코', + breed: '골든 리트리버', + birthdate: '2021-03-12', + weight: '28.5', + gender: 'MALE', + isNeutered: true, + vaccines: { + DHPPL: true, + RABIES: true, + KENNEL_COUGH: false, + CORONA_ENTERITIS: false, + HEARTWORM: true, + PARASITE: true, + }, + lastCheckup: '2025-02-15', + diseases: '없음', +}; + +function formatAge(birthdate: string): string { + if (!birthdate) return '미입력'; + const birth = new Date(birthdate); + if (Number.isNaN(birth.getTime())) return birthdate; + const today = new Date(); + let age = today.getFullYear() - birth.getFullYear(); + const passed = + today.getMonth() > birth.getMonth() || + (today.getMonth() === birth.getMonth() && today.getDate() >= birth.getDate()); + if (!passed) age -= 1; + return `만 ${Math.max(age, 0)}세 (${birth.getFullYear()}년생)`; +} + export default function PetProfileScreen() { const navigate = useNavigate(); + const photoInputRef = useRef(null); + + const [isEditing, setIsEditing] = useState(false); + const [form, setForm] = useState(INITIAL_FORM); + const [draft, setDraft] = useState(INITIAL_FORM); + const [isCustomBreed, setIsCustomBreed] = useState(false); + const [showBreedPicker, setShowBreedPicker] = useState(false); + + const [photoFile, setPhotoFile] = useState(null); + const [photoPreviewUrl, setPhotoPreviewUrl] = useState(null); + + useEffect(() => { + return () => { if (photoPreviewUrl) URL.revokeObjectURL(photoPreviewUrl); }; + }, [photoPreviewUrl]); + + const handlePhotoChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + setPhotoPreviewUrl(curr => { if (curr) URL.revokeObjectURL(curr); return URL.createObjectURL(file); }); + setPhotoFile(file); + }; + + const startEdit = () => { + setDraft(form); + setIsCustomBreed(false); + setIsEditing(true); + }; + + const cancelEdit = () => { + setDraft(form); + setIsCustomBreed(false); + setIsEditing(false); + }; + + const saveEdit = () => { + setForm(draft); + setIsCustomBreed(false); + setIsEditing(false); + }; + + const breedList = draft.petType === 'CAT' ? CAT_BREEDS : DOG_BREEDS; + + const handleBreedSelect = (breed: string) => { + if (breed === '기타') { + setDraft(f => ({ ...f, breed: '' })); + setIsCustomBreed(true); + setShowBreedPicker(false); + return; + } + setDraft(f => ({ ...f, breed })); + setIsCustomBreed(false); + setShowBreedPicker(false); + }; + + const petEmoji = form.petType === 'CAT' ? '🐱' : '🐶'; return ( -
- navigate('/profile/switch')} - className="flex items-center gap-1 px-3 py-1.5 rounded-full transition-all active:scale-95" - style={{ backgroundColor: '#E8F0FA', border: '1px solid #C5D8EE' }} - > - - 전환 - - } +
+ + +
{/* Hero Card */}
@@ -47,46 +151,51 @@ export default function PetProfileScreen() { boxShadow: '0 6px 24px rgba(13,43,94,0.35)', }} > - {/* Avatar + Basic Info */}
- 🐶 + {photoPreviewUrl ? ( + 반려동물 사진 + ) : ( + {petEmoji} + )}
- {/* Edit photo overlay */}
-

코코

+

+ {form.name || '반려동물'} +

- 대형견 + {form.petType === 'CAT' ? '고양이' : '대형견'}
-

골든 리트리버

+

+ {form.breed || '품종 미입력'} +

등록번호 410191-000XXXX

- {/* Info Tags Row */}
{[ - { icon: , text: '만 4세 (2021.03.12)' }, - { icon: , text: '28.5 kg' }, - { icon: , text: '수컷 · 중성화 완료' }, + { icon: , text: formatAge(form.birthdate) }, + { icon: , text: form.weight ? `${form.weight} kg` : '체중 미입력' }, + { icon: , text: `${form.gender === 'FEMALE' ? '암컷' : '수컷'} · 중성화 ${form.isNeutered ? '완료' : '미완료'}` }, { icon: null, text: 'HYFIVE 등록' }, ].map((tag, i) => (
- {/* Quick Stats */} -
-

오늘의 건강 현황

-
- {petStats.map((stat, i) => ( -
-
- {stat.icon} + {isEditing ? ( + <> + {/* Basic Info Edit Card */} +
+
+ 🐾 +

기본 정보

+
+
+ {/* Pet Type (read-only) */} +
+ +
+ {([{ value: 'DOG' as PetType, label: '강아지', emoji: '🐶' }, { value: 'CAT' as PetType, label: '고양이', emoji: '🐱' }]).map(item => ( +
+ {item.emoji} + {item.label} +
+ ))} +
+ + {/* Name (read-only) */}
-

{stat.label}

-

{stat.value}

-

{stat.sub}

+ +
+ {draft.name} +
-
- ))} -
-
- {/* Vaccine / Prevention */} -
-
-

예방접종 · 투약 현황

- -
-
- {vaccineRecords.map((v, i) => ( -
-
- + {/* Breed */} +
+ +
+ + +
+ {isCustomBreed && ( + setDraft(f => ({ ...f, breed: e.target.value }))} + placeholder="품종을 직접 입력해주세요" + className="w-full rounded-xl px-4 mt-2" + style={{ height: '44px', fontSize: '13px', border: '1.5px solid #C5D8EE', backgroundColor: 'white', color: '#1C1C1C', outline: 'none' }} + /> + )}
-
-
-

{v.name}

- + + setDraft(f => ({ ...f, birthdate: e.target.value }))} + className="w-full rounded-xl px-4" + style={{ height: '44px', fontSize: '13px', border: '1.5px solid #E0E0E0', backgroundColor: 'white', color: '#1C1C1C', outline: 'none' }} + /> +
+ + {/* Weight */} +
+ +
+ setDraft(f => ({ ...f, weight: e.target.value }))} + className="w-full rounded-xl px-4 pr-12" + style={{ height: '44px', fontSize: '15px', fontWeight: 600, border: draft.weight ? '2px solid #1B4B8C' : '1.5px solid #E0E0E0', backgroundColor: draft.weight ? '#E8F0FA' : 'white', color: '#0D2B5E', outline: 'none', textAlign: 'center' }} + step="0.1" min="0.1" max="200" + /> + kg +
+
+ + {/* Gender */} +
+ +
+ {([{ value: 'MALE' as PetGender, label: '수컷', icon: '♂' }, { value: 'FEMALE' as PetGender, label: '암컷', icon: '♀' }]).map(g => ( + + ))} +
+
+ + {/* Neutered */} +
+ +
+ {([{ value: true, label: '완료' }, { value: false, label: '미완료' }]).map(n => ( + + ))} +
+
+
+
+ + {/* Vaccine Edit Card */} +
+
+ 💉 +

예방접종 현황

+
+
+ {VACCINE_LIST.map(v => { + const isOn = draft.vaccines[v.code]; + return ( +
- {v.status} + {v.label} + +
+ ); + })} +
+
+ + {/* Health Info Edit Card */} +
+
+ 🏥 +

추가 건강 정보

+
+
+
+ + setDraft(f => ({ ...f, lastCheckup: e.target.value }))} + className="w-full rounded-xl px-4" + style={{ height: '44px', fontSize: '13px', border: draft.lastCheckup ? '2px solid #1B4B8C' : '1.5px solid #E0E0E0', backgroundColor: draft.lastCheckup ? '#E8F0FA' : 'white', color: '#1C1C1C', outline: 'none' }} + /> +
+
+ +