Skip to content

Commit

Permalink
Merge pull request #125 from alkong-dalkong/feature/#84_heath_page
Browse files Browse the repository at this point in the history
feat: 건강 페이지
  • Loading branch information
G0MTENG authored Oct 30, 2024
2 parents 399f62c + b318817 commit 6877076
Show file tree
Hide file tree
Showing 54 changed files with 1,269 additions and 15 deletions.
383 changes: 383 additions & 0 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.52.2",
"recharts": "2.13.0-alpha.5",
"swiper": "^11.1.10",
"tailwind-scrollbar-hide": "^1.1.7",
"zod": "^3.23.8",
Expand Down
15 changes: 3 additions & 12 deletions src/app/health/[userId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import { BottomNav } from '@/components'
import { HealthClientPage } from '@/features'

export type HealthRouteParams = {
params: { userId: string }
}

const Health = ({ params: { userId } }: HealthRouteParams) => {
return (
<div>
<h2>{userId}</h2>
<BottomNav />
</div>
)
const Health = () => {
return <HealthClientPage />
}

export default Health
1 change: 0 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './clinic'
export * from './medicine'
export * from './slide'
export * from './tos'
export * from './zIndex'
32 changes: 32 additions & 0 deletions src/features/health/hooks/useGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client'

import { useMemo, useState } from 'react'

import { useWeightInfo } from '@/features'

export const useGraph = () => {
const info = useWeightInfo()
const [index, setIndex] = useState<number>(0)

const quadGraphData = useMemo(() => {
const groups = []
for (let i = 0; i < info.length; i += 4) {
groups.push(info.slice(i, i + 4))
}
return groups
}, [info])

const increase = () => {
if (index < quadGraphData.length - 1) {
setIndex((prev) => prev + 1)
}
}

const decrease = () => {
if (index > 0) {
setIndex((prev) => prev - 1)
}
}

return { data: quadGraphData[index]?.toReversed(), increase, decrease }
}
25 changes: 25 additions & 0 deletions src/features/health/query/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { api } from '@/apis'
import type {
GetPhysicalRequest,
GetPhysicalResponse,
PostPhysicalRequest,
PostPhysicalResponse,
PutPhysicalRequest,
PutPhysicalResponse,
} from '@/types'

export const getHealth = async ({ userId, period }: GetPhysicalRequest) => {
return await api.get<GetPhysicalResponse>(`/physical/${userId}`, {
params: {
period,
},
})
}

export const postHealth = async (request: PostPhysicalRequest) => {
return await api.post<PostPhysicalResponse>('/physical', request)
}

export const putHealth = async (weightId: number, request: PutPhysicalRequest) => {
return await api.put<PutPhysicalResponse>(`/physical/${weightId}`, request)
}
4 changes: 4 additions & 0 deletions src/features/health/query/queryKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const queryKeys = {
all: ['health'] as const,
page: (userId: string, period: string) => [...queryKeys.all, userId, period],
}
39 changes: 39 additions & 0 deletions src/features/health/query/useHealth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import { useParams } from 'next/navigation'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'

import { getHealth, postHealth, putHealth } from '@/features'
import { queryKeys } from '@/features'
import { usePeriod } from '@/features/health/store/healthStore'
import type { PutPhysicalRequest } from '@/types'

export const useFetchHealth = () => {
const period = usePeriod()
const { userId } = useParams<{ userId: string }>()
return useQuery({
queryKey: queryKeys.page(userId, period),
queryFn: () => getHealth({ userId, period }),
})
}

export const useCreateHealth = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: postHealth,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.all })
},
})
}

export const useEditHealth = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ weightId, request }: { weightId: number; request: PutPhysicalRequest }) =>
putHealth(weightId, request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.all })
},
})
}
35 changes: 35 additions & 0 deletions src/features/health/service/useWeightSelectConfirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import dayjs from 'dayjs'

import { usePhysicalId, useWeightId } from '@/features'
import { useCreateHealth, useEditHealth } from '@/features'
import { useSelectedWeight } from '@/store'

export const useWeightSelectConfirm = (toggleShowing: VoidFunction) => {
const weightId = useWeightId()
const physicalId = usePhysicalId()
const weight = Number(useSelectedWeight())
const { mutate: editWeight } = useEditHealth()
const { mutate: createWeight } = useCreateHealth()
const createdAt = dayjs().format('YYYY-MM-DD')

const handleConfirm = () => {
if (weightId) {
editWeight({
weightId,
request: {
weight,
createdAt,
},
})
} else if (physicalId) {
createWeight({
physicalId,
weight,
createdAt,
})
}
toggleShowing()
}

return handleConfirm
}
52 changes: 52 additions & 0 deletions src/features/health/store/healthStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { create } from 'zustand'

import type { GetPhysicalResponse, HealthReportType, WeightInfoType } from '@/types'

type PeriodType = 'weekly' | 'monthly'

type HealthState = {
period: PeriodType
physicalId?: number
weightId?: number
weight?: number
weightInfo: WeightInfoType
healthreport?: HealthReportType
actions: HealthActions
}

type HealthActions = {
togglePeriod: VoidFunction
syncFetchHealthData: (data: GetPhysicalResponse) => void
}

