diff --git a/backend/src/mpt/entities/mpt.entity.ts b/backend/src/mpt/entities/mpt.entity.ts index ba5ee02..2e07be2 100644 --- a/backend/src/mpt/entities/mpt.entity.ts +++ b/backend/src/mpt/entities/mpt.entity.ts @@ -37,67 +37,208 @@ export class MptModel extends BaseModel { }) fkFarmNo: number; - @Column({ name: 'test_dt', type: 'date', nullable: true, comment: '검사일자' }) + @Column({ + name: 'test_dt', + type: 'date', + nullable: true, + comment: '검사일자', + }) testDt: Date; - @Column({ name: 'month_age', type: 'int', nullable: true, comment: '월령' }) + @Column({ + name: 'month_age', + type: 'int', + nullable: true, + comment: '월령', + }) monthAge: number; - @Column({ name: 'milk_yield', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '유량' }) + @Column({ + name: 'milk_yield', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '유량', + }) milkYield: number; - @Column({ name: 'parity', type: 'int', nullable: true, comment: '산차' }) + @Column({ + name: 'parity', + type: 'int', + nullable: true, + comment: '산차', + }) parity: number; - @Column({ name: 'glucose', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '혈당' }) + @Column({ + name: 'glucose', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '혈당', + }) glucose: number; - @Column({ name: 'cholesterol', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '콜레스테롤' }) + @Column({ + name: 'cholesterol', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '콜레스테롤', + }) cholesterol: number; - @Column({ name: 'nefa', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '유리지방산(NEFA)' }) + @Column({ + name: 'nefa', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '유리지방산(NEFA)', + }) nefa: number; - @Column({ name: 'bcs', type: 'decimal', precision: 5, scale: 2, nullable: true, comment: 'BCS' }) + @Column({ + name: 'bcs', + type: 'decimal', + precision: 5, + scale: 2, + nullable: true, + comment: 'BCS', + }) bcs: number; - @Column({ name: 'total_protein', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '총단백질' }) + @Column({ + name: 'total_protein', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '총단백질', + }) totalProtein: number; - @Column({ name: 'albumin', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '알부민' }) + @Column({ + name: 'albumin', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '알부민', + }) albumin: number; - @Column({ name: 'globulin', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '총글로불린' }) + @Column({ + name: 'globulin', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '총글로불린', + }) globulin: number; - @Column({ name: 'ag_ratio', type: 'decimal', precision: 5, scale: 2, nullable: true, comment: 'A/G 비율' }) + @Column({ + name: 'ag_ratio', + type: 'decimal', + precision: 5, + scale: 2, + nullable: true, + comment: 'A/G 비율', + }) agRatio: number; - @Column({ name: 'bun', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '요소태질소(BUN)' }) + @Column({ + name: 'bun', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '요소태질소(BUN)', + }) bun: number; - @Column({ name: 'ast', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: 'AST' }) + @Column({ + name: 'ast', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: 'AST', + }) ast: number; - @Column({ name: 'ggt', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: 'GGT' }) + @Column({ + name: 'ggt', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: 'GGT', + }) ggt: number; - @Column({ name: 'fatty_liver_idx', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '지방간지수' }) + @Column({ + name: 'fatty_liver_idx', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '지방간지수', + }) fattyLiverIdx: number; - @Column({ name: 'calcium', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '칼슘' }) + @Column({ + name: 'calcium', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '칼슘', + }) calcium: number; - @Column({ name: 'phosphorus', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '인' }) + @Column({ + name: 'phosphorus', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '인', + }) phosphorus: number; - @Column({ name: 'ca_p_ratio', type: 'decimal', precision: 5, scale: 2, nullable: true, comment: '칼슘/인 비율' }) + @Column({ + name: 'ca_p_ratio', + type: 'decimal', + precision: 5, + scale: 2, + nullable: true, + comment: '칼슘/인 비율', + }) caPRatio: number; - @Column({ name: 'magnesium', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '마그네슘' }) + @Column({ + name: 'magnesium', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '마그네슘', + }) magnesium: number; - @Column({ name: 'creatinine', type: 'decimal', precision: 10, scale: 2, nullable: true, comment: '크레아틴' }) + @Column({ + name: 'creatinine', + type: 'decimal', + precision: 10, + scale: 2, + nullable: true, + comment: '크레아틴', + }) creatinine: number; // Relations diff --git a/frontend/src/app/cow/[cowNo]/reproduction/_components/mpt-table.tsx b/frontend/src/app/cow/[cowNo]/reproduction/_components/mpt-table.tsx new file mode 100644 index 0000000..bcdbbbd --- /dev/null +++ b/frontend/src/app/cow/[cowNo]/reproduction/_components/mpt-table.tsx @@ -0,0 +1,497 @@ +'use client' + +import { useEffect, useState } from 'react' +import { Card, CardContent } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { mptApi, MptDto } from "@/lib/api" +import { Activity, CheckCircle2, XCircle } from 'lucide-react' +import { CowNumberDisplay } from "@/components/common/cow-number-display" +import { CowDetail } from "@/types/cow.types" +import { GenomeRequestDto } from "@/lib/api" +import { MPT_REFERENCE_RANGES } from "@/constants/mpt-reference" + +// 혈액화학검사 카테고리별 항목 +const MPT_CATEGORIES = [ + { name: '에너지 대사', items: ['glucose', 'cholesterol', 'nefa', 'bcs'], color: 'bg-muted/50' }, + { name: '단백질 대사', items: ['totalProtein', 'albumin', 'globulin', 'agRatio', 'bun'], color: 'bg-muted/50' }, + { name: '간기능', items: ['ast', 'ggt', 'fattyLiverIdx'], color: 'bg-muted/50' }, + { name: '미네랄', items: ['calcium', 'phosphorus', 'caPRatio', 'magnesium'], color: 'bg-muted/50' }, + { name: '기타', items: ['creatinine'], color: 'bg-muted/50' }, +] + +// 측정값 상태 판정 +function getMptValueStatus(key: string, value: number | null): 'normal' | 'warning' | 'danger' | 'unknown' { + if (value === null || value === undefined) return 'unknown' + const ref = MPT_REFERENCE_RANGES[key] + if (!ref || ref.lowerLimit === null || ref.upperLimit === null) return 'unknown' + if (value >= ref.lowerLimit && value <= ref.upperLimit) return 'normal' + const margin = (ref.upperLimit - ref.lowerLimit) * 0.1 + if (value >= ref.lowerLimit - margin && value <= ref.upperLimit + margin) return 'warning' + return 'danger' +} + +interface MptTableProps { + cowShortNo?: string + cowNo?: string + farmNo?: number + cow?: CowDetail | null + genomeRequest?: GenomeRequestDto | null +} + +export function MptTable({ cowShortNo, cowNo, farmNo, cow, genomeRequest }: MptTableProps) { + const [mptData, setMptData] = useState([]) + const [selectedMpt, setSelectedMpt] = useState(null) + const [loading, setLoading] = useState(false) + + useEffect(() => { + const fetchMptData = async () => { + if (!cowShortNo) return + + setLoading(true) + try { + const data = await mptApi.findByCowShortNo(cowShortNo) + setMptData(data) + if (data.length > 0) { + setSelectedMpt(data[0]) + } + } catch (error) { + console.error('MPT 데이터 로드 실패:', error) + } finally { + setLoading(false) + } + } + + fetchMptData() + }, [cowShortNo]) + + if (loading) { + return ( +
+
+
+

데이터를 불러오는 중...

+
+
+ ) + } + + return ( +
+ {/* 개체 정보 섹션 */} +

개체 정보

+ + + {/* 데스크탑: 가로 그리드 */} +
+
+
+ 개체번호 +
+
+ +
+
+
+
+ 생년월일 +
+
+ + {cow?.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR') : '-'} + +
+
+
+
+ 월령 +
+
+ + {cow?.cowBirthDt + ? `${Math.floor((new Date().getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` + : '-'} + +
+
+
+
+ 성별 +
+
+ + {cow?.cowSex === 'F' ? '암' : cow?.cowSex === 'M' ? '수' : '-'} + +
+
+
+ {/* 모바일: 좌우 배치 리스트 */} +
+
+ 개체번호 +
+ +
+
+
+ 생년월일 + + {cow?.cowBirthDt ? new Date(cow.cowBirthDt).toLocaleDateString('ko-KR') : '-'} + +
+
+ 월령 + + {cow?.cowBirthDt + ? `${Math.floor((new Date().getTime() - new Date(cow.cowBirthDt).getTime()) / (1000 * 60 * 60 * 24 * 30.44))}개월` + : '-'} + +
+
+ 성별 + + {cow?.cowSex === 'F' ? '암' : cow?.cowSex === 'M' ? '수' : '-'} + +
+
+
+
+ + {/* 혈통정보 섹션 */} +

혈통정보

+ + + {/* 데스크탑: 가로 그리드 */} +
+
+
+ 부 KPN번호 +
+
+ + {cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'} + + {(() => { + const chipSireName = genomeRequest?.chipSireName + if (chipSireName === '일치') { + return ( + + + 일치 + + ) + } else if (chipSireName && chipSireName !== '일치') { + return ( + + + 불일치 + + ) + } else { + return null + } + })()} +
+
+
+
+ 모 개체번호 +
+
+ {cow?.damCowId && cow.damCowId !== '0' ? ( + + ) : ( + - + )} + {(() => { + const chipDamName = genomeRequest?.chipDamName + if (chipDamName === '일치') { + return ( + + + 일치 + + ) + } else if (chipDamName === '불일치') { + return ( + + + 불일치 + + ) + } else if (chipDamName === '이력제부재') { + return ( + + + 이력제부재 + + ) + } else { + return null + } + })()} +
+
+
+ {/* 모바일: 좌우 배치 리스트 */} +
+
+ 부 KPN번호 +
+ + {cow?.sireKpn && cow.sireKpn !== '0' ? cow.sireKpn : '-'} + + {(() => { + const chipSireName = genomeRequest?.chipSireName + if (chipSireName === '일치') { + return ( + + + 일치 + + ) + } else if (chipSireName && chipSireName !== '일치') { + return ( + + + 불일치 + + ) + } else { + return null + } + })()} +
+
+
+ 모 개체번호 +
+ {cow?.damCowId && cow.damCowId !== '0' ? ( + + ) : ( + - + )} + {(() => { + const chipDamName = genomeRequest?.chipDamName + if (chipDamName === '일치') { + return ( + + + 일치 + + ) + } else if (chipDamName === '불일치') { + return ( + + + 불일치 + + ) + } else if (chipDamName === '이력제부재') { + return ( + + + 이력제부재 + + ) + } else { + return null + } + })()} +
+
+
+
+
+ + {/* 검사 정보 */} + {selectedMpt && ( + <> +

검사 정보

+ + +
+
+
+ 검사일자 +
+
+ + {selectedMpt.testDt ? new Date(selectedMpt.testDt).toLocaleDateString('ko-KR') : '-'} + +
+
+
+
+ 월령 +
+
+ + {selectedMpt.monthAge ? `${selectedMpt.monthAge}개월` : '-'} + +
+
+
+
+ 산차 +
+
+ + {selectedMpt.parity ? `${selectedMpt.parity}산차` : '-'} + +
+
+
+
+ 유량 +
+
+ + {selectedMpt.milkYield ? `${selectedMpt.milkYield}kg` : '-'} + +
+
+
+ {/* 모바일 */} +
+
+ 검사일자 + + {selectedMpt.testDt ? new Date(selectedMpt.testDt).toLocaleDateString('ko-KR') : '-'} + +
+
+ 월령 + + {selectedMpt.monthAge ? `${selectedMpt.monthAge}개월` : '-'} + +
+
+ 산차 + + {selectedMpt.parity ? `${selectedMpt.parity}산차` : '-'} + +
+
+ 유량 + + {selectedMpt.milkYield ? `${selectedMpt.milkYield}kg` : '-'} + +
+
+
+
+ + )} + + {/* 혈액화학검사 결과 테이블 */} +

혈액화학검사 결과

+ + +
+ + + + + + + + + + + + + + {MPT_CATEGORIES.map((category) => ( + category.items.map((itemKey, itemIdx) => { + const ref = MPT_REFERENCE_RANGES[itemKey] + const value = selectedMpt ? (selectedMpt[itemKey as keyof MptDto] as number | null) : null + const status = getMptValueStatus(itemKey, value) + + return ( + + {itemIdx === 0 && ( + + )} + + + + + + + + ) + }) + ))} + +
카테고리검사항목측정값하한값상한값단위상태
+ {category.name} + {ref?.name || itemKey} + + {value !== null && value !== undefined ? value.toFixed(2) : '-'} + + {ref?.lowerLimit ?? '-'}{ref?.upperLimit ?? '-'}{ref?.unit || '-'} + {value !== null && value !== undefined ? ( + + {status === 'normal' ? '정상' : + status === 'warning' ? '주의' : + status === 'danger' ? '이상' : '-'} + + ) : ( + - + )} +
+
+
+
+ + {/* 검사 이력 (여러 검사 결과가 있을 경우) */} + {mptData.length > 1 && ( + <> +

검사 이력

+ + +
+ {mptData.map((mpt, idx) => ( + + ))} +
+
+
+ + )} + + {/* 데이터 없음 안내 */} + {/* {!selectedMpt && ( + + + +

혈액화학검사 데이터 없음

+

+ 이 개체는 아직 혈액화학검사(MPT) 결과가 등록되지 않았습니다. +

+
+
+ )} */} +
+ ) +} diff --git a/frontend/src/app/mpt/page.tsx b/frontend/src/app/mpt/page.tsx new file mode 100644 index 0000000..35ed915 --- /dev/null +++ b/frontend/src/app/mpt/page.tsx @@ -0,0 +1,392 @@ +'use client' + +import { useSearchParams, useRouter } from "next/navigation" +import { AppSidebar } from "@/components/layout/app-sidebar" +import { SiteHeader } from "@/components/layout/site-header" +import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { useToast } from "@/hooks/use-toast" +import { mptApi, MptDto, cowApi } from "@/lib/api" +import { CowDetail } from "@/types/cow.types" +import { + ArrowLeft, + Activity, + Search, +} from 'lucide-react' +import { useEffect, useState } from 'react' +import { CowNumberDisplay } from "@/components/common/cow-number-display" +import { AuthGuard } from "@/components/auth/auth-guard" + +// 혈액화학검사 항목별 참조값 (정상 범위) +const MPT_REFERENCE_VALUES: Record = { + // 에너지 대사 + glucose: { min: 45, max: 75, unit: 'mg/dL', name: '혈당' }, + cholesterol: { min: 80, max: 120, unit: 'mg/dL', name: '콜레스테롤' }, + nefa: { min: 0, max: 0.4, unit: 'mEq/L', name: 'NEFA(유리지방산)' }, + bcs: { min: 2.5, max: 3.5, unit: '점', name: 'BCS' }, + // 단백질 대사 + totalProtein: { min: 6.5, max: 8.5, unit: 'g/dL', name: '총단백질' }, + albumin: { min: 3.0, max: 3.6, unit: 'g/dL', name: '알부민' }, + globulin: { min: 3.0, max: 5.0, unit: 'g/dL', name: '글로불린' }, + agRatio: { min: 0.6, max: 1.2, unit: '', name: 'A/G 비율' }, + bun: { min: 8, max: 25, unit: 'mg/dL', name: 'BUN(요소태질소)' }, + // 간기능 + ast: { min: 45, max: 110, unit: 'U/L', name: 'AST' }, + ggt: { min: 10, max: 36, unit: 'U/L', name: 'GGT' }, + fattyLiverIdx: { min: 0, max: 30, unit: '', name: '지방간지수' }, + // 미네랄 + calcium: { min: 8.5, max: 11.5, unit: 'mg/dL', name: '칼슘' }, + phosphorus: { min: 4.0, max: 7.5, unit: 'mg/dL', name: '인' }, + caPRatio: { min: 1.0, max: 2.0, unit: '', name: 'Ca/P 비율' }, + magnesium: { min: 1.8, max: 2.5, unit: 'mg/dL', name: '마그네슘' }, + creatinine: { min: 1.0, max: 2.0, unit: 'mg/dL', name: '크레아틴' }, +} + +// 카테고리별 항목 그룹핑 +const MPT_CATEGORIES = [ + { + name: '에너지 대사', + items: ['glucose', 'cholesterol', 'nefa', 'bcs'], + color: 'bg-orange-500', + }, + { + name: '단백질 대사', + items: ['totalProtein', 'albumin', 'globulin', 'agRatio', 'bun'], + color: 'bg-blue-500', + }, + { + name: '간기능', + items: ['ast', 'ggt', 'fattyLiverIdx'], + color: 'bg-green-500', + }, + { + name: '미네랄', + items: ['calcium', 'phosphorus', 'caPRatio', 'magnesium', 'creatinine'], + color: 'bg-purple-500', + }, +] + +// 측정값 상태 판정 (정상/주의/위험) +function getValueStatus(key: string, value: number | null): 'normal' | 'warning' | 'danger' | 'unknown' { + if (value === null || value === undefined) return 'unknown' + const ref = MPT_REFERENCE_VALUES[key] + if (!ref) return 'unknown' + + if (value >= ref.min && value <= ref.max) return 'normal' + + // 10% 이내 범위 이탈은 주의 + const margin = (ref.max - ref.min) * 0.1 + if (value >= ref.min - margin && value <= ref.max + margin) return 'warning' + + return 'danger' +} + +export default function MptPage() { + const searchParams = useSearchParams() + const router = useRouter() + const cowShortNo = searchParams.get('cowShortNo') + const farmNo = searchParams.get('farmNo') + const { toast } = useToast() + + const [searchInput, setSearchInput] = useState(cowShortNo || '') + const [mptData, setMptData] = useState([]) + const [selectedMpt, setSelectedMpt] = useState(null) + const [cow, setCow] = useState(null) + const [loading, setLoading] = useState(false) + + // 검색 실행 + const handleSearch = async () => { + if (!searchInput.trim()) { + toast({ + title: '검색어를 입력해주세요', + variant: 'destructive', + }) + return + } + + setLoading(true) + try { + const data = await mptApi.findByCowShortNo(searchInput.trim()) + setMptData(data) + if (data.length > 0) { + setSelectedMpt(data[0]) // 가장 최근 검사 결과 선택 + } else { + setSelectedMpt(null) + toast({ + title: '검사 결과가 없습니다', + description: `개체번호 ${searchInput}의 혈액화학검사 결과를 찾을 수 없습니다.`, + }) + } + } catch (error) { + console.error('MPT 데이터 로드 실패:', error) + toast({ + title: '데이터 로드 실패', + variant: 'destructive', + }) + } finally { + setLoading(false) + } + } + + // 초기 로드 + useEffect(() => { + if (cowShortNo) { + handleSearch() + } + }, []) + + const handleBack = () => { + router.back() + } + + return ( + + + + + +
+
+ {/* 헤더 */} +
+
+ +
+ +
+
+

혈액화학검사

+

Metabolic Profile Test

+
+
+
+ + {/* 검색 영역 */} + + +
+ setSearchInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="flex-1" + /> + +
+
+
+ + {loading && ( +
+
+
+

데이터를 불러오는 중...

+
+
+ )} + + {!loading && selectedMpt && ( + <> + {/* 개체 정보 */} +

개체 정보

+ + +
+
+
+ 개체번호 +
+
+ {selectedMpt.cowShortNo || '-'} +
+
+
+
+ 검사일자 +
+
+ + {selectedMpt.testDt ? new Date(selectedMpt.testDt).toLocaleDateString('ko-KR') : '-'} + +
+
+
+
+ 월령 +
+
+ + {selectedMpt.monthAge ? `${selectedMpt.monthAge}개월` : '-'} + +
+
+
+
+ 산차 +
+
+ + {selectedMpt.parity ? `${selectedMpt.parity}산차` : '-'} + +
+
+
+ {/* 모바일 */} +
+
+ 개체번호 + {selectedMpt.cowShortNo || '-'} +
+
+ 검사일자 + + {selectedMpt.testDt ? new Date(selectedMpt.testDt).toLocaleDateString('ko-KR') : '-'} + +
+
+ 월령 + + {selectedMpt.monthAge ? `${selectedMpt.monthAge}개월` : '-'} + +
+
+ 산차 + + {selectedMpt.parity ? `${selectedMpt.parity}산차` : '-'} + +
+
+
+
+ + {/* 혈액화학검사 결과 테이블 */} +

혈액화학검사 결과

+ + +
+ + + + + + + + + + + + + + {MPT_CATEGORIES.map((category, catIdx) => ( + category.items.map((itemKey, itemIdx) => { + const ref = MPT_REFERENCE_VALUES[itemKey] + const value = selectedMpt[itemKey as keyof MptDto] as number | null + const status = getValueStatus(itemKey, value) + + return ( + + {itemIdx === 0 && ( + + )} + + + + + + + + ) + }) + ))} + +
카테고리검사항목측정값하한값상한값단위상태
+ {category.name} + {ref?.name || itemKey} + + {value !== null && value !== undefined ? value.toFixed(2) : '-'} + + {ref?.min ?? '-'}{ref?.max ?? '-'}{ref?.unit || '-'} + + {status === 'normal' ? '정상' : + status === 'warning' ? '주의' : + status === 'danger' ? '이상' : '-'} + +
+
+
+
+ + {/* 검사 이력 (여러 검사 결과가 있을 경우) */} + {mptData.length > 1 && ( + <> +

검사 이력

+ + +
+ {mptData.map((mpt, idx) => ( + + ))} +
+
+
+ + )} + + )} + + {!loading && !selectedMpt && cowShortNo && ( +
+ +

검사 결과가 없습니다

+

해당 개체의 혈액화학검사 결과를 찾을 수 없습니다.

+
+ )} + + {!loading && !selectedMpt && !cowShortNo && ( +
+ +

개체번호를 검색해주세요

+

개체 요약번호를 입력하여 혈액화학검사 결과를 조회합니다.

+
+ )} +
+
+
+
+
+ ) +} diff --git a/frontend/src/constants/mpt-reference.ts b/frontend/src/constants/mpt-reference.ts index 93dfdcb..6537f40 100644 --- a/frontend/src/constants/mpt-reference.ts +++ b/frontend/src/constants/mpt-reference.ts @@ -1,142 +1,178 @@ /** * MPT (혈액대사판정시험) 항목별 권장치 참고 범위 - * + * MPT 참조값의 중앙 관리 파일 */ export interface MptReferenceRange { + name: string; // 한글 표시명 upperLimit: number | null; lowerLimit: number | null; unit: string; - category: '에너지' | '단백질' | '간기능' | '미네랄' | '별도'; + category: '에너지' | '단백질' | '간기능' | '미네랄' | '기타'; + description?: string; // 항목 설명 (선택) } export const MPT_REFERENCE_RANGES: Record = { // 에너지 카테고리 - '글루코스': { + glucose: { + name: '혈당', upperLimit: 72, lowerLimit: 46.9, unit: 'mg/dL', category: '에너지', + description: '에너지 대사 상태 지표', }, - 'NEFA': { - upperLimit: 382, - lowerLimit: 118, - unit: 'uEq/L', - category: '에너지', - }, - 'BHBA': { - upperLimit: 7.9, - lowerLimit: 4.3, - unit: 'mg/dL', - category: '에너지', - }, - '콜레스테롤': { + cholesterol: { + name: '콜레스테롤', upperLimit: 169, lowerLimit: 117, unit: 'mg/dL', category: '에너지', + description: '혈액 내 콜레스테롤 수치', + }, + nefa: { + name: '유리지방산(NEFA)', + upperLimit: 382, + lowerLimit: 118, + unit: 'uEq/L', + category: '에너지', + description: '혈액 내 유리지방산 수치', + }, + bcs: { + name: 'BCS', + upperLimit: 3.5, + lowerLimit: 2.5, + unit: '-', + category: '에너지', + description: '혈액 내 BCS 수치', }, // 단백질 카테고리 - '알부민': { + totalProtein: { + name: '총단백질', + upperLimit: 8.5, + lowerLimit: 6.5, + unit: 'g/dL', + category: '단백질', + description: '혈액 내 총단백질 수치', + }, + albumin: { + name: '알부민', upperLimit: 4.3, lowerLimit: 3.3, unit: 'g/dL', category: '단백질', + description: '혈액 내 알부민 수치', }, - '총글로불린': { + globulin: { + name: '총글로불린', upperLimit: 36.1, lowerLimit: 9.1, unit: 'g/L', category: '단백질', + description: '혈액 내 총글로불린 수치', }, - 'A/G': { + agRatio: { + name: 'A/G ', upperLimit: 0.4, lowerLimit: 0.1, unit: '-', category: '단백질', + description: '혈액 내 A/G 수치', }, - '요소태질소(BUN)': { + bun: { + name: '요소태질소(BUN)', upperLimit: 18.9, lowerLimit: 11.7, unit: 'mg/dL', category: '단백질', + description: '혈액 내 요소태질소 수치', }, // 간기능 카테고리 - 'AST': { + ast: { + name: 'AST', upperLimit: 92, lowerLimit: 47, unit: 'U/L', category: '간기능', + description: '혈액 내 AST 수치', }, - 'GGT': { + ggt: { + name: 'GGT', upperLimit: 32, lowerLimit: 11, unit: 'U/L', category: '간기능', + description: '혈액 내 GGT 수치', }, - '지방간 지수': { + fattyLiverIdx: { + name: '지방간 지수', upperLimit: 9.9, lowerLimit: -1.2, unit: '-', category: '간기능', + description: '혈액 내 지방간 지수 수치', }, // 미네랄 카테고리 - '칼슘': { + calcium: { + name: '칼슘', upperLimit: 10.6, lowerLimit: 8.1, unit: 'mg/dL', category: '미네랄', + description: '혈액 내 칼슘 수치', }, - '인': { + phosphorus: { + name: '인', upperLimit: 8.9, lowerLimit: 6.2, unit: 'mg/dL', category: '미네랄', + description: '혈액 내 인 수치', }, - '칼슘/인': { + caPRatio: { + name: '칼슘/인 비율', upperLimit: 1.3, lowerLimit: 1.2, unit: '-', category: '미네랄', + description: '혈액 내 칼슘/인 비율 수치', }, - '마그네슘': { + magnesium: { + name: '마그네슘', upperLimit: 3.3, lowerLimit: 1.6, unit: 'mg/dL', category: '미네랄', + description: '혈액 내 마그네슘 수치', }, - // 별도 카테고리 - '총빌리루빈': { - upperLimit: null, - lowerLimit: null, - unit: 'mg/dL', - category: '별도', - }, - '크레아틴': { + // 기타 카테고리 + creatinine: { + name: '크레아티닌', upperLimit: 2.0, lowerLimit: 1.0, unit: 'mg/dL', - category: '별도', + category: '기타', + description: '혈액 내 크레아티닌 수치', }, }; /** * MPT 카테고리 목록 (표시 순서) */ -export const MPT_CATEGORIES = ['에너지', '단백질', '간기능', '미네랄', '별도'] as const; +export const MPT_CATEGORIES = ['에너지', '단백질', '간기능', '미네랄', '기타'] as const; /** * 측정값이 정상 범위 내에 있는지 확인 */ export function isWithinRange( value: number, - itemName: string + itemKey: string ): 'normal' | 'high' | 'low' | 'unknown' { - const reference = MPT_REFERENCE_RANGES[itemName]; + const reference = MPT_REFERENCE_RANGES[itemKey]; if (!reference || reference.upperLimit === null || reference.lowerLimit === null) { return 'unknown'; @@ -157,8 +193,8 @@ export function getMptItemsByCategory() { grouped[category] = []; }); - Object.entries(MPT_REFERENCE_RANGES).forEach(([itemName, reference]) => { - grouped[reference.category].push(itemName); + Object.entries(MPT_REFERENCE_RANGES).forEach(([itemKey, reference]) => { + grouped[reference.category].push(itemKey); }); return grouped; diff --git a/frontend/src/lib/api/index.ts b/frontend/src/lib/api/index.ts index 39fbe12..64b37c7 100644 --- a/frontend/src/lib/api/index.ts +++ b/frontend/src/lib/api/index.ts @@ -20,3 +20,4 @@ export { breedApi } from './breed.api'; // API 클라이언트도 export (필요 시 직접 사용 가능) export { default as apiClient } from '../api-client'; +export { mptApi, type MptDto } from './mpt.api'; diff --git a/frontend/src/lib/api/mpt.api.ts b/frontend/src/lib/api/mpt.api.ts new file mode 100644 index 0000000..d5c7736 --- /dev/null +++ b/frontend/src/lib/api/mpt.api.ts @@ -0,0 +1,105 @@ +import apiClient from "../api-client"; + +/** + * MPT(혈액화학검사) 결과 DTO + */ +export interface MptDto { + pkMptNo: number; + cowShortNo: string; + fkFarmNo: number; + testDt: string; + monthAge: number; + milkYield: number; + parity: number; + // 에너지 대사 + glucose: number; + cholesterol: number; + nefa: number; + bcs: number; + // 단백질 대사 + totalProtein: number; + albumin: number; + globulin: number; + agRatio: number; + bun: number; + // 간기능 + ast: number; + ggt: number; + fattyLiverIdx: number; + // 미네랄 + calcium: number; + phosphorus: number; + caPRatio: number; + magnesium: number; + creatinine: number; + // Relations + farm?: { + pkFarmNo: number; + farmerName: string; + }; +} + +/** + * MPT(혈액화학검사) 관련 API + */ +export const mptApi = { + /** + * GET /mpt - 전체 검사 결과 목록 + */ + findAll: async (): Promise => { + return await apiClient.get("/mpt"); + }, + + /** + * GET /mpt?farmId=:farmId - 특정 농장의 검사 결과 + */ + findByFarmId: async (farmId: number): Promise => { + return await apiClient.get("/mpt", { + params: { farmId }, + }); + }, + + /** + * GET /mpt?cowShortNo=:cowShortNo - 특정 개체의 검사 결과 + */ + findByCowShortNo: async (cowShortNo: string): Promise => { + return await apiClient.get("/mpt", { + params: { cowShortNo }, + }); + }, + + /** + * GET /mpt/:id - 검사 결과 상세 조회 + */ + findOne: async (id: number): Promise => { + return await apiClient.get(`/mpt/${id}`); + }, + + /** + * POST /mpt - 검사 결과 생성 + */ + create: async (data: Partial): Promise => { + return await apiClient.post("/mpt", data); + }, + + /** + * POST /mpt/bulk - 검사 결과 일괄 생성 + */ + bulkCreate: async (data: Partial[]): Promise => { + return await apiClient.post("/mpt/bulk", data); + }, + + /** + * PUT /mpt/:id - 검사 결과 수정 + */ + update: async (id: number, data: Partial): Promise => { + return await apiClient.put(`/mpt/${id}`, data); + }, + + /** + * DELETE /mpt/:id - 검사 결과 삭제 + */ + remove: async (id: number): Promise => { + return await apiClient.delete(`/mpt/${id}`); + }, +};