Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat/#32] 대시보드에 차트가 보이도록 구현 #50

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@tanstack/react-query": "^5.51.23",
"axios": "1.7.3",
"core-js": "^3.28.0",
"echarts": "^5.5.1",
"echarts-for-react": "^3.0.2",
"lucide-react": "^0.435.0",
"p5": "^1.9.4",
"react": "^18.2.0",
Expand Down
24 changes: 23 additions & 1 deletion src/api/analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,33 @@ export interface TodayAnalysisData {
}[]
}

export const getTodayAnalysis = async (): Promise<TodayAnalysisData> => {
export interface TotalAnalysisData {
data: TodayAnalysisData[]
page: number
size: number
totalPage: number
totalCount: number
sort: {
empty: boolean
sorted: boolean
unsorted: boolean
}
}

export const getTodayPoseAnalysis = async (): Promise<TodayAnalysisData> => {
try {
const res = await axiosInstance.get("/pose-counts/daily")
return res.data.data
} catch (e) {
throw e
}
}

export const getTotalPoseAnalysis = async (): Promise<TodayAnalysisData[]> => {
try {
const res = await axiosInstance.get("/pose-counts?sort=date,desc")
return res.data.data
} catch (e) {
throw e
}
}
10 changes: 10 additions & 0 deletions src/assets/icons/dash-board-total-count.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions src/components/Dashboard/Chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ECharts from "echarts-for-react"

const poseTypeLabels: any = {
TURTLE_NECK: "거북목",
SHOULDER_TWIST: "어깨 틀어짐",
CHIN_UTP: "턱 괴기",
TAILBONE_SIT: "꼬리뼈로 앉기",
}

const PoseAnalysisChart = ({ data }: { data: any[] }) => {
const options = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
},
legend: {
data: Object.values(poseTypeLabels),
top: 20,
},
grid: {
left: "3%",
right: "3%",
bottom: "5%",
top: "25%",
containLabel: true,
},
xAxis: {
type: "category",
data: data.map((item) => item.date),
},
yAxis: {
type: "value",
minInterval: 1,
axisLabel: {
formatter: "{value}",
},
},
series: Object.keys(poseTypeLabels).map((poseType) => ({
name: poseTypeLabels[poseType],
type: "line",
data: data.map((item) => {
const poseCount = item.count.find((c: any) => c.type === poseType)
return poseCount ? poseCount.count : 0
}),
})),
}

return <ECharts option={options} opts={{ renderer: "svg" }} style={{ height: "100%", width: "100%" }} />
}

export default PoseAnalysisChart
27 changes: 27 additions & 0 deletions src/hooks/useDashBoard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getTodayPoseAnalysis, getTotalPoseAnalysis } from "@/api/analysis"
import { useQueries } from "@tanstack/react-query"

export const usePoseAnalysis = () => {
const results = useQueries({
queries: [
{
queryKey: ["todayAnalysis"],
queryFn: getTodayPoseAnalysis,
},
{
queryKey: ["totalAnalysis"],
queryFn: getTotalPoseAnalysis,
},
],
})

const [todayAnalysis, totalAnalysis] = results

return {
todayAnalysis: todayAnalysis.data,
totalAnalysis: totalAnalysis.data,
isLoading: results.some((result) => result.isLoading),
isError: results.some((result) => result.isError),
errors: results.map((result) => result.error).filter(Boolean),
}
}
2 changes: 1 addition & 1 deletion src/layouts/AnalysisLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom"

export default function AnalysisLayout() {
return (
<div className="h-full bg-[#F9F9FD] p-12">
<div className="h-full bg-[#F9F9FD] px-28 py-12">
<Outlet />
</div>
)
Expand Down
60 changes: 30 additions & 30 deletions src/pages/AnalysisDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { useState, useRef, useEffect } from "react"
import { useQuery } from "@tanstack/react-query"
import { getTodayAnalysis, TodayAnalysisData } from "@/api/analysis"
import { poseType } from "@/api/pose"
import { Calendar, ChevronLeft, ChevronRight } from "lucide-react"
import { useEffect, useRef, useState } from "react"
import { usePoseAnalysis } from "@/hooks/useDashBoard"

import TurtleNeckImage from "@/assets/images/tutle-neck.png"
import ShoulderTwistImage from "@/assets/images/shoulder-twist.png"
import TotalCountChartIcon from "@/assets/icons/dash-board-total-count.svg?react"
import ChinUpImage from "@/assets/images/chin-up.png"
import ShoulderTwistImage from "@/assets/images/shoulder-twist.png"
import TailBoneSitImage from "@/assets/images/tail-bone-sit.png"
import TurtleNeckImage from "@/assets/images/tutle-neck.png"
import PoseAnalysisChart from "@/components/Dashboard/Chart"

const AnalysisDashboard = () => {
const [currentIndex, setCurrentIndex] = useState(0)
const carouselRef = useRef(null)

const { data, isLoading, isError } = useQuery<TodayAnalysisData>({
queryKey: ["todayAnalysis"],
queryFn: getTodayAnalysis,
})
const { todayAnalysis, totalAnalysis, isLoading, isError } = usePoseAnalysis()

console.log("totalAnalysis: ", totalAnalysis)

const getPoseCount = (type: poseType) => {
return data?.count.find((item: any) => item.type === type)?.count || 0
return todayAnalysis?.count.find((item: any) => item.type === type)?.count || 0
}

const totalCount = data?.count.reduce((acc, item) => acc + item.count, 0) || 0
const totalCount = todayAnalysis?.count.reduce((acc, item) => acc + item.count, 0) || 0

const carouselItems = [
{ title: "거북목", type: "TURTLE_NECK" as poseType, image: TurtleNeckImage },
Expand Down Expand Up @@ -75,14 +75,19 @@ const AnalysisDashboard = () => {
{isLoading && <div>로딩 중입니다...</div>}
{isError && <div>데이터를 불러오는 것에 실패했습니다</div>}

{!isLoading && !isError && data && (
{!isLoading && !isError && todayAnalysis && (
<div className="relative mb-8 overflow-hidden">
<div className="flex">
{/* 고정된 전체 틀어짐 횟수 카드 */}
<div className="mr-4 w-1/4 flex-shrink-0 rounded-lg bg-black p-4 text-white">
<p className="text-sm text-blue-400">전체 틀어짐 횟수</p>
<p className="mt-2 text-3xl font-bold">{totalCount}회</p>
<div className="mt-2 h-24 bg-gray-700">{/* 차트 이미지 삽입 */}</div>
<div className="relative mr-3 flex w-60 flex-col items-center rounded-lg bg-black text-white">
<p className="mt-8 text-sm text-[#5A9CFF]">전체 틀어짐 횟수</p>
<div className="mt-2 flex items-center">
<span className="text-4xl font-bold">{totalCount}</span>
<span className="ml-1 text-sm">회</span>
</div>
<div className="absolute bottom-[25px] flex h-24 justify-center">
<TotalCountChartIcon />
</div>
</div>

{/* 캐러셀 컨테이너 */}
Expand All @@ -99,7 +104,7 @@ const AnalysisDashboard = () => {
style={{ width: `${(carouselItems.length / 3) * 100}%` }}
>
{carouselItems.map(({ title, type, image }, index) => (
<div key={index} className="w-1/3 flex-shrink-0 px-2">
<div key={index} className="mr-3 w-1/5 flex-shrink-0">
<div className="relative overflow-hidden rounded-lg bg-gray-100">
<img src={image} alt={title} className="h-full w-full" />
<div className="absolute inset-0 flex flex-col items-center pt-8 text-black">
Expand All @@ -120,29 +125,24 @@ const AnalysisDashboard = () => {
)}
</div>
</div>
<div className="mb-12 mt-8">
<hr />
</div>
{/* 차트 섹션 */}
<div className="rounded-lg p-6 shadow">
<div className="rounded-lg shadow">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center space-x-2">
<button className="rounded-full bg-gray-800 px-4 py-2 text-sm text-white">7월 첫째주</button>
<ChevronLeft size={20} />
<span className="rounded-full bg-zinc-800 px-4 py-2 text-white">7월 첫째주 추이</span>
<ChevronRight size={20} />
</div>
<div className="flex items-center text-sm text-gray-600">
<Calendar size={16} className="mr-2" />
{data?.date}
{"2024-09-09"}
</div>
</div>
<div className="h-64 bg-gray-100">{/* 실제 차트 컴포넌트 삽입 */}</div>
<div className="mt-4 flex justify-center space-x-4">
{["거북목", "어깨 틀어짐", "턱 괴기", "고개숙여 보기"].map((item, index) => (
<div key={index} className="flex items-center">
<div
className={`mr-2 h-3 w-3 rounded-full bg-${["red", "blue", "green", "purple"][index]}-500`}
></div>
<span className="text-sm">{item}</span>
</div>
))}
<div className="h-[340px] rounded-[10px] border-[1px] border-solid border-gray-200 bg-white">
{totalAnalysis && <PoseAnalysisChart data={totalAnalysis} />}
</div>
</div>
</div>
Expand Down
33 changes: 33 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,22 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==

echarts-for-react@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1"
integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==
dependencies:
fast-deep-equal "^3.1.3"
size-sensor "^1.0.1"

echarts@^5.5.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.5.1.tgz#8dc9c68d0c548934bedcb5f633db07ed1dd2101c"
integrity sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==
dependencies:
tslib "2.3.0"
zrender "5.6.0"

electron-to-chromium@^1.5.4:
version "1.5.6"
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz"
Expand Down Expand Up @@ -3001,6 +3017,11 @@ signal-exit@^4.0.1:
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==

size-sensor@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/size-sensor/-/size-sensor-1.0.2.tgz#b8f8da029683cf2b4e22f12bf8b8f0a1145e8471"
integrity sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==

slash@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
Expand Down Expand Up @@ -3289,6 +3310,11 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"

tslib@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==

tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
Expand Down Expand Up @@ -3556,6 +3582,13 @@ yocto-queue@^0.1.0:
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zrender@5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.6.0.tgz#01325b0bb38332dd5e87a8dbee7336cafc0f4a5b"
integrity sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==
dependencies:
tslib "2.3.0"

zustand@^4.5.5:
version "4.5.5"
resolved "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz"
Expand Down
Loading