Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat/#28
Browse files Browse the repository at this point in the history
  • Loading branch information
lkhoony committed Aug 28, 2024
2 parents a9a60ce + 109fd0c commit 7428376
Show file tree
Hide file tree
Showing 20 changed files with 266 additions and 41 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ jobs:
aws s3 sync --region ap-northeast-2 dist s3://alignlab-client --delete
continue-on-error: true

- name: Invalidate CloudFront Cache
if: steps.deploy.outcome == 'success'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
run: |
aws cloudfront create-invalidation --region ap-northeast-2 --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*"
- name: Discord notification - Success
if: steps.deploy.outcome == 'success'
env:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@tanstack/react-query": "^5.51.23",
"axios": "1.7.3",
"core-js": "^3.28.0",
"lucide-react": "^0.435.0",
"p5": "^1.9.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
19 changes: 19 additions & 0 deletions src/api/analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axiosInstance from "./axiosInstance"
import { poseType } from "./pose"

export interface TodayAnalysisData {
date: string
count: {
type: poseType
count: number
}[]
}

export const getTodayAnalysis = async (): Promise<TodayAnalysisData> => {
try {
const res = await axiosInstance.get("/pose-counts/daily")
return res.data.data
} catch (e) {
throw e
}
}
20 changes: 14 additions & 6 deletions src/components/PoseDetector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import usePushNotification from "@/hooks/usePushNotification"
import type { pose } from "@/utils/detector"
import { detectSlope, detectTextNeck } from "@/utils/detector"
import { detectHandOnChin, detectSlope, detectTextNeck } from "@/utils/detector"
import { drawPose } from "@/utils/drawer"
import { worker } from "@/utils/worker"
import { useCallback, useEffect, useRef, useState } from "react"
Expand All @@ -19,6 +19,7 @@ const PoseDetector: React.FC = () => {
const [isScriptError, setIsScriptError] = useState<boolean>(false)
const [isTextNeck, setIsTextNeck] = useState<boolean | null>(null)
const [isShoulderTwist, setIsShoulderTwist] = useState<boolean | null>(null)
const [isHandOnChin, setIsHandOnChin] = useState<boolean | null>(null)
const [isModelLoaded, setIsModelLoaded] = useState<boolean>(false)
const [isSnapSaved, setIsSnapSaved] = useState<boolean>(false)
const [isPopupVisible, setIsPopupVisible] = useState<boolean>(false)
Expand All @@ -28,7 +29,7 @@ const PoseDetector: React.FC = () => {

const turtleNeckTimer = useRef<any>(null)
const shoulderTwistTimer = useRef<any>(null)
// const chinUtpTimer = useRef<any>(null)
const chinUtpTimer = useRef<any>(null)
// const tailboneSit = useRef<any>(null)

const canvasRef = useRef<HTMLCanvasElement>(null)
Expand Down Expand Up @@ -89,12 +90,14 @@ const PoseDetector: React.FC = () => {
if (snapRef.current) {
const _isShoulderTwist = detectSlope(snapRef.current, results, false)
const _isTextNeck = detectTextNeck(snapRef.current, results, true)
const _isHandOnChin = detectHandOnChin(results)

if (_isShoulderTwist !== null) setIsShoulderTwist(_isShoulderTwist)
if (_isTextNeck !== null) setIsTextNeck(_isTextNeck)
if (_isHandOnChin !== null) setIsHandOnChin(_isHandOnChin)
}
},
[setIsShoulderTwist, setIsTextNeck, showNotification]
[setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, showNotification]
)

const detectStart = useCallback(
Expand Down Expand Up @@ -139,8 +142,12 @@ const PoseDetector: React.FC = () => {
}
}

const getIsRight = (_isShoulderTwist: boolean | null, _isTextNeck: boolean | null): boolean => {
if (!_isShoulderTwist && !_isTextNeck) return true
const getIsRight = (
_isShoulderTwist: boolean | null,
_isTextNeck: boolean | null,
_isHandOnChin: boolean | null
): boolean => {
if (!_isShoulderTwist && !_isTextNeck && !_isHandOnChin) return true
return false
}

Expand All @@ -166,6 +173,7 @@ const PoseDetector: React.FC = () => {

usePoseTimer(isTextNeck, "TURTLE_NECK", turtleNeckTimer)
usePoseTimer(isShoulderTwist, "SHOULDER_TWIST", shoulderTwistTimer)
usePoseTimer(isHandOnChin, "CHIN_UTP", chinUtpTimer)

useEffect(() => {
requestNotificationPermission()
Expand Down Expand Up @@ -209,7 +217,7 @@ const PoseDetector: React.FC = () => {
<div className="absolute top-0 flex w-full items-center justify-center rounded-t-lg bg-[#1A1B1D] bg-opacity-75 p-[20px] text-white">
{!isSnapSaved
? "바른 자세를 취한 후, 하단의 버튼을 눌러주세요."
: getIsRight(isShoulderTwist, isTextNeck)
: getIsRight(isShoulderTwist, isTextNeck, isHandOnChin)
? "올바른 자세입니다."
: "올바르지 않은 자세입니다."}
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Posture/PostrueCrew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PostureGuide from "@assets/icons/posture-guide-button-icon.svg?react"
import RankingGuideToolTip from "@assets/images/ranking-guide.png"
import { useEffect, useState } from "react"
import SelectBox from "@components/SelectBox"
import { useAuthStore } from "@/store"

interface IPostureCrew {
groupUserId: number
Expand All @@ -19,15 +20,14 @@ interface PostureCrewProps {

export default function PostrueCrew(props: PostureCrewProps) {
const { toggleSidebar } = props
const accessToken = useAuthStore((state) => state.accessToken)
const [crews, setCrews] = useState<IPostureCrew[]>([])
const [isConnected, setIsConnected] = useState<"loading" | "success" | "disconnected">("loading")
const [isEnabled, setIsEnabled] = useState(true)
const [notiAlarmTime, setNotiAlarmTime] = useState("틀어진 즉시")

useEffect(() => {
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJoZXJvLWFsaWdubGFiLWFwaSIsImF1ZCI6Imhlcm8tYWxpZ25sYWItYXBpIiwiaWQiOjIwMDAwMSwidHlwZSI6ImFjY2Vzc1Rva2VuIiwiZXhwIjoxNzM1Mzk4MDAwfQ.pIl87yrMX4EVoLlBOG0A2X5AMRRUXalwMKnfH6cSDE8"
const socket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${token}`)
const socket = new WebSocket(`wss://api.alignlab.site/ws/v1/groups/1/users?X-HERO-AUTH-TOKEN=${accessToken}`)

socket.onopen = () => {
console.log("WebSocket connected")
Expand Down
31 changes: 15 additions & 16 deletions src/components/SideNav.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useAuthStore } from "@/store/AuthStore"
import MainCraftIcon from "@assets/icons/posture-craft-side-nav-icon.svg?react"
import MonitoringIcon from "@assets/icons/side-nav-monitor-icon.svg?react"
import AnalysisIcon from "@assets/icons/side-nav-analysis-icon.svg?react"
import CrewIcon from "@assets/icons/side-nav-crew-icon.svg?react"
import { useAuthStore } from "@/store/AuthStore"
import MonitoringIcon from "@assets/icons/side-nav-monitor-icon.svg?react"
import { Link, useLocation } from "react-router-dom"

const navItems = [
{
icon: MonitoringIcon,
label: "모니터링",
link: "/monitoring",
className: "bg-gray-700", // 선택 됐을 때
},
{
icon: AnalysisIcon,
Expand All @@ -27,6 +27,7 @@ const footerLinks = ["이용약관", "의견보내기", "로그아웃"]

export default function SideNav() {
const nickname = useAuthStore((state) => state.user?.nickname)
const location = useLocation()

return (
<aside className="w-[224px] flex-none bg-[#1C1D20]">
Expand All @@ -51,19 +52,17 @@ export default function SideNav() {
{/* Navigation Links */}
<nav className="mt-10">
<ul>
{navItems.map(({ icon: Icon, label, className }) => (
<li
key={label}
className={`mb-1 flex cursor-pointer items-center rounded-r-md p-3 hover:bg-gray-700 ${
className || ""
}`}
>
<a className="flex w-full items-center">
<Icon className="ml-3 mr-2 h-5 w-5" />
<span>{label}</span>
</a>
</li>
))}
{navItems.map(({ icon: Icon, label, link }) => {
const isActive = location.pathname === link
return (
<li key={label} className={`mb-1 rounded-r-md ${isActive ? "bg-gray-700" : "hover:bg-gray-700"}`}>
<Link to={link} className={`nav-item flex w-full items-center p-3 ${isActive ? "active" : ""}`}>
<Icon className="ml-3 mr-2 h-5 w-5" />
<span className={isActive ? "font-bold" : ""}>{label}</span>
</Link>
</li>
)
})}
</ul>
</nav>
</div>
Expand Down
10 changes: 6 additions & 4 deletions src/constants/routes.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"MONITORING": "/monitoring",
"ANALYSIS": "/analysis",
"LOGIN": "/login",
"AUTH" : "/auth",
"SOCKET": "/socket",
"HOME" : "/home"
}
"AUTH": "/auth",
"CREW": "/crew",
"HOME": "/home",
"SOCKET": "/socket"
}
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReactDOM from "react-dom/client"
import App from "./App"
import "@/style/tailwind.css"
import "@/style/index.css"

ReactDOM.createRoot(document.getElementById("root")!).render(<App />)
9 changes: 9 additions & 0 deletions src/layouts/AnalysisLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Outlet } from "react-router-dom"

export default function AnalysisLayout() {
return (
<div className="h-full bg-[#F9F9FD] p-12">
<Outlet />
</div>
)
}
7 changes: 3 additions & 4 deletions src/layouts/Layout.tsx → src/layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React from "react"
import { Outlet } from "react-router-dom"
import SideNav from "@/components/SideNav"

const Layout: React.FC = () => {
const BaseLayout: React.FC = () => {
return (
<div className="flex min-h-screen w-screen bg-gray-100">
<div className="flex w-full">
<SideNav />

{/* Main Content */}
<main className="min-w-[652px] flex-1 overflow-y-auto bg-[#1C1D20] p-3">
<main className="min-w-[652px] flex-1 overflow-y-auto">
<Outlet />
</main>
</div>
</div>
)
}

export default Layout
export default BaseLayout
9 changes: 9 additions & 0 deletions src/layouts/MonitoringLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Outlet } from "react-router-dom"

export default function MonitoringLayout() {
return (
<div className="h-full bg-[#1C1D20] p-3">
<Outlet />
</div>
)
}
4 changes: 3 additions & 1 deletion src/layouts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { default as Layout } from "./Layout"
export { default as Layout } from "./BaseLayout"
export { default as AnalysisLayout } from "./AnalysisLayout"
export { default as MonitoringLayout } from "./MonitoringLayout"
91 changes: 91 additions & 0 deletions src/pages/AnalysisDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { getTodayAnalysis, TodayAnalysisData } from "@/api/analysis"
import { poseType } from "@/api/pose"
import { useQuery } from "@tanstack/react-query"
import { Calendar, ChevronLeft, ChevronRight } from "lucide-react"
import { useMemo } from "react"

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

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

const totalCount = useMemo(() => {
return data?.count.reduce((acc, item) => acc + item.count, 0) || 0
}, [data])

return (
<div className="h-full w-full">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-bold">오늘의 자세 분석</h1>
<div className="flex space-x-2">
<button className="rounded-full bg-gray-200 p-2">
<ChevronLeft size={20} />
</button>
<button className="rounded-full bg-blue-500 p-2 text-white">
<ChevronRight size={20} />
</button>
</div>
</div>

{isLoading && <div>로딩 중입니다...</div>}
{isError && <div>데이터를 불러오는 것에 실패했습니다</div>}

{/* 상단 카드 섹션 */}
{!isLoading && !isError && data && (
<>
<div className="mb-8 grid grid-cols-4 gap-4">
<div className="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>
{[
{ title: "거북목", type: "TURTLE_NECK" as poseType },
{ title: "어깨 틀어짐", type: "SHOULDER_TWIST" as poseType },
{ title: "턱 괴기", type: "CHIN_UTP" as poseType },
].map(({ title, type }, index) => (
<div key={index} className="rounded-lg bg-gray-100 p-4">
<p className="text-sm text-gray-600">{title}</p>
<p className="mt-2 text-3xl font-bold">{getPoseCount(type)}</p>
<div className="mt-2 h-24 bg-gray-300">{/* 이미지 삽입 */}</div>
</div>
))}
</div>

{/* 차트 섹션 */}
<div className="rounded-lg bg-white p-6 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} />
<ChevronRight size={20} />
</div>
<div className="flex items-center text-sm text-gray-600">
<Calendar size={16} className="mr-2" />
{data?.date}
</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>
</div>
</>
)}
</div>
)
}

export default AnalysisDashboard
2 changes: 2 additions & 0 deletions src/pages/AuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const AuthPage: React.FC = () => {

const _accessToken = await oauthMutation.mutateAsync(code)

console.log("_accessToken: ", _accessToken)

const isUserSignedUp = await getIsSignUpMutation.mutateAsync(_accessToken)

if (!isUserSignedUp) {
Expand Down
3 changes: 3 additions & 0 deletions src/pages/Crew.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Crew() {
return <div>Crew</div>
}
2 changes: 2 additions & 0 deletions src/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { default as AuthPage } from "./AuthPage"
export { default as MonitoringPage } from "./MonitoringPage"
export { default as AnalysisDashboard } from "./AnalysisDashboard"
export { default as Crew } from "./Crew"
export { default as HomePage } from "./HomePage"
Loading

0 comments on commit 7428376

Please sign in to comment.