Skip to content

Commit 83b01e5

Browse files
committed
[feat/#58] 사이드 네비게이션 및 재촬영 관련 로직 수정, 코드 최적화
1 parent a7cd920 commit 83b01e5

15 files changed

+309
-135
lines changed
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import CloseCrewPanelIcon from "@assets/images/posture-snapshot-guide.png"
2+
import { ReactElement } from "react"
3+
import ModalContainer from "../ModalContainer"
4+
5+
const GoodPostureGuidePopupModal = ({ onClose }: { onClose: () => void }): ReactElement => {
6+
return (
7+
<ModalContainer onClose={onClose}>
8+
<div className="absolute inset-0 flex items-center justify-center bg-zinc-900/25">
9+
{/* blur 처리 */}
10+
<div className="flex h-[472px] w-[800px] flex-col items-center rounded-lg bg-white pt-10 shadow-lg">
11+
<div className="pb-6">
12+
<div className="text-[30px] font-bold text-[#1E2535]">바른 자세 가이드</div>
13+
</div>
14+
{/* content */}
15+
<div className="flex items-center gap-8 pb-6">
16+
<div className="flex h-[254px] w-[284px] flex-col items-center justify-end rounded-[17px] bg-[#EFEFF0]">
17+
<img src={CloseCrewPanelIcon} alt="스냅샷 가이드" />
18+
</div>
19+
<div className="flex flex-col gap-8">
20+
<div className="flex gap-3">
21+
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
22+
1
23+
</span>
24+
<span className="font-[20px] font-semibold text-zinc-800">머리와 목을 일직선으로 곧게 펴기</span>
25+
</div>
26+
<div className="flex gap-3">
27+
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
28+
2
29+
</span>
30+
<span className="font-[20px] font-semibold text-zinc-800">양쪽 어깨 일직선 유지하기</span>
31+
</div>
32+
<div className="flex gap-3">
33+
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
34+
3
35+
</span>
36+
<span className="font-[20px] font-semibold text-zinc-800">팔은 책상 위에 수평으로 두기</span>
37+
</div>
38+
<div className="flex gap-3">
39+
<span className="flex h-6 w-6 justify-center rounded-full bg-[#5A9CFF] text-center font-semibold text-white">
40+
4
41+
</span>
42+
<span className="font-[20px] font-semibold text-zinc-800">등과 허리는 등받이에 지지하기</span>
43+
</div>
44+
</div>
45+
</div>
46+
<button className="w-[354px] rounded-full bg-[#1A75FF] py-3 text-white" onClick={onClose}>
47+
확인
48+
</button>
49+
</div>
50+
</div>
51+
</ModalContainer>
52+
)
53+
}
54+
55+
export default GoodPostureGuidePopupModal

src/components/Modal/Modals.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { ModalsDispatchContext, ModalsStateContext } from "@/contexts/ModalsContext"
22
import { useContext, useEffect } from "react"
3+
import { useLocation } from "react-router-dom"
34
import CreateCrewModal from "./CreateCrewModal"
5+
import GoodPostureGuidePopupModal from "./GoodPostureGuideModal"
46
import InviteCrewModal from "./InviteCrewModal"
57
import JoinCrewModal from "./JoinCrewModal"
6-
import WithdrawCrewModal from "./WithdrawCrewModal"
78
import ToWithdrawModal from "./ToWithdrawModal"
8-
import { useLocation } from "react-router-dom"
9+
import WithdrawCrewModal from "./WithdrawCrewModal"
910

1011
export const modals = {
1112
createCrewModal: CreateCrewModal,
1213
inviteCrewModal: InviteCrewModal,
1314
joinCrewModal: JoinCrewModal,
1415
withdrawCrewModal: WithdrawCrewModal,
1516
ToWithdrawModal: ToWithdrawModal,
17+
postureGuideModal: GoodPostureGuidePopupModal,
1618
}
1719

1820
const Modals = (): React.ReactNode => {

src/components/PoseDetector.tsx

+48-62
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1+
import { position } from "@/api"
2+
import { duration } from "@/api/notification"
3+
import { poseType } from "@/api/pose"
4+
import { useCameraPermission } from "@/hooks/useCameraPermission"
5+
import { useGuidePopup } from "@/hooks/useGuidePopup"
6+
import { useSendPose } from "@/hooks/usePoseMutation"
17
import usePushNotification from "@/hooks/usePushNotification"
8+
import { useCreateSnaphot } from "@/hooks/useSnapshotMutation"
9+
import { useNotificationStore } from "@/store/NotificationStore"
10+
import { useSnapshotStore } from "@/store/SnapShotStore"
211
import type { pose } from "@/utils/detector"
3-
import { detectHandOnChin, detectSlope, detectTextNeck, detectTailboneSit } from "@/utils/detector"
12+
import { detectHandOnChin, detectSlope, detectTailboneSit, detectTextNeck } from "@/utils/detector"
413
import { drawPose } from "@/utils/drawer"
514
import { worker } from "@/utils/worker"
615
import { useCallback, useEffect, useRef, useState } from "react"
16+
import { useLocation } from "react-router-dom"
717
import Camera from "./Camera"
8-
import GuidePopup from "./Posture/GuidePopup/GuidePopup"
9-
import { useSnapshotStore } from "@/store/SnapshotStore"
10-
import { useCreateSnaphot } from "@/hooks/useSnapshotMutation"
11-
import { position } from "@/api"
12-
import { useSendPose } from "@/hooks/usePoseMutation"
13-
import { poseType } from "@/api/pose"
14-
import PostureMessage from "./Posture/PostureMessage"
1518
import Controls from "./Posture/Controls"
16-
import { useNotificationStore } from "@/store/NotificationStore"
17-
import { duration } from "@/api/notification"
18-
import { useCameraPermission } from "@/hooks/useCameraPermission"
19-
import { useLocation } from "react-router-dom"
19+
import GuidePopupModal from "./Posture/GuidePopup/GuidePopupModal"
20+
import PostureMessage from "./Posture/PostureMessage"
2021

2122
const PoseDetector: React.FC = () => {
2223
const [isScriptLoaded, setIsScriptLoaded] = useState<boolean>(false)
@@ -26,10 +27,12 @@ const PoseDetector: React.FC = () => {
2627
const [isTailboneSit, setIsTailboneSit] = useState<boolean | null>(null)
2728
const [isHandOnChin, setIsHandOnChin] = useState<boolean | null>(null)
2829
const [isModelLoaded, setIsModelLoaded] = useState<boolean>(false)
29-
const [isSnapSaved, setIsSnapSaved] = useState<boolean>(false)
30-
const [isPopupVisible, setIsPopupVisible] = useState<boolean>(true)
30+
// const [isSnapShotSaved, setIsSnapSaved] = useState<boolean>(false)
3131

3232
const { showNotification } = usePushNotification()
33+
34+
const { isPopupOpen, handleClosePopup, openPopup } = useGuidePopup()
35+
3336
const modelRef = useRef<any>(null)
3437
const snapRef = useRef<pose[] | null>(null)
3538
const resultRef = useRef<pose[] | null>(null)
@@ -46,11 +49,10 @@ const PoseDetector: React.FC = () => {
4649

4750
const canvasRef = useRef<HTMLCanvasElement>(null)
4851

49-
const snapshot = useSnapshotStore((state) => state.snapshot)
52+
const { isSnapShotSaved, snapshot, setSnapShot } = useSnapshotStore()
5053
const createSnapMutation = useCreateSnaphot()
5154
const sendPoseMutation = useSendPose()
5255

53-
const setSnap = useSnapshotStore((state) => state.setSnapshot)
5456
const userNoti = useNotificationStore((state) => state.notification)
5557

5658
const { requestNotificationPermission } = usePushNotification()
@@ -115,11 +117,11 @@ const PoseDetector: React.FC = () => {
115117
condition: boolean | null,
116118
timerRef: React.MutableRefObject<any>,
117119
poseType: poseType,
118-
isSnapSaved: boolean,
120+
isSnapShotSaved: boolean,
119121
cntRef: React.MutableRefObject<any>,
120122
isShowNoti: boolean | undefined
121123
): void => {
122-
if (condition && isSnapSaved) {
124+
if (condition && isSnapShotSaved) {
123125
if (!timerRef.current) {
124126
console.log(poseType, "start")
125127
timerRef.current = setInterval(() => {
@@ -158,24 +160,24 @@ const PoseDetector: React.FC = () => {
158160
if (_isTailboneSit !== null) setIsTailboneSit(_isTailboneSit)
159161

160162
// 공통 타이머 관리 함수 호출
161-
managePoseTimer(_isTextNeck, turtleNeckTimer, "TURTLE_NECK", isSnapSaved, turtleNeckCnt, _isShowNoti)
163+
managePoseTimer(_isTextNeck, turtleNeckTimer, "TURTLE_NECK", isSnapShotSaved, turtleNeckCnt, _isShowNoti)
162164
managePoseTimer(
163165
_isShoulderTwist,
164166
shoulderTwistTimer,
165167
"SHOULDER_TWIST",
166-
isSnapSaved,
168+
isSnapShotSaved,
167169
shoulderTwistCnt,
168170
_isShowNoti
169171
)
170-
managePoseTimer(_isTailboneSit, tailboneSitTimer, "TAILBONE_SIT", isSnapSaved, tailboneSitCnt, _isShowNoti)
171-
managePoseTimer(_isHandOnChin, chinUtpTimer, "CHIN_UTP", isSnapSaved, chinUtpCnt, _isShowNoti)
172+
managePoseTimer(_isTailboneSit, tailboneSitTimer, "TAILBONE_SIT", isSnapShotSaved, tailboneSitCnt, _isShowNoti)
173+
managePoseTimer(_isHandOnChin, chinUtpTimer, "CHIN_UTP", isSnapShotSaved, chinUtpCnt, _isShowNoti)
172174
const isRight = !_isTextNeck && !_isHandOnChin && !_isShoulderTwist && !_isTailboneSit
173175
if (canvasRef.current) drawPose(results, canvasRef.current, isRight)
174176
} else {
175177
if (canvasRef.current) drawPose(results, canvasRef.current)
176178
}
177179
},
178-
[setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, setIsTailboneSit, isSnapSaved, managePoseTimer, userNoti]
180+
[setIsShoulderTwist, setIsTextNeck, setIsHandOnChin, setIsTailboneSit, isSnapShotSaved, managePoseTimer, userNoti]
179181
)
180182

181183
const detectStart = useCallback(
@@ -204,21 +206,21 @@ const PoseDetector: React.FC = () => {
204206
{
205207
onSuccess: () => {
206208
if (snapRef.current) {
207-
setSnap(snapRef.current[0].keypoints)
208-
setIsSnapSaved(true)
209+
setSnapShot(snapRef.current[0].keypoints)
210+
// setIsSnapSaved(true)
209211
}
210212
},
211213
}
212214
)
213215
}
214216
}
215217
}
216-
}, [createSnapMutation, snapshot, setSnap])
218+
}, [createSnapMutation, snapshot, setSnapShot])
217219

218220
const getUserSnap = (): void => {
219221
if (snapshot) {
220222
snapRef.current = [{ keypoints: snapshot }]
221-
setIsSnapSaved(true)
223+
// setIsSnapSaved(true)
222224
}
223225
}
224226

@@ -235,15 +237,6 @@ const PoseDetector: React.FC = () => {
235237
notificationTimer.current = null
236238
}
237239

238-
const clearSnap = (): void => {
239-
if (snapshot) {
240-
snapRef.current = null
241-
setIsSnapSaved(false)
242-
setSnap(null)
243-
clearTimers() // 타이머들을 초기화
244-
}
245-
}
246-
247240
const clearCnt = (): void => {
248241
turtleNeckCnt.current = 0
249242
shoulderTwistCnt.current = 0
@@ -293,11 +286,11 @@ const PoseDetector: React.FC = () => {
293286
}, [])
294287

295288
useEffect(() => {
296-
if (!isSnapSaved || !hasPermission) {
289+
if (!isSnapShotSaved || !hasPermission) {
297290
clearTimers() // 스냅샷이 저장되지 않았을 때 타이머들을 초기화
298291
clearCnt() // 횟수도 초기화
299292
}
300-
}, [isSnapSaved, hasPermission])
293+
}, [isSnapShotSaved, hasPermission])
301294

302295
useEffect(() => {
303296
if (isModelLoaded && hasPermission) {
@@ -313,7 +306,7 @@ const PoseDetector: React.FC = () => {
313306
}, [snapshot])
314307

315308
useEffect(() => {
316-
if (!isSnapSaved || !userNoti) return
309+
if (!isSnapShotSaved || !userNoti) return
317310

318311
clearCnt()
319312
clearInterval(notificationTimer.current)
@@ -328,16 +321,11 @@ const PoseDetector: React.FC = () => {
328321
}
329322
}, 1000 * 60 * t)
330323
}
331-
}, [userNoti, isSnapSaved])
324+
}, [userNoti, isSnapShotSaved])
332325

