diff --git a/src/main/src/widgetConfig.ts b/src/main/src/widgetConfig.ts
index de9ed20..5cc32d7 100644
--- a/src/main/src/widgetConfig.ts
+++ b/src/main/src/widgetConfig.ts
@@ -8,8 +8,8 @@ export const WIDGET_CONFIG = {
defaultHeight: 320,
// 최소 / 최대 크기 (사용자가 조절 가능한 범위)
- minWidth: 150,
- minHeight: 32,
+ minWidth: 160,
+ minHeight: 45,
maxWidth: 260,
maxHeight: 348,
diff --git a/src/renderer/src/components/WidgetTitleBar/WidgetTitleBar.tsx b/src/renderer/src/components/WidgetTitleBar/WidgetTitleBar.tsx
index aa75989..685cedb 100644
--- a/src/renderer/src/components/WidgetTitleBar/WidgetTitleBar.tsx
+++ b/src/renderer/src/components/WidgetTitleBar/WidgetTitleBar.tsx
@@ -40,8 +40,8 @@ export function WidgetTitleBar({
,
sendMetrics: () => void,
) => {
+ // sessionId를 state로 관리하여 변경 감지
+ const [sessionId, setSessionId] = useState
(() =>
+ localStorage.getItem('sessionId'),
+ );
+
+ // sendMetrics 함수를 ref로 저장 (dependency 문제 방지)
+ const sendMetricsRef = useRef(sendMetrics);
+
+ // sendMetrics가 변경되면 ref 업데이트
+ useEffect(() => {
+ sendMetricsRef.current = sendMetrics;
+ }, [sendMetrics]);
+
+ // sessionId 변경 감지 (1초마다 체크)
useEffect(() => {
+ const checkSessionId = () => {
+ const currentSessionId = localStorage.getItem('sessionId');
+ if (currentSessionId !== sessionId) {
+ console.log(
+ `[세션 변경 감지] 타이머 초기화 - 이전: ${sessionId}, 현재: ${currentSessionId}`,
+ );
+ setSessionId(currentSessionId);
+ }
+ };
+
+ const checkInterval = setInterval(checkSessionId, 1000);
+
+ return () => {
+ clearInterval(checkInterval);
+ };
+ }, [sessionId]);
+
+ // 5분마다 자동 전송 (sessionId가 변경되면 타이머 재시작)
+ useEffect(() => {
+ // 세션이 없으면 interval을 시작하지 않음
+ if (!sessionId) {
+ console.log('[자동 전송] 세션이 없어 타이머를 시작하지 않습니다.');
+ return;
+ }
+
+ console.log('[자동 전송] 5분 타이머 시작');
const FIVE_MINUTES = 5 * 60 * 1000; // 5분 = 300,000ms
const intervalId = setInterval(() => {
- const sessionId = localStorage.getItem('sessionId');
+ const currentSessionId = localStorage.getItem('sessionId');
// 세션이 활성화되어 있고, 전송할 데이터가 있을 때만 전송
- if (sessionId && metricsRef.current && metricsRef.current.length > 0) {
+ if (
+ currentSessionId &&
+ metricsRef.current &&
+ metricsRef.current.length > 0
+ ) {
console.log(
`[자동 전송] ${metricsRef.current.length}개 메트릭 데이터 전송`,
);
- sendMetrics();
+ sendMetricsRef.current(); // ref를 통해 최신 함수 호출
}
}, FIVE_MINUTES);
- // 클린업: 컴포넌트 언마운트 시 interval 정리
+ // 클린업: 컴포넌트 언마운트 시 또는 세션 변경 시 interval 정리
return () => {
+ console.log('[자동 전송] 타이머 정리');
clearInterval(intervalId);
};
- }, [metricsRef, sendMetrics]);
+ }, [sessionId, metricsRef]); // sendMetrics 제거!
};
diff --git a/src/renderer/src/pages/Calibration/components/WelcomePanel.tsx b/src/renderer/src/pages/Calibration/components/WelcomePanel.tsx
index cb09f84..db34ccf 100644
--- a/src/renderer/src/pages/Calibration/components/WelcomePanel.tsx
+++ b/src/renderer/src/pages/Calibration/components/WelcomePanel.tsx
@@ -9,6 +9,9 @@ const WelcomePanel = ({
isPoseDetected,
onStartMeasurement,
}: WelcomePanelProps) => {
+ // localStorage에서 사용자 이름 가져오기
+ const username = localStorage.getItem('userName') || '사용자';
+
return (
@@ -16,7 +19,7 @@ const WelcomePanel = ({
바른자세 기준점 등록
- 거부기온앤온님의 바른 자세를 등록할 준비가 되셨다면
+ {username}님의 바른 자세를 등록할 준비가 되셨다면
측정하기 버튼을 눌러주세요.
diff --git a/src/renderer/src/pages/Main/MainPage.tsx b/src/renderer/src/pages/Main/MainPage.tsx
index 83df5a2..9d23677 100644
--- a/src/renderer/src/pages/Main/MainPage.tsx
+++ b/src/renderer/src/pages/Main/MainPage.tsx
@@ -43,17 +43,34 @@ const MainPage = () => {
const classifierRef = useRef(new PostureClassifier());
- // 메트릭을 서버로 전송하는 함수
- const sendMetricsToServer = () => {
- const sessionId = localStorage.getItem('sessionId');
- if (sessionId && metricsRef.current.length > 0) {
- saveMetrics({
- sessionId,
- metrics: metricsRef.current,
- });
- // 전송 후 메트릭 초기화
- metricsRef.current = [];
- }
+ // 메트릭을 서버로 전송하는 함수 (Promise 반환)
+ const sendMetricsToServer = (): Promise
=> {
+ return new Promise((resolve) => {
+ const sessionId = localStorage.getItem('sessionId');
+ if (sessionId && metricsRef.current.length > 0) {
+ saveMetrics(
+ {
+ sessionId,
+ metrics: metricsRef.current,
+ },
+ {
+ onSuccess: () => {
+ // 전송 완료 후 메트릭 초기화
+ metricsRef.current = [];
+ resolve();
+ },
+ onError: () => {
+ // 에러가 발생해도 resolve (계속 진행)
+ metricsRef.current = [];
+ resolve();
+ },
+ },
+ );
+ } else {
+ // 전송할 데이터가 없으면 즉시 완료
+ resolve();
+ }
+ });
};
/* 창 닫기 시 세션 정리 (메트릭 전송, 세션 종료, 카메라 종료, 위젯 닫기) */
diff --git a/src/renderer/src/pages/Main/components/AverageGraph/AverageGraphPannel.tsx b/src/renderer/src/pages/Main/components/AverageGraph/AverageGraphPannel.tsx
index 57c2c64..daa080f 100644
--- a/src/renderer/src/pages/Main/components/AverageGraph/AverageGraphPannel.tsx
+++ b/src/renderer/src/pages/Main/components/AverageGraph/AverageGraphPannel.tsx
@@ -91,10 +91,10 @@ const AverageGraphPannel = () => {
''}
itemStyle={{ fontSize: 12 }}
diff --git a/src/renderer/src/pages/Main/components/ExitPanel.tsx b/src/renderer/src/pages/Main/components/ExitPanel.tsx
index b8e0a79..419ad88 100644
--- a/src/renderer/src/pages/Main/components/ExitPanel.tsx
+++ b/src/renderer/src/pages/Main/components/ExitPanel.tsx
@@ -49,10 +49,16 @@ const ExitPanel = () => {
};
// 세션 조회 API 데이터 사용
- const totalTime = Math.round((data?.data.totalSeconds || 0) / 60); // 초를 분으로 변환
- const correctPostureTime = Math.round((data?.data.goodSeconds || 0) / 60); // 바른 자세 시간 (분)
+ const totalSeconds = data?.data.totalSeconds || 0;
+ const goodSeconds = data?.data.goodSeconds || 0;
+
+ const totalTime = Math.round(totalSeconds / 60); // 초를 분으로 변환
+ const correctPostureTime = Math.round(goodSeconds / 60); // 바른 자세 시간 (분)
+
+ // 비율은 초 단위로 먼저 계산 후 반올림 (정확도 향상)
const correctPosturePercentage =
- totalTime > 0 ? Math.round((correctPostureTime / totalTime) * 100) : 75;
+ totalSeconds > 0 ? Math.round((goodSeconds / totalSeconds) * 100) : 0;
+
const score = data?.data.score || 0; // 바른 자세 점수
/* 이번 세션에서 이동한 거리 계산 */
@@ -82,10 +88,16 @@ const ExitPanel = () => {
[colors.background],
);
- // 안쪽 링 프로그레스 데이터 (노란색) - 바른 자세 점수만큼 노란색
+ // 안쪽 링 프로그레스 데이터 (노란색) - 바른 자세 비율만큼 노란색
const ScoreProgressData = useMemo(
- () => [{ name: '바른 자세 점수', value: score, color: colors.score }],
- [score, colors.score],
+ () => [
+ {
+ name: '바른 자세 비율',
+ value: correctPosturePercentage,
+ color: colors.score,
+ },
+ ],
+ [correctPosturePercentage, colors.score],
);
const formatTime = (minutes: number) => {
@@ -179,7 +191,7 @@ const ExitPanel = () => {
innerRadius={77.75}
outerRadius={92}
startAngle={450}
- endAngle={450 - (72 / 100) * 360}
+ endAngle={450 - (correctPosturePercentage / 100) * 360}
dataKey="value"
stroke="none"
paddingAngle={0}
@@ -211,7 +223,7 @@ const ExitPanel = () => {
바른 자세 시간
- {72}%
+ {correctPosturePercentage}%
diff --git a/src/renderer/src/pages/Main/components/WebcamPanel.tsx b/src/renderer/src/pages/Main/components/WebcamPanel.tsx
index 462f80c..fa855c2 100644
--- a/src/renderer/src/pages/Main/components/WebcamPanel.tsx
+++ b/src/renderer/src/pages/Main/components/WebcamPanel.tsx
@@ -22,7 +22,7 @@ interface Props {
worldLandmarks?: WorldLandmark[],
) => void;
onToggleWebcam: () => void;
- onSendMetrics: () => void;
+ onSendMetrics: () => Promise
;
}
const WebcamPanel = ({
@@ -45,7 +45,7 @@ const WebcamPanel = ({
useResumeSessionMutation();
const { data: levelData } = useLevelQuery();
- const handleStartStop = () => {
+ const handleStartStop = async () => {
if (isExit) {
// 시작하기: 세션 생성 후 카메라 시작
createSession(undefined, {
@@ -61,13 +61,13 @@ const WebcamPanel = ({
},
});
} else {
- // 종료하기: 메트릭 전송 → 세션 중단 → 카메라 종료 → 위젯 닫기
+ // 종료하기: 메트릭 전송 완료 → 세션 중단 → 카메라 종료 → 위젯 닫기
const sessionId = localStorage.getItem('sessionId');
if (sessionId) {
- // 1. 수집된 메트릭을 서버로 전송
- onSendMetrics();
+ // 1. 수집된 메트릭을 서버로 전송 (완료 대기)
+ await onSendMetrics();
- // 2. 세션 종료
+ // 2. 세션 종료 (메트릭 전송 완료 후 실행)
stopSession(sessionId, {
onSuccess: () => {
setExit();
diff --git a/src/renderer/src/pages/Widget/WidgetPage.tsx b/src/renderer/src/pages/Widget/WidgetPage.tsx
index fcfd664..12f694f 100644
--- a/src/renderer/src/pages/Widget/WidgetPage.tsx
+++ b/src/renderer/src/pages/Widget/WidgetPage.tsx
@@ -9,7 +9,6 @@ import { usePostureSyncWithLocalStorage } from './hooks/usePostureSyncWithLocalS
import { useThemeSync } from './hooks/useThemeSync';
type WidgetSize = 'mini' | 'medium';
-type PostureState = 'turtle' | 'giraffe';
/* 레이아웃 전환 기준점 */
const BREAKPOINT = {
@@ -75,7 +74,7 @@ export function WidgetPage() {
const isMini = widgetSize === 'mini';
return (
-
+
{/* 커스텀 타이틀바 */}
diff --git a/src/renderer/src/pages/Widget/components/MediumWidgetContent.tsx b/src/renderer/src/pages/Widget/components/MediumWidgetContent.tsx
index c5e9957..8b05d24 100644
--- a/src/renderer/src/pages/Widget/components/MediumWidgetContent.tsx
+++ b/src/renderer/src/pages/Widget/components/MediumWidgetContent.tsx
@@ -64,10 +64,10 @@ export function MediumWidgetContent({ posture }: MediumWidgetContentProps) {
}
return (
-
+
{/* 캐릭터 영역 */}
{isGiraffe ? (
@@ -78,7 +78,7 @@ export function MediumWidgetContent({ posture }: MediumWidgetContentProps) {
{/* 상세 정보 영역 */}
-
+
{/* 진행 바 */}
diff --git a/src/renderer/src/styles/breakpoint.css b/src/renderer/src/styles/breakpoint.css
index aa7940f..268e863 100644
--- a/src/renderer/src/styles/breakpoint.css
+++ b/src/renderer/src/styles/breakpoint.css
@@ -20,6 +20,7 @@
}
/* 미디어 쿼리 헬퍼 */
+@custom-variant mini (@media(max-height: 62px));
@custom-media --mobile (max-width: 30rem);
@custom-media --tablet (min-width: 30rem) and (max-width: 56rem);
@custom-media --large (min-width: 80rem);
diff --git a/src/renderer/src/styles/colors.css b/src/renderer/src/styles/colors.css
index 976c60e..7e86381 100644
--- a/src/renderer/src/styles/colors.css
+++ b/src/renderer/src/styles/colors.css
@@ -89,6 +89,9 @@
--color-modal-button: #f9f8f7;
--color-modal-disabled: #e3e1df;
--color-global-yellow-100: #ffebb0;
+
+ /* 평균 자세 그래프 색깔 */
+ --color-dashboard-score: #ffe28a;
}
@theme inline {
@@ -166,6 +169,9 @@
--color-modal-disabled: var(--color-modal-disabled);
--color-sementic-brand-primary: var(--color-sementic-brand-primary);
--color-global-yellow-100: var(--color-global-yellow-100);
+
+ /* 평균 자세 그래프 색깔*/
+ --color-dashboard-score: var(--color-dashboard-score);
}
/* Dark mode colors */
@@ -246,4 +252,7 @@
--color-modal-button: #232323;
--color-modal-disabled: #2c2c2c;
--color-global-yellow-100: rgba(73, 55, 4, 0.5);
+
+ /* 평균 자세 그래프 색깔*/
+ --color-dashboard-score: #2c2c2c;
}