Skip to content

#26 세션별 기기 관리 기능 추가 및 히스토리 API 연동#27

Merged
yunho09 merged 2 commits intomainfrom
feature/#26
Mar 12, 2026
Merged

#26 세션별 기기 관리 기능 추가 및 히스토리 API 연동#27
yunho09 merged 2 commits intomainfrom
feature/#26

Conversation

@JAEJUN090210
Copy link
Copy Markdown
Member

@JAEJUN090210 JAEJUN090210 commented Mar 12, 2026

  • sessionId를 사용하여 세션별로 기기 목록 및 별명을 localStorage에 분리 저장합니다.
  • 기기 상세 페이지의 더미 데이터를 제거하고 실제 히스토리 API 연동을 통해 차트를 구현합니다.
  • 앱 진입 시 마지막 세션 경로를 유지하기 위한 리다이렉트 로직을 추가합니다.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multi-session navigation with automatic restoration of the last viewed session
    • Implemented per-session device management and data persistence
    • Dynamic device history charts now display real historical data instead of static placeholders
  • Bug Fixes

    • Fixed device data organization to properly scope to individual sessions rather than globally

- sessionId를 사용하여 세션별로 기기 목록 및 별명을 localStorage에 분리 저장합니다.
- 기기 상세 페이지의 더미 데이터를 제거하고 실제 히스토리 API 연동을 통해 차트를 구현합니다.
- 앱 진입 시 마지막 세션 경로를 유지하기 위한 리다이렉트 로직을 추가합니다.
@JAEJUN090210 JAEJUN090210 requested a review from yunho09 March 12, 2026 10:23
@JAEJUN090210 JAEJUN090210 self-assigned this Mar 12, 2026
- useState의 지연 초기화(lazy initialization)를 사용하여 로컬 스토리지에서 기기 목록을 직접 불러오도록 수정합니다.
- 불필요한 useEffect 호출을 제거하여 초기 렌더링 시의 데이터 정합성을 확보하고 불필요한 재렌더링을 방지합니다.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

This PR implements session-based routing and device history tracking. It adds localStorage-backed session persistence to the sidebar navigation, updates the routing system to support dynamic session IDs, and integrates API-driven device history fetching with dynamic chart visualization on the detail page. Device data is now stored and managed per-session rather than globally.

Changes

Cohort / File(s) Summary
Routing & Session Navigation
src/App.tsx, src/components/sidebar/sidebar.tsx
Adds session-based routing with localhost persistence. App now redirects to the last visited session stored in localStorage, and Sidebar persists session navigation items and the currently selected session path.
Device Data Management
src/hooks/useDeviceData.ts, src/pages/home/home.tsx
Refactors useDeviceData to accept an optional sessionId parameter and implement per-session device storage (deviceIds and deviceNames). Home component now reads sessionId from URL params and passes it to the hook.
Device History API & Types
src/apis/deviceHistory.ts, src/types/deviceHistory.ts
Adds new API module to fetch device history from backend and introduces DeviceHistoryPoint interface with time, deviceId, temperature, and risk fields.
Device History Integration
src/hooks/useDeviceHistory.ts, src/utils/deviceHistoryUtils.ts, src/pages/detail.tsx
Introduces new hook for querying device history via React Query and utility function to transform history data into chart-ready labels and datasets. Detail page now dynamically populates chart data from fetched history instead of static data.

Sequence Diagram

sequenceDiagram
    actor User
    participant App
    participant Storage as localStorage
    participant Sidebar
    participant DetailPage
    participant API as Backend API
    
    User->>Sidebar: Click session
    Sidebar->>Storage: Save lastSessionPath
    Sidebar->>App: Route to /:sessionId
    
    App->>Storage: Retrieve lastSessionPath
    App->>DetailPage: Render with sessionId
    
    DetailPage->>DetailPage: Extract serialNumber from route
    DetailPage->>API: Fetch device history
    API-->>DetailPage: Return DeviceHistoryPoint[]
    
    DetailPage->>DetailPage: mapHistoryToChartData()
    DetailPage->>User: Display dynamic chart
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • yunho09

Poem

🐰 Sessions bloom where paths once froze,
Each journey saved in local prose.
History charts now dance with light,
Per-session magic shining bright!
The detail whispers what once lay hidden— ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: adding per-session device management and integrating the history API, which directly aligns with the primary objectives of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#26

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/sidebar/sidebar.tsx (1)

74-82: ⚠️ Potential issue | 🟡 Minor