const useHealthStore = create<HealthState>((set, get) => ({
period: 'weekly',
physicalId: undefined,
weightId: undefined,
weight: undefined,
weightInfo: [],
healthreport: undefined,
actions: {
togglePeriod: () => {
const { period } = get()
set({ period: period === 'weekly' ? 'monthly' : 'weekly' })
},
syncFetchHealthData: ({ data }: GetPhysicalResponse) => {
set({
physicalId: data.physicalId,
weightId: data.weight?.weightId,
weight: data.weight?.weight,
weightInfo: data.weightInfo,
healthreport: data.healthReport,
})
},
},
}))

export const usePeriod = () => useHealthStore((state) => state.period)
export const usePhysicalId = () => useHealthStore((state) => state.physicalId)
export const useWeightId = () => useHealthStore((state) => state.weightId)
export const useWeight = () => useHealthStore((state) => state.weight)
export const useHealthReport = () => useHealthStore((state) => state.healthreport)
export const useWeightInfo = () => useHealthStore((state) => state.weightInfo)
export const useHealthActions = () => useHealthStore((state) => state.actions)
19 changes: 19 additions & 0 deletions src/features/health/styles/chart.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.recharts-xAxis {
display: none;
}

.recharts-cartesian-axis-line {
display: none;
}

.recharts-cartesian-axis-tick-line {
display: none;
}

.recharts-cartesian-grid-vertical {
display: none;
}

.recharts-yAxis {
width: 0;
}
57 changes: 57 additions & 0 deletions src/features/health/ui/GraphSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client'

import { Icon } from '@/components'
import Label from '@/components/label/Label'
import { useHealthActions, usePeriod, useWeightInfo } from '@/features'

import { useGraph } from '../hooks/useGraph'

import { WeightChart } from './WeightChart'

export const GraphSection = () => {
const period = usePeriod()
const WeightInfo = useWeightInfo()
const { togglePeriod } = useHealthActions()
const { data, increase, decrease } = useGraph()

return (
<section className="mb-10 w-full">
{/** Label과 Toggle Button 렌더링 */}
<div className="flex items-center gap-4">
<Label icon="check-label">체중 그래프</Label>
<button
className="body-M flex-center gap-2 rounded-full bg-gray-2 px-3 pb-[6px] pt-1 text-gray-7"
onClick={togglePeriod}
>
<span>{period === 'weekly' ? '주간' : '월간'}</span>
<span className="mt-[2px]">
<Icon name="arrow-down" />
</span>
</button>
</div>

{/** 그래프 네비게이터 렌더링 */}
<div className="mb-4 mt-[21px] flex items-center justify-between">
<button className="flex cursor-pointer items-center gap-2" onClick={increase}>
<Icon name="arrow-left" color="#0E8763" />
<div className="subtitle-B text-gray-7">이전</div>
</button>
<button className="flex cursor-pointer items-center gap-2" onClick={decrease}>
<div className="subtitle-B text-gray-7">다음</div>
<Icon name="arrow-right" color="#0E8763" />
</button>
</div>

{/** 그래프 렌더링 */}
<div className="h-[243px] w-full">
{WeightInfo.length !== 0 ? (
<WeightChart data={data} />
) : (
<div className="flex-center size-full whitespace-pre text-center text-gray-6">
{'체중을 입력하면\n그래프가 나타나요!'}
</div>
)}
</div>
</section>
)
}
33 changes: 33 additions & 0 deletions src/features/health/ui/HealthClientPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client'

import { DashBoardTemplate } from '@/features'
import { useHealthActions } from '@/features'
import { useFetchHealth } from '@/features'

import { GraphSection } from './GraphSection'
import { ReportSection } from './ReportSection'
import { WeightSection } from './WeightSection'

export const HealthClientPage = () => {
const { data } = useFetchHealth()
const { syncFetchHealthData } = useHealthActions()

if (!data) {
return (
<DashBoardTemplate route="health">
{/* 추후 수정 */}
<div>데이터를 불러오는데 실패하였습니다</div>
</DashBoardTemplate>
)
}

syncFetchHealthData({ ...data })

return (
<DashBoardTemplate route="health">
<GraphSection />
<WeightSection />
<ReportSection />
</DashBoardTemplate>
)
}
32 changes: 32 additions & 0 deletions src/features/health/ui/ReportSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client'

import Label from '@/components/label/Label'
import { useHealthReport } from '@/features'
import { useUserStore } from '@/store'

export const ReportSection = () => {
const {
user: { name },
} = useUserStore()
const report = useHealthReport()

return (
<section className="w-full">
<Label icon="health-label">건강 분석 리포트</Label>
{report ? (
<>
<div className="subtitle-M mt-2 whitespace-pre rounded-xl bg-mint-0 px-6 py-4">
{`${name}님과 같은 성별, 나이의 평균\n몸무게인 ${report.apiAvgWeight}kg보다 ${Math.abs(report.diffWeight).toFixed(2)}kg ${report.diffWeight > 0 ? '높아요' : '낮아요'}.`}
</div>
<div className="subtitle-M mt-2 whitespace-pre rounded-xl bg-mint-0 px-6 py-4">
{`${name}님의 평균 체중이\n지난주보다 ${Math.abs(report.lastweekWeight)}kg ${report.lastweekWeight > 0 ? '증가했어요' : '감소했어요'}.`}
</div>
</>
) : (
<div className="subtitle-M mt-2 whitespace-pre rounded-xl bg-mint-0 px-6 py-4 text-gray-6">
{'체중을 입력하면\n리포트를 확인할 수 있어요!'}
</div>
)}
</section>
)
}
Loading

0 comments on commit 6877076

Please sign in to comment.