333326
// 팝업 열기
334327
const handleShowPopup = (): void => {
335-
setIsPopupVisible(true)
336-
}
337-
338-
// 팝업 닫기
339-
const handleClosePopup = (): void => {
340-
setIsPopupVisible(false)
328+
openPopup()
341329
}
342330

343331
return (
@@ -351,24 +339,22 @@ const PoseDetector: React.FC = () => {
351339
<Camera detectStart={detectStart} canvasRef={canvasRef} />
352340
{isModelLoaded && (
353341
<>
354-
<PostureMessage
355-
isSnapSaved={isSnapSaved}
356-
isShoulderTwist={isShoulderTwist}
357-
isTextNeck={isTextNeck}
358-
isHandOnChin={isHandOnChin}
359-
isTailboneSit={isTailboneSit}
360-
hasPermission={hasPermission}
361-
/>
362-
<Controls
363-
isSnapSaved={isSnapSaved}
364-
getInitSnap={getInitSnap}
365-
clearSnap={clearSnap}
366-
handleShowPopup={handleShowPopup}
367-
hasPermission={hasPermission}
368-
/>
342+
{!isPopupOpen && (
343+
<PostureMessage
344+
isSnapShotSaved={isSnapShotSaved}
345+
isShoulderTwist={isShoulderTwist}
346+
isTextNeck={isTextNeck}
347+
isHandOnChin={isHandOnChin}
348+
isTailboneSit={isTailboneSit}
349+
hasPermission={hasPermission}
350+
/>
351+
)}
352+
{!isSnapShotSaved && hasPermission && (
353+
<Controls getInitSnap={getInitSnap} handleShowPopup={handleShowPopup} />
354+
)}
369355
</>
370356
)}
371-
{isPopupVisible && <GuidePopup onClose={handleClosePopup} />}
357+
{isPopupOpen && <GuidePopupModal onClose={handleClosePopup} />}
372358
</div>
373359
)}
374360
</>

src/components/Posture/Controls.tsx

+20-37
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,29 @@ import PostureCheckIcon from "@assets/icons/good-posture-check-button-icon.svg?r
22
import GuideIcon from "@assets/icons/posture-guide-button-icon.svg?react"
33

44
const Controls: React.FC<{
5-
isSnapSaved: boolean
6-
hasPermission: boolean
75
getInitSnap: () => void
8-
clearSnap: () => void
96
handleShowPopup: () => void
10-
}> = ({ isSnapSaved, getInitSnap, clearSnap, handleShowPopup, hasPermission }) => {
11-
return !hasPermission ? null : (
7+
}> = ({ getInitSnap, handleShowPopup }) => {
8+
return (
129
<div className="absolute bottom-0 flex w-full items-center justify-center gap-[16px] p-[50px] text-white">
13-
{!isSnapSaved ? (
14-
<>
15-
<button
16-
className="flex w-[260px] items-center justify-center rounded rounded-full bg-white bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-zinc-900"
17-
onClick={handleShowPopup}
18-
>
19-
<div className="flex flex-row items-center gap-2">
20-
<GuideIcon />
21-
<span>가이드 다시 볼게요!</span>
22-
</div>
23-
</button>
24-
<button
25-
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-white"
26-
onClick={getInitSnap}
27-
>
28-
<div className="flex flex-row items-center gap-2">
29-
<PostureCheckIcon />
30-
바른자세를 취했어요!
31-
</div>
32-
</button>
33-
</>
34-
) : (
35-
<button
36-
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 p-[20px] text-white"
37-
onClick={clearSnap}
38-
>
39-
<div className="flex flex-row items-center gap-2">
40-
<PostureCheckIcon />
41-
스냅샷 다시찍기
42-
</div>
43-
</button>
44-
)}
10+
<button
11+
className="flex w-[260px] items-center justify-center rounded rounded-full bg-white bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-zinc-900"
12+
onClick={handleShowPopup}
13+
>
14+
<div className="flex flex-row items-center gap-2">
15+
<GuideIcon />
16+
<span>가이드 다시 볼게요!</span>
17+
</div>
18+
</button>
19+
<button
20+
className="flex w-[260px] items-center justify-center rounded rounded-full bg-[#1A75FF] bg-opacity-80 px-10 py-3 font-semibold leading-[32px] text-white"
21+
onClick={getInitSnap}
22+
>
23+
<div className="flex flex-row items-center gap-2">
24+
<PostureCheckIcon />
25+
바른자세를 취했어요!
26+
</div>
27+
</button>
4528
</div>
4629
)
4730
}

0 commit comments

Comments
 (0)