Potential ID collision when items are deleted in the future.

The ID generation logic uses prev.length + 1, which could produce duplicate IDs if section deletion is added later (e.g., delete "section-2", then add new → creates another "section-3"). Consider using a UUID or tracking a monotonically increasing counter in state/localStorage.

🛡️ Suggested approach using a counter
+const getNextSectionId = (items: SidebarNavItem[]): number => {
+  const ids = items.map((i) => parseInt(i.id.replace("section-", ""), 10) || 0);
+  return Math.max(0, ...ids) + 1;
+};
+
 const handleAddItem = useCallback(() => {
   setNavItems((prev) => {
-    const nextIndex = prev.length + 1;
+    const nextIndex = getNextSectionId(prev);
     const newId = `section-${nextIndex}`;
     const newLabel = `Section ${nextIndex}`;
     const newPath = `/${newId}`;
     return [...prev, { id: newId, label: newLabel, path: newPath }];
   });
 }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sidebar/sidebar.tsx` around lines 74 - 82, handleAddItem
currently builds IDs from prev.length+1 which can collide after deletions;
change the ID generation to use a monotonic counter or a UUID instead of list
length. Add a persistent counter (e.g., useRef or state like nextSectionIndex)
that you increment on each add and use to create newId/newLabel/newPath in
handleAddItem (or call crypto.randomUUID() if you prefer UUIDs) so IDs remain
unique even when items are removed. Update any code that assumes numeric
sequential IDs if necessary (e.g., label/path creation) to derive values from
the new counter/UUID.
🧹 Nitpick comments (2)
src/hooks/useDeviceData.ts (2)

31-37: Consider adding cleanup to avoid stale subscriptions on session change.

If sessionId changes and devices are reinitialized (per the fix above), old device subscriptions won't be cleaned up automatically. Adding a cleanup function would ensure proper resource management:

Proposed improvement
  useEffect(() => {
    if (!isConnected) return;
+   const subscribedIds: string[] = [];
    devices.forEach((device) => {
      subscribeDevice(device.id);
+     subscribedIds.push(device.id);
      console.log(`소켓 구독 ${device.id}`);
    });
+
+   return () => {
+     subscribedIds.forEach((id) => {
+       unsubscribeDevice(id);
+       console.log(`소켓 구독 해제 (cleanup) ${id}`);
+     });
+   };
- }, [devices, subscribeDevice, isConnected]);
+ }, [devices, subscribeDevice, unsubscribeDevice, isConnected]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useDeviceData.ts` around lines 31 - 37, The effect that subscribes
to devices (useEffect using isConnected, devices, subscribeDevice) needs a
cleanup to unsubscribe previous subscriptions when session/devices change;
implement a cleanup function that iterates the currently subscribed device ids
and calls unsubscribeDevice(device.id) (or add an unsubscribeDevice prop if
missing) and return that cleanup from the useEffect, and ensure
unsubscribeDevice (or a stable wrapper) is included in the dependency array so
stale subscriptions are removed on session changes.

78-80: Avoid side effects inside setState updater functions.

The localStorage.setItem call inside the setDevices callback is a side effect. React may invoke updater functions multiple times (e.g., in StrictMode), causing redundant writes. While functionally harmless here due to idempotency, it's cleaner to separate state updates from persistence.

Alternative approach using useEffect for persistence
+ useEffect(() => {
+   const ids = devices.map((device) => device.id);
+   localStorage.setItem(deviceIdsStorageKey, JSON.stringify(ids));
+ }, [devices, deviceIdsStorageKey]);

  const addDevice = (id: string) => {
    const savedNames: Record<string, string> = JSON.parse(
      localStorage.getItem(deviceNamesStorageKey) || "{}",
    );

    setDevices((prev) => {
      if (prev.some((device) => device.id === id)) {
        return prev;
      }

      const deviceName = savedNames[id] || `기기 ${prev.length + 1}`;

      const newDevice: Device = {
        id: id,
        name: deviceName,
        temperature: 0,
        warning: false,
        hasShownWarning: false,
      };

-     const newDevices = [...prev, newDevice];
-     const newIds = newDevices.map((device) => device.id);
-     localStorage.setItem(deviceIdsStorageKey, JSON.stringify(newIds));
-
-     return newDevices;
+     return [...prev, newDevice];
    });
  };

This would also simplify deleteDevice similarly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useDeviceData.ts` around lines 78 - 80, The state updater passed to
setDevices currently performs a side effect (localStorage.setItem) inside the
updater; move persistence out of the updater by removing localStorage writes
from the setDevices callback and instead add a useEffect that watches the
devices state (the same state variable updated by setDevices) and writes device
IDs to localStorage there (use the existing deviceIdsStorageKey and mapping
logic that builds newIds from devices). Also update deleteDevice to only call
setDevices (no localStorage call) so persistence is handled consistently by the
new useEffect.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/deviceHistory.ts`:
- Around line 6-8: The code uses const baseUrl =
import.meta.env.VITE_API_BASE_URL and builds the fetch URL with serialNumber
without checking baseUrl; add a guard that validates baseUrl is a non-empty
string before using it (e.g., in the same module or entry function that calls
the fetch), and if it's missing throw a descriptive Error like
"VITE_API_BASE_URL is not set" so the call to
fetch(`${baseUrl}/histories/all/${encodeURIComponent(serialNumber)}`) cannot
proceed with "undefined"; update any callers if you centralize the check into a
helper (e.g., getBaseUrl) and ensure serialNumber is still encoded as before.

In `@src/hooks/useDeviceData.ts`:
- Around line 10-29: The devices state is only initialized once by the useState
lazy initializer and doesn't update when effectiveSessionId changes; add a
useEffect that watches effectiveSessionId (or sessionId) and re-reads
localStorage keys deviceIdsStorageKey and deviceNamesStorageKey, constructs the
Device[] (same shape as the initializer) and calls setDevices to reinitialize
devices when the session changes; keep existing useState initializer for mount
but ensure the effect mirrors that logic and runs whenever effectiveSessionId
changes.

In `@src/pages/detail.tsx`:
- Around line 40-42: mapHistoryToChartData returns temperatureData but the
component only destructures labels and riskData, so temperatureData is never
passed to the chart; update the React.useMemo destructuring to include
temperatureData and add a second dataset to the chart data where the existing
dataset uses riskData and the new dataset uses temperatureData (matching the
chart config used in this file), referencing mapHistoryToChartData,
temperatureData, labels, and riskData to locate the code to change.

---

Outside diff comments:
In `@src/components/sidebar/sidebar.tsx`:
- Around line 74-82: handleAddItem currently builds IDs from prev.length+1 which
can collide after deletions; change the ID generation to use a monotonic counter
or a UUID instead of list length. Add a persistent counter (e.g., useRef or
state like nextSectionIndex) that you increment on each add and use to create
newId/newLabel/newPath in handleAddItem (or call crypto.randomUUID() if you
prefer UUIDs) so IDs remain unique even when items are removed. Update any code
that assumes numeric sequential IDs if necessary (e.g., label/path creation) to
derive values from the new counter/UUID.

---

Nitpick comments:
In `@src/hooks/useDeviceData.ts`:
- Around line 31-37: The effect that subscribes to devices (useEffect using
isConnected, devices, subscribeDevice) needs a cleanup to unsubscribe previous
subscriptions when session/devices change; implement a cleanup function that
iterates the currently subscribed device ids and calls
unsubscribeDevice(device.id) (or add an unsubscribeDevice prop if missing) and
return that cleanup from the useEffect, and ensure unsubscribeDevice (or a
stable wrapper) is included in the dependency array so stale subscriptions are
removed on session changes.
- Around line 78-80: The state updater passed to setDevices currently performs a
side effect (localStorage.setItem) inside the updater; move persistence out of
the updater by removing localStorage writes from the setDevices callback and
instead add a useEffect that watches the devices state (the same state variable
updated by setDevices) and writes device IDs to localStorage there (use the
existing deviceIdsStorageKey and mapping logic that builds newIds from devices).
Also update deleteDevice to only call setDevices (no localStorage call) so
persistence is handled consistently by the new useEffect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6e6da16-9b18-4dd3-836b-125a3fbd0b15

📥 Commits

Reviewing files that changed from the base of the PR and between 4c8ec80 and 34c41e1.

📒 Files selected for processing (9)
  • src/App.tsx
  • src/apis/deviceHistory.ts
  • src/components/sidebar/sidebar.tsx
  • src/hooks/useDeviceData.ts
  • src/hooks/useDeviceHistory.ts
  • src/pages/detail.tsx
  • src/pages/home/home.tsx
  • src/types/deviceHistory.ts
  • src/utils/deviceHistoryUtils.ts

@yunho09 yunho09 merged commit b5fb6d6 into main Mar 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants