Skip to content

Feature/policy api#60

Merged
haeni82 merged 4 commits into
developfrom
feature/policy-api
Mar 19, 2026
Merged

Feature/policy api#60
haeni82 merged 4 commits into
developfrom
feature/policy-api

Conversation

@haeni82
Copy link
Copy Markdown
Contributor

@haeni82 haeni82 commented Mar 19, 2026

이슈

  • closed #이슈번호

✔️ 체크리스트

  • : Merge할 브랜치를 확인해 주세요.

🔍 작업 내용

  • 슬라이더 min값 무시하고 0으로 고정
  • 바이트 함수 1미만일 시 MB로 표시
  • 회선 여러개일 경우에만 전화번호 뒷자리 표시(공유풀 사용량 그래프, 권한 양도 모달)
  • 알림 템플릿 수정

⚠️ 주의 사항 / 기타

Summary by CodeRabbit

릴리스 노트

  • 스타일

    • 온보딩 화면 UI 재디자인: 색상 및 장식 요소 업데이트
  • 버그 수정

    • 데이터 용량 표시 형식 통일 (GB/MB 일관성 개선)
    • 중복 이름 사용자에 한해 휴대폰 번호 마스킹 표시
    • 레이아웃 최소 높이 제약 조건 추가
  • 기능 개선

    • 정책 이관 모달 헤더 텍스트 변경
    • 데이터 조회 새로고침 간격 최적화

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

데이터 포맷팅 유틸리티를 도입하여 바이트-기가바이트 변환 로직을 통합하고, 알람 메시지 상수 키에 POLICY_ 접두사를 추가하며, 서비스 레이어에서 바이트 변환을 제거하고, 여러 UI 컴포넌트에서 새 포매터를 사용하도록 업데이트하며, 온보딩 모달의 색상과 이모지를 변경하고, 쿼리 재조회 간격을 조정합니다.

Changes

Cohort / File(s) Summary
Core Data Formatting Utility
src/utils/dataFormat.ts
새로운 formatDataLabel 함수 추가: 바이트를 GB(≥1) 또는 MB로 변환하여 포맷된 레이블 반환.
Service Layer
src/api/services/sharedPoolService.ts
getMainRemainingAmountgetMySharedPool에서 bytesToGb 변환 제거, response.data 직접 반환; API 호출 인자 서식 미세 조정.
Alarm System
src/constants/alarmMessages.ts, src/types/alarm.ts, src/utils/alarmUtils.ts
ALARM_MESSAGE_MAP 키에 POLICY_ 접두사 추가 (UPDATE_*POLICY_UPDATE_*, CREATE_*/DELETE_*POLICY_CREATE_*/POLICY_DELETE_*); NotificationValue 타입 추가 (message 필드 선택적); getAlarmMessage 함수 선언형으로 변경하고 메시지 우선순위 로직 업데이트.
Main Page Components
src/page/Main/MainPage.tsx, src/page/Main/components/DataBar.tsx, src/page/Main/components/PieChart.tsx
쿼리 refetchInterval 조정 (500ms → 10000ms 또는 51000000ms); formatDataLabel을 사용한 데이터 표시 업데이트.
Policy Page Components
src/page/Policy/Policy.tsx, src/page/Policy/components/DataRemainingCard.tsx, src/page/Policy/components/DataThresholdSlider.tsx, src/page/Policy/components/UserInfoCard.tsx, src/page/PolicyDetail/components/SliderCard.tsx
formatDataLabel 활용으로 포매팅 통합; 최소 슬라이더 값을 상수 0으로 변경; 라벨을 개행 포맷으로 업데이트; 버튼 폰트 크기 추가; 제한 금액 표시 로직 변경.
Log & Shared Data Pages
src/page/Log/LogPage.tsx, src/page/SharedData/SharedData.tsx, src/page/SharedData/components/DataTransferCard.tsx, src/page/SharedData/components/SharedPoolCard.tsx
formatDataLabel 도입으로 데이터 표시 포매팅 표준화; DataTransferCard에서 limitData 계산 로직을 바이트 정규화 방식으로 재작업; showUnit 조건부 로직 제거; 로그 효과 추가.
Onboarding Modal
src/components/Onboarding/OnboardingModal.tsx
스크린샷 경로를 모듈 임포트로 전환; 색상을 밝은 청록색/파스텔 톤으로 업데이트; 이모지 변경 (🏊→🐳, 🏐→🪸) 및 🦀 추가; 불투명도 및 애니메이션 범위 조정.
Common Components
src/components/common/Slider.tsx, src/page/Detail/components/AppUsageChart.tsx, src/page/Detail/components/DataBalance.tsx
formatDataLabel 도입으로 툴팁 및 사용량 값 표시 업데이트; 지역 포매팅 함수 제거; 부호 없는 값 가드 추가; 백분율 계산 다중행 삼항식으로 리팩토링.
Layout
src/layout/Layout.tsx
루트 컨테이너에 min-h-[600px] Tailwind 클래스 추가, 뷰포트 높이 제약 보강.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 제목이 너무 모호하고 변경 사항의 핵심을 구체적으로 설명하지 않습니다. PR에는 정책 API 뿐만 아니라 데이터 포맷팅, 슬라이더 설정, 알림 템플릿, UI 색상 변경 등 다양한 변경사항이 포함되어 있습니다. PR의 주요 변경사항을 더 구체적으로 반영하는 제목으로 수정하세요. 예: 'refactor: 데이터 포맷팅 통일 및 슬라이더, 알림 기능 개선'과 같은 형태가 더 명확합니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed 설명이 기본 템플릿 구조는 따르고 있으나 작업 내용이 간략하고 부분적으로만 작성되어 있습니다. 실제 변경사항(예: sharedPoolService 변경, OnboardingModal 색상/이미지 변경, formatDataLabel 추가, etc.)이 충분히 상세하게 설명되지 않았습니다.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/policy-api
📝 Coding Plan
  • Generate coding plan for human review comments

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: 5

Caution

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

⚠️ Outside diff range comments (4)
src/components/Onboarding/OnboardingModal.tsx (1)

320-338: ⚠️ Potential issue | 🟠 Major

하단 설명 문구의 대비가 부족합니다.

배경을 밝게 바꾼 상태에서 설명 문구와 커서를 밝은 계열로 두면 가독성이 크게 떨어집니다. 온보딩 핵심 안내 문구라서 더 어두운 전경색으로 맞추는 편이 안전합니다.

수정 예시
-            <p className="text-white text-[13px] text-center leading-relaxed min-h-[38px] mt-1">
+            <p className="text-sky-900 text-[13px] text-center leading-relaxed min-h-[38px] mt-1">
               {typedDesc}
               {titleDone && typedDesc.length < descText.length && (
-                <Cursor height="h-[13px]" color="bg-sky-300" />
+                <Cursor height="h-[13px]" color="bg-sky-700" />
               )}
             </p>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Onboarding/OnboardingModal.tsx` around lines 320 - 338, The
description paragraph and its typing Cursor are using light foreground colors
against a light gradient, reducing contrast; update the <p> that renders
{typedDesc} (currently className="text-white ...") to a darker, high-contrast
color (e.g. text-slate-800 or a hex like `#0f172a`) and change the Cursor color
prop used with the description (currently color="bg-sky-300") to a darker color
class (e.g. "bg-slate-800"); also check the title Cursor (Cursor with
color="bg-white") and switch it to a darker cursor color when the slide
background is the light gradient so both typedTitle and typedDesc have
sufficient contrast. Ensure you modify the <p> rendering typedDesc and the two
Cursor components in OnboardingModal to implement these color changes.
src/page/Log/LogPage.tsx (1)

31-34: ⚠️ Potential issue | 🟡 Minor

로그 항목 금액도 formatDataLabel로 통일해 주세요.

요약 카드만 새 formatter를 쓰고 있어서, 여기서는 1GB 미만 값이 여전히 0.xGB로 보입니다. 이번 PR의 표시 정책과 어긋납니다.

💡 제안된 수정
 function formatAmount(entry: HistoryEntry): string {
-  const gb = formatData(Math.abs(entry.amount));
-  return entry.eventType === "USAGE" ? `- ${gb}GB` : `+ ${gb}GB`;
+  const amount = formatDataLabel(Math.abs(entry.amount));
+  return entry.eventType === "USAGE" ? `- ${amount}` : `+ ${amount}`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Log/LogPage.tsx` around lines 31 - 34, The formatAmount function
currently uses formatData and appends "GB", causing sub-1GB values to display as
"0.xGB"; change it to call formatDataLabel(Math.abs(entry.amount)) instead and
use that returned label (which already includes proper unit formatting),
preserving the existing +/- prefix logic in formatAmount for HistoryEntry
eventType comparisons.
src/components/common/Slider.tsx (1)

9-13: ⚠️ Potential issue | 🟠 Major

gb prop 계약과 formatter 입력 단위가 맞지 않습니다.

SliderProps는 여전히 GB 값을 받는다고 설명하고 있는데, formatDataLabel은 bytes를 받습니다. 호출부를 그대로 두면 툴팁 값이 거의 항상 잘못 표시됩니다. GB 계약을 유지할 거면 여기서 bytes로 변환하고, bytes가 맞다면 prop 이름/주석도 같이 정리해 주세요.

💡 `gb` 계약을 유지하는 경우의 수정 예시
-                      {formatDataLabel(item.gb)}
+                      {formatDataLabel(item.gb * 1e9)}

Also applies to: 161-170

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

In `@src/components/common/Slider.tsx` around lines 9 - 13, The gb prop in
SliderProps is documented/used as gigabytes but formatDataLabel expects bytes,
causing incorrect tooltip values; update the code that builds the data passed to
Slider (or inside Slider's render) to convert gb to bytes before calling
formatDataLabel (multiply gb by 1024**3) if you intend to keep the gb contract,
or alternatively rename the prop to bytes (and update its comment) and pass
bytes directly to formatDataLabel; ensure you update all usages of gb (including
the data array and any code paths around formatDataLabel and isCurrent) so the
units are consistent.
src/api/services/sharedPoolService.ts (1)

32-37: ⚠️ Potential issue | 🟠 Major

getMainRemainingAmountgetMainRemainingAmountByLine 간의 단위 불일치.

두 메서드 모두 동일한 엔드포인트(/shared-pools/main/remaining-amount)를 호출하고 SharedPoolMainData 타입을 반환하지만, 이제 서로 다른 단위를 반환합니다:

  • getMainRemainingAmount: 바이트(raw bytes) 반환
  • getMainRemainingAmountByLine: GB로 변환하여 반환

동일한 타입이 서로 다른 단위를 나타내면 혼란을 야기하고, 소비자가 일관된 단위를 기대할 경우 버그로 이어질 수 있습니다. UI 레이어에서 바이트 포맷팅으로 전환하는 것이라면, getMainRemainingAmountByLine에서도 bytesToGb 변환을 제거하여 일관성을 유지해야 합니다.

🔧 일관성을 위해 `getMainRemainingAmountByLine`에서도 변환 제거 제안
   // [어드민] lineId로 공유풀 메인 데이터 조회
   getMainRemainingAmountByLine: async (lineId: number) => {
     const response = await apiClient.get<SharedPoolMainData>(
       "/shared-pools/main/remaining-amount",
       { params: { lineId } },
     );
-    const data = response.data;
-
-    // Bytes를 GB로 변환
-    return {
-      sharedPoolBaseData: bytesToGb(data.sharedPoolBaseData),
-      sharedPoolAdditionalData: bytesToGb(data.sharedPoolAdditionalData),
-      sharedPoolRemainingData: bytesToGb(data.sharedPoolRemainingData),
-      sharedPoolTotalData: bytesToGb(data.sharedPoolTotalData),
-    };
+    return response.data; // 변환 없이 바이트 그대로
   },

Also applies to: 84-98

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

In `@src/api/services/sharedPoolService.ts` around lines 32 - 37, The two methods
getMainRemainingAmount and getMainRemainingAmountByLine call the same endpoint
but return different units (raw bytes vs GB); update
getMainRemainingAmountByLine to stop converting with bytesToGb so both methods
return the same raw-byte values matching SharedPoolMainData (or, if you prefer
conversion, apply bytesToGb in both), ensuring the unit is consistent across
getMainRemainingAmount and getMainRemainingAmountByLine and update any related
comments/types accordingly.
🧹 Nitpick comments (3)
src/page/Policy/Policy.tsx (1)

264-270: 필터 범위에 대한 선택적 개선 사항.

현재 로직은 전체 familyMembers 배열을 기준으로 중복 여부를 확인합니다. 현재 사용자가 제외된 상태에서도 전체 배열을 검사하므로, 표시 목록에는 동일 이름이 하나뿐이어도 전화번호 뒷자리가 표시될 수 있습니다.

예: 현재 사용자 "김철수"가 제외되고, 다른 회원 "김철수" 한 명만 표시되는 경우에도 뒷자리가 표시됨.

기능적으로 문제가 되지는 않지만, 더 정확한 표시를 원한다면 필터링된 목록을 기준으로 검사할 수 있습니다.

♻️ 선택적 개선 제안
// 컴포넌트 상단에서 필터링된 목록을 미리 계산
const displayedMembers = familyMembers.filter(
  (member) => member.lineId !== userData?.lineId
);

// JSX에서 사용
{displayedMembers.filter(
  (m) => m.userName === member.userName,
).length > 1 && (
  <span className="text-xs font-normal text-gray-400 ml-1">
    ({member.phone.slice(-4)})
  </span>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Policy/Policy.tsx` around lines 264 - 270, The current
duplicate-name check uses the full familyMembers array which can include the
current user; compute a filtered list of visible members (e.g., displayedMembers
= familyMembers.filter(m => m.lineId !== userData?.lineId)) and use that list
when checking duplicates (replace familyMembers.filter(...).length > 1 with
displayedMembers.filter(m => m.userName === member.userName).length > 1) so the
phone tail shown by member.phone.slice(-4) is based only on displayed members.
src/page/SharedData/SharedData.tsx (1)

30-32: 디버그 로그는 머지 전에 제거하는 편이 좋겠습니다.

myData가 갱신될 때마다 콘솔이 찍혀서 refetch 시 로그가 계속 쌓이고, 사용자별 공유 데이터 상태도 브라우저 콘솔에 남습니다. 의도된 관측용 코드가 아니라면 빼는 게 안전합니다.

정리 예시
-  useEffect(() => {
-    console.log("myData:", myData);
-  }, [myData]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/SharedData/SharedData.tsx` around lines 30 - 32, Remove the debug
console logging in the useEffect that watches myData in SharedData.tsx: locate
the useEffect referencing myData and delete the console.log("myData:", myData)
(or guard it behind a development-only flag if you need runtime debugging), so
browser consoles no longer receive user data on every refetch; keep the effect
only if it performs necessary side-effects beyond logging.
src/api/services/sharedPoolService.ts (1)

40-58: 서비스 전체의 변환 전략 일관성 검토 권장.

현재 서비스 내 메서드들이 혼재된 전략을 사용합니다:

  • 바이트 반환: getMainRemainingAmount, getMySharedPool
  • GB 변환: getSharedPools, getSharedPoolsByFamilyId, getMainRemainingAmountByLine

PR 목표가 UI 레이어에서 formatDataLabel을 통한 포맷팅으로 전환하는 것이라면, 모든 메서드에서 일관되게 바이트를 반환하거나, 명확한 네이밍 컨벤션(예: getRaw..., getFormatted...)을 사용하는 것이 좋습니다.

Also applies to: 61-81

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

In `@src/api/services/sharedPoolService.ts` around lines 40 - 58, The current
service mixes return units: getMainRemainingAmount and getMySharedPool return
raw bytes while getSharedPools, getSharedPoolsByFamilyId, and
getMainRemainingAmountByLine convert bytes to GB; to make behavior consistent
with the UI-driven formatting approach, change getSharedPools (and similarly
getSharedPoolsByFamilyId and getMainRemainingAmountByLine) to return raw byte
values (remove bytesToGb conversions) so all methods return bytes, or
alternatively rename these methods to explicitly indicate GB output (e.g.,
getSharedPoolsInGb) if you prefer keeping GB conversion—update only the service
methods (getSharedPools, getSharedPoolsByFamilyId, getMainRemainingAmountByLine)
and ensure their return shapes match the existing byte-based methods
(getMainRemainingAmount, getMySharedPool).
🤖 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/components/Onboarding/OnboardingModal.tsx`:
- Around line 6-23: The imports for screenshot2 through screenshot8 all point to
screenshot1.png, causing all onboarding slides to show the same image; update
the import statements (screenshot2, screenshot3, ..., screenshot8) to import
their respective asset files (e.g., screenshot2.png, screenshot3.png, etc.) and
ensure the SCREENSHOT_PATHS mapping still references those variables (keys 2–9
-> screenshot1..screenshot8) so each slide renders its correct image.

In `@src/constants/alarmMessages.ts`:
- Around line 23-42: ALARM_MESSAGE_MAP uses keys with a POLICY_ prefix (e.g.,
POLICY_UPDATE_SHAREDATA_LIMIT, POLICY_CREATE_IMMEDIATE_BLOCK) that do not match
the unprefixed keys produced/used by TYPE_TO_ALARM_CODE and read by
getAlarmMessage (which does ALARM_MESSAGE_MAP[value.type]); update
ALARM_MESSAGE_MAP to use the same unprefixed keys (e.g., UPDATE_SHAREDATA_LIMIT,
CREATE_IMMEDIATE_BLOCK, DELETE_DAYDATA_LIMIT, UPDATE_REPEAT_BLOCK,
UPDATE_IMMEDIATE_BLOCK, CREATE_REPEAT_BLOCK, DELETE_REPEAT_BLOCK, etc.) or
alternatively add duplicate unprefixed entries that map to the same messages,
and make the naming consistent for UPDATE/CREATE/DELETE of
repeat/immediate/data-limit entries so getAlarmMessage(value.type) finds the
correct message.

In `@src/page/Log/LogPage.tsx`:
- Around line 162-165: The total data label is rendered with an extra "GB"
because formatDataLabel(total) already returns a unit-suffixed string; update
the Render in LogPage (around formatDataLabel(remaining) /
formatDataLabel(total)) to stop appending the literal "GB" — either remove the
trailing "GB" after formatDataLabel(total) or call a variant of formatDataLabel
that returns a unit-less string if available (reference symbols:
formatDataLabel, remaining, total, LogPage).

In `@src/page/SharedData/components/DataTransferCard.tsx`:
- Around line 165-181: The limit value (limitData → limitDisplay) is rendered
without units while contributedGB includes units, causing inconsistent UI;
update the DataTransferCard rendering so limitDisplay includes the unit string
(e.g., "GB" or "MB") before passing to DataStatItem or set showUnit=true and
pass a formatted string; specifically modify how limitData is converted to
limitDisplay in DataTransferCard (where limitDisplay is computed) so the value
passed into the DataStatItem for label "한도 데이터" matches the format used for
contributedGB.

In `@src/utils/dataFormat.ts`:
- Around line 20-24: The function formatDataLabel currently rounds the GB value
before checking the 1GB threshold which can cause values just under 1GB to be
labeled "1GB"; change formatDataLabel to first compute the exact GB value (e.g.,
gbExact = bytes / 1e9) and use gbExact >= 1 for the branch decision, and only
then format/round the displayed GB value (e.g., round to two decimals) when
returning `${...}GB`; otherwise fall back to MB formatting (keep existing mb
rounding logic). Ensure you update the logic in the formatDataLabel function
accordingly.

---

Outside diff comments:
In `@src/api/services/sharedPoolService.ts`:
- Around line 32-37: The two methods getMainRemainingAmount and
getMainRemainingAmountByLine call the same endpoint but return different units
(raw bytes vs GB); update getMainRemainingAmountByLine to stop converting with
bytesToGb so both methods return the same raw-byte values matching
SharedPoolMainData (or, if you prefer conversion, apply bytesToGb in both),
ensuring the unit is consistent across getMainRemainingAmount and
getMainRemainingAmountByLine and update any related comments/types accordingly.

In `@src/components/common/Slider.tsx`:
- Around line 9-13: The gb prop in SliderProps is documented/used as gigabytes
but formatDataLabel expects bytes, causing incorrect tooltip values; update the
code that builds the data passed to Slider (or inside Slider's render) to
convert gb to bytes before calling formatDataLabel (multiply gb by 1024**3) if
you intend to keep the gb contract, or alternatively rename the prop to bytes
(and update its comment) and pass bytes directly to formatDataLabel; ensure you
update all usages of gb (including the data array and any code paths around
formatDataLabel and isCurrent) so the units are consistent.

In `@src/components/Onboarding/OnboardingModal.tsx`:
- Around line 320-338: The description paragraph and its typing Cursor are using
light foreground colors against a light gradient, reducing contrast; update the
<p> that renders {typedDesc} (currently className="text-white ...") to a darker,
high-contrast color (e.g. text-slate-800 or a hex like `#0f172a`) and change the
Cursor color prop used with the description (currently color="bg-sky-300") to a
darker color class (e.g. "bg-slate-800"); also check the title Cursor (Cursor
with color="bg-white") and switch it to a darker cursor color when the slide
background is the light gradient so both typedTitle and typedDesc have
sufficient contrast. Ensure you modify the <p> rendering typedDesc and the two
Cursor components in OnboardingModal to implement these color changes.

In `@src/page/Log/LogPage.tsx`:
- Around line 31-34: The formatAmount function currently uses formatData and
appends "GB", causing sub-1GB values to display as "0.xGB"; change it to call
formatDataLabel(Math.abs(entry.amount)) instead and use that returned label
(which already includes proper unit formatting), preserving the existing +/-
prefix logic in formatAmount for HistoryEntry eventType comparisons.

---

Nitpick comments:
In `@src/api/services/sharedPoolService.ts`:
- Around line 40-58: The current service mixes return units:
getMainRemainingAmount and getMySharedPool return raw bytes while
getSharedPools, getSharedPoolsByFamilyId, and getMainRemainingAmountByLine
convert bytes to GB; to make behavior consistent with the UI-driven formatting
approach, change getSharedPools (and similarly getSharedPoolsByFamilyId and
getMainRemainingAmountByLine) to return raw byte values (remove bytesToGb
conversions) so all methods return bytes, or alternatively rename these methods
to explicitly indicate GB output (e.g., getSharedPoolsInGb) if you prefer
keeping GB conversion—update only the service methods (getSharedPools,
getSharedPoolsByFamilyId, getMainRemainingAmountByLine) and ensure their return
shapes match the existing byte-based methods (getMainRemainingAmount,
getMySharedPool).

In `@src/page/Policy/Policy.tsx`:
- Around line 264-270: The current duplicate-name check uses the full
familyMembers array which can include the current user; compute a filtered list
of visible members (e.g., displayedMembers = familyMembers.filter(m => m.lineId
!== userData?.lineId)) and use that list when checking duplicates (replace
familyMembers.filter(...).length > 1 with displayedMembers.filter(m =>
m.userName === member.userName).length > 1) so the phone tail shown by
member.phone.slice(-4) is based only on displayed members.

In `@src/page/SharedData/SharedData.tsx`:
- Around line 30-32: Remove the debug console logging in the useEffect that
watches myData in SharedData.tsx: locate the useEffect referencing myData and
delete the console.log("myData:", myData) (or guard it behind a development-only
flag if you need runtime debugging), so browser consoles no longer receive user
data on every refetch; keep the effect only if it performs necessary
side-effects beyond logging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1cf71afb-1385-4aea-a456-e976aefc70d9

📥 Commits

Reviewing files that changed from the base of the PR and between c87f08a and 2d03540.

📒 Files selected for processing (22)
  • src/api/services/sharedPoolService.ts
  • src/components/Onboarding/OnboardingModal.tsx
  • src/components/common/Slider.tsx
  • src/constants/alarmMessages.ts
  • src/layout/Layout.tsx
  • src/page/Detail/components/AppUsageChart.tsx
  • src/page/Detail/components/DataBalance.tsx
  • src/page/Log/LogPage.tsx
  • src/page/Main/MainPage.tsx
  • src/page/Main/components/DataBar.tsx
  • src/page/Main/components/PieChart.tsx
  • src/page/Policy/Policy.tsx
  • src/page/Policy/components/DataRemainingCard.tsx
  • src/page/Policy/components/DataThresholdSlider.tsx
  • src/page/Policy/components/UserInfoCard.tsx
  • src/page/PolicyDetail/components/SliderCard.tsx
  • src/page/SharedData/SharedData.tsx
  • src/page/SharedData/components/DataTransferCard.tsx
  • src/page/SharedData/components/SharedPoolCard.tsx
  • src/types/alarm.ts
  • src/utils/alarmUtils.ts
  • src/utils/dataFormat.ts

Comment on lines +6 to +23
import screenshot1 from "@/assets/img/screenshot1.png";
import screenshot2 from "@/assets/img/screenshot1.png";
import screenshot3 from "@/assets/img/screenshot1.png";
import screenshot4 from "@/assets/img/screenshot1.png";
import screenshot5 from "@/assets/img/screenshot1.png";
import screenshot6 from "@/assets/img/screenshot1.png";
import screenshot7 from "@/assets/img/screenshot1.png";
import screenshot8 from "@/assets/img/screenshot1.png";

const SCREENSHOT_PATHS: Record<number, string> = {
2: "/src/assets/img/screenshot1.png",
3: "/src/assets/img/screenshot2.png",
4: "/src/assets/img/screenshot3.png",
5: "/src/assets/img/screenshot4.png",
6: "/src/assets/img/screenshot5.png",
7: "/src/assets/img/screenshot6.png",
8: "/src/assets/img/screenshot7.png",
9: "/src/assets/img/screenshot8.png",
2: screenshot1,
3: screenshot2,
4: screenshot3,
5: screenshot4,
6: screenshot5,
7: screenshot6,
8: screenshot7,
9: screenshot8,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

모든 온보딩 스크린샷이 같은 이미지로 표시됩니다.

screenshot2screenshot8도 전부 screenshot1.png를 import하고 있어서, 29번 슬라이드가 서로 다른 화면이 아니라 동일한 이미지를 렌더링합니다. 각 변수에 맞는 자산으로 연결해야 합니다.

수정 예시
 import screenshot1 from "@/assets/img/screenshot1.png";
-import screenshot2 from "@/assets/img/screenshot1.png";
-import screenshot3 from "@/assets/img/screenshot1.png";
-import screenshot4 from "@/assets/img/screenshot1.png";
-import screenshot5 from "@/assets/img/screenshot1.png";
-import screenshot6 from "@/assets/img/screenshot1.png";
-import screenshot7 from "@/assets/img/screenshot1.png";
-import screenshot8 from "@/assets/img/screenshot1.png";
+import screenshot2 from "@/assets/img/screenshot2.png";
+import screenshot3 from "@/assets/img/screenshot3.png";
+import screenshot4 from "@/assets/img/screenshot4.png";
+import screenshot5 from "@/assets/img/screenshot5.png";
+import screenshot6 from "@/assets/img/screenshot6.png";
+import screenshot7 from "@/assets/img/screenshot7.png";
+import screenshot8 from "@/assets/img/screenshot8.png";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import screenshot1 from "@/assets/img/screenshot1.png";
import screenshot2 from "@/assets/img/screenshot1.png";
import screenshot3 from "@/assets/img/screenshot1.png";
import screenshot4 from "@/assets/img/screenshot1.png";
import screenshot5 from "@/assets/img/screenshot1.png";
import screenshot6 from "@/assets/img/screenshot1.png";
import screenshot7 from "@/assets/img/screenshot1.png";
import screenshot8 from "@/assets/img/screenshot1.png";
const SCREENSHOT_PATHS: Record<number, string> = {
2: "/src/assets/img/screenshot1.png",
3: "/src/assets/img/screenshot2.png",
4: "/src/assets/img/screenshot3.png",
5: "/src/assets/img/screenshot4.png",
6: "/src/assets/img/screenshot5.png",
7: "/src/assets/img/screenshot6.png",
8: "/src/assets/img/screenshot7.png",
9: "/src/assets/img/screenshot8.png",
2: screenshot1,
3: screenshot2,
4: screenshot3,
5: screenshot4,
6: screenshot5,
7: screenshot6,
8: screenshot7,
9: screenshot8,
import screenshot1 from "@/assets/img/screenshot1.png";
import screenshot2 from "@/assets/img/screenshot2.png";
import screenshot3 from "@/assets/img/screenshot3.png";
import screenshot4 from "@/assets/img/screenshot4.png";
import screenshot5 from "@/assets/img/screenshot5.png";
import screenshot6 from "@/assets/img/screenshot6.png";
import screenshot7 from "@/assets/img/screenshot7.png";
import screenshot8 from "@/assets/img/screenshot8.png";
const SCREENSHOT_PATHS: Record<number, string> = {
2: screenshot1,
3: screenshot2,
4: screenshot3,
5: screenshot4,
6: screenshot5,
7: screenshot6,
8: screenshot7,
9: screenshot8,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Onboarding/OnboardingModal.tsx` around lines 6 - 23, The
imports for screenshot2 through screenshot8 all point to screenshot1.png,
causing all onboarding slides to show the same image; update the import
statements (screenshot2, screenshot3, ..., screenshot8) to import their
respective asset files (e.g., screenshot2.png, screenshot3.png, etc.) and ensure
the SCREENSHOT_PATHS mapping still references those variables (keys 2–9 ->
screenshot1..screenshot8) so each slide renders its correct image.

Comment on lines +23 to +42
POLICY_UPDATE_SHAREDATA_LIMIT: "월 공유 데이터 사용량 제한이 수정되었습니다.",
POLICY_UPDATE_DAYDATA_LIMIT: "일 개인 데이터 사용량 제한이 수정되었습니다.",
POLICY_UPDATE_APP_USAGE_LIMIT: "앱 데이터의 사용량 제한이 수정되었습니다.",
POLICY_UPDATE_DATA_SPEED_LIMIT: "앱 데이터의 속도 제한이 수정되었습니다.",
ACTIVATE_POLICY: "새로운 정책이 활성화되었습니다.",
DEACTIVATE_POLICY: "새로운 정책이 비활성화되었습니다.",

// Policy_Limit
CREATE_REPEAT_BLOCK: "반복적 차단 정책이 생성되었습니다.",
DELETE_REPEAT_BLOCK: "반복적 차단 정책이 삭제되었습니다.",
CREATE_IMMEDIATE_BLOCK: "즉시 차단 정책이 생성되었습니다.",
DELETE_IMMEDIATE_BLOCK: "즉시 차단 정책이 삭제되었습니다.",
CREATE_DAYDATA_LIMIT: "일 개인 데이터 사용량 제한이 생성되었습니다.",
DELETE_DAYDATA_LIMIT: "일 개인 데이터 사용량 제한이 삭제되었습니다.",
CREATE_SHAREDATA_LIMIT: "월 공유 데이터 사용량 제한이 생성되었습니다.",
DELETE_SHAREDATA_LIMIT: "월 공유 데이터 사용량 제한이 삭제되었습니다.",
CREATE_APP_USAGE_LIMIT: "앱 데이터의 사용량 제한이 생성되었습니다.",
DELETE_APP_USAGE_LIMIT: "앱 데이터의 사용량 제한이 삭제되었습니다.",
CREATE_DATA_SPEED_LIMIT: "앱 데이터의 속도 제한이 생성되었습니다.",
DELETE_DATA_SPEED_LIMIT: "앱 데이터의 속도 제한이 삭제되었습니다.",
POLICY_CREATE_IMMEDIATE_BLOCK: "즉시 차단 정책이 생성되었습니다.",
POLICY_DELETE_IMMEDIATE_BLOCK: "즉시 차단 정책이 삭제되었습니다.",
POLICY_CREATE_DAYDATA_LIMIT: "일 개인 데이터 사용량 제한이 생성되었습니다.",
POLICY_DELETE_DAYDATA_LIMIT: "일 개인 데이터 사용량 제한이 삭제되었습니다.",
POLICY_CREATE_SHAREDATA_LIMIT: "월 공유 데이터 사용량 제한이 생성되었습니다.",
POLICY_DELETE_SHAREDATA_LIMIT: "월 공유 데이터 사용량 제한이 삭제되었습니다.",
POLICY_CREATE_APP_USAGE_LIMIT: "앱 데이터의 사용량 제한이 생성되었습니다.",
POLICY_DELETE_APP_USAGE_LIMIT: "앱 데이터의 사용량 제한이 삭제되었습니다.",
POLICY_CREATE_DATA_SPEED_LIMIT: "앱 데이터의 속도 제한이 생성되었습니다.",
POLICY_DELETE_DATA_SPEED_LIMIT: "앱 데이터의 속도 제한이 삭제되었습니다.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: ALARM_MESSAGE_MAP과 TYPE_TO_ALARM_CODE 간 키 불일치 확인

echo "=== ALARM_MESSAGE_MAP keys ==="
rg -n 'POLICY_UPDATE_|POLICY_CREATE_|POLICY_DELETE_' src/constants/alarmMessages.ts

echo ""
echo "=== TYPE_TO_ALARM_CODE keys (expecting same prefix) ==="
rg -n 'UPDATE_SHAREDATA_LIMIT|UPDATE_DAYDATA_LIMIT|UPDATE_APP_USAGE_LIMIT|UPDATE_DATA_SPEED_LIMIT|CREATE_IMMEDIATE_BLOCK|DELETE_IMMEDIATE_BLOCK|CREATE_DAYDATA_LIMIT|DELETE_DAYDATA_LIMIT|CREATE_SHAREDATA_LIMIT|DELETE_SHAREDATA_LIMIT|CREATE_APP_USAGE_LIMIT|DELETE_APP_USAGE_LIMIT|CREATE_DATA_SPEED_LIMIT|DELETE_DATA_SPEED_LIMIT' src/constants/alarmMessages.ts

Repository: pooli-dev/pooli-fe

Length of output: 1787


🏁 Script executed:

cat -n src/constants/alarmMessages.ts | head -100

Repository: pooli-dev/pooli-fe

Length of output: 4406


🏁 Script executed:

rg -n "getAlarmMessage|ALARM_MESSAGE_MAP\[" --type ts --type tsx | head -30

Repository: pooli-dev/pooli-fe

Length of output: 89


🏁 Script executed:

rg -n "getAlarmMessage|ALARM_MESSAGE_MAP\[" --type ts

Repository: pooli-dev/pooli-fe

Length of output: 518


🏁 Script executed:

cat -n src/utils/alarmUtils.ts

Repository: pooli-dev/pooli-fe

Length of output: 807


ALARM_MESSAGE_MAPPOLICY_ 접두사가 TYPE_TO_ALARM_CODE의 키와 불일치하여 알림 메시지가 표시되지 않습니다.

TYPE_TO_ALARM_CODEUPDATE_SHAREDATA_LIMIT, CREATE_IMMEDIATE_BLOCK, DELETE_DAYDATA_LIMIT 등 접두사 없는 키를 사용하지만, ALARM_MESSAGE_MAP은 동일한 개념의 키에 POLICY_ 접두사를 붙였습니다(POLICY_UPDATE_SHAREDATA_LIMIT, POLICY_CREATE_IMMEDIATE_BLOCK, POLICY_DELETE_DAYDATA_LIMIT 등).

getAlarmMessage(src/utils/alarmUtils.ts:15)는 ALARM_MESSAGE_MAP[value.type]으로 직접 조회하기 때문에, 백엔드에서 UPDATE_SHAREDATA_LIMIT 같은 unprefixed 키를 전송하면 매칭되지 않아 폴백 메시지("알림이 도착했습니다.")만 표시됩니다. 이는 UPDATE 계열 4개, CREATE/DELETE IMMEDIATE_BLOCK 2개, CREATE/DELETE 데이터 제한 8개 등 총 14개 이상의 알림 메시지에 영향을 미칩니다.

추가로 UPDATE_REPEAT_BLOCK, UPDATE_IMMEDIATE_BLOCK, CREATE_REPEAT_BLOCK, DELETE_REPEAT_BLOCK은 접두사가 없어 명명 규칙이 일관되지 않습니다.

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

In `@src/constants/alarmMessages.ts` around lines 23 - 42, ALARM_MESSAGE_MAP uses
keys with a POLICY_ prefix (e.g., POLICY_UPDATE_SHAREDATA_LIMIT,
POLICY_CREATE_IMMEDIATE_BLOCK) that do not match the unprefixed keys
produced/used by TYPE_TO_ALARM_CODE and read by getAlarmMessage (which does
ALARM_MESSAGE_MAP[value.type]); update ALARM_MESSAGE_MAP to use the same
unprefixed keys (e.g., UPDATE_SHAREDATA_LIMIT, CREATE_IMMEDIATE_BLOCK,
DELETE_DAYDATA_LIMIT, UPDATE_REPEAT_BLOCK, UPDATE_IMMEDIATE_BLOCK,
CREATE_REPEAT_BLOCK, DELETE_REPEAT_BLOCK, etc.) or alternatively add duplicate
unprefixed entries that map to the same messages, and make the naming consistent
for UPDATE/CREATE/DELETE of repeat/immediate/data-limit entries so
getAlarmMessage(value.type) finds the correct message.

Comment thread src/page/Log/LogPage.tsx
Comment on lines +162 to 165
{formatDataLabel(remaining)}
<span className="text-base font-normal text-gray-400 ml-1">
/ {total}GB
/ {formatDataLabel(total)}GB
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

총량 라벨에 단위가 한 번 더 붙습니다.

formatDataLabel(total)가 이미 단위를 포함하므로 현재 문자열은 500MBGB1.2GBGB처럼 렌더링됩니다.

💡 제안된 수정
                 <span className="text-base font-normal text-gray-400 ml-1">
-                  / {formatDataLabel(total)}GB
+                  / {formatDataLabel(total)}
                 </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{formatDataLabel(remaining)}
<span className="text-base font-normal text-gray-400 ml-1">
/ {total}GB
/ {formatDataLabel(total)}GB
</span>
{formatDataLabel(remaining)}
<span className="text-base font-normal text-gray-400 ml-1">
/ {formatDataLabel(total)}
</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/Log/LogPage.tsx` around lines 162 - 165, The total data label is
rendered with an extra "GB" because formatDataLabel(total) already returns a
unit-suffixed string; update the Render in LogPage (around
formatDataLabel(remaining) / formatDataLabel(total)) to stop appending the
literal "GB" — either remove the trailing "GB" after formatDataLabel(total) or
call a variant of formatDataLabel that returns a unit-less string if available
(reference symbols: formatDataLabel, remaining, total, LogPage).

Comment on lines 165 to +181
const limitDisplay = limitData.toString();

return (
<div className="mb-8">
<div className="flex justify-between items-start mb-3">
<DataStatItem label="공유한 데이터" value={contributedGB} color={COLORS.primary} showUnit={true} />
<DataStatItem label="한도 데이터" value={limitDisplay} color={COLORS.textGray} showUnit={true} />
<DataStatItem
label="공유한 데이터"
value={contributedGB}
color={COLORS.primary}
showUnit={true}
/>
<DataStatItem
label="한도 데이터"
value={limitDisplay}
color={COLORS.textGray}
showUnit={true}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

한도 데이터가 단위 없이 렌더링됩니다.

공유한 데이터GB/MB가 포함된 문자열인데, 여기서는 limitData를 숫자만 넘겨서 현재 UI가 50처럼 보입니다. 같은 카드 안에서 표기 규칙이 달라져서 헷갈리니 이 값도 단위를 포함해 주세요.

수정 예시
 function DataStats({ contributedGB, limitData }: DataStatsProps) {
-  const limitDisplay = limitData.toString();
+  const limitDisplay = `${limitData}GB`;

   return (
     <div className="mb-8">
       <div className="flex justify-between items-start mb-3">
-        <DataStatItem
-          label="공유한 데이터"
-          value={contributedGB}
-          color={COLORS.primary}
-          showUnit={true}
-        />
-        <DataStatItem
-          label="한도 데이터"
-          value={limitDisplay}
-          color={COLORS.textGray}
-          showUnit={true}
-        />
+        <DataStatItem label="공유한 데이터" value={contributedGB} color={COLORS.primary} />
+        <DataStatItem label="한도 데이터" value={limitDisplay} color={COLORS.textGray} />
       </div>
     </div>
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const limitDisplay = limitData.toString();
return (
<div className="mb-8">
<div className="flex justify-between items-start mb-3">
<DataStatItem label="공유한 데이터" value={contributedGB} color={COLORS.primary} showUnit={true} />
<DataStatItem label="한도 데이터" value={limitDisplay} color={COLORS.textGray} showUnit={true} />
<DataStatItem
label="공유한 데이터"
value={contributedGB}
color={COLORS.primary}
showUnit={true}
/>
<DataStatItem
label="한도 데이터"
value={limitDisplay}
color={COLORS.textGray}
showUnit={true}
/>
const limitDisplay = `${limitData}GB`;
return (
<div className="mb-8">
<div className="flex justify-between items-start mb-3">
<DataStatItem label="공유한 데이터" value={contributedGB} color={COLORS.primary} />
<DataStatItem label="한도 데이터" value={limitDisplay} color={COLORS.textGray} />
</div>
</div>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/page/SharedData/components/DataTransferCard.tsx` around lines 165 - 181,
The limit value (limitData → limitDisplay) is rendered without units while
contributedGB includes units, causing inconsistent UI; update the
DataTransferCard rendering so limitDisplay includes the unit string (e.g., "GB"
or "MB") before passing to DataStatItem or set showUnit=true and pass a
formatted string; specifically modify how limitData is converted to limitDisplay
in DataTransferCard (where limitDisplay is computed) so the value passed into
the DataStatItem for label "한도 데이터" matches the format used for contributedGB.

Comment thread src/utils/dataFormat.ts
Comment on lines +20 to +24
export const formatDataLabel = (bytes: number): string => {
const gb = Math.round((bytes / 1e9) * 100) / 100;
if (gb >= 1) return `${gb}GB`;
const mb = Math.round(bytes / 1e6);
return `${mb}MB`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

1GB 경계값 판정이 반올림 때문에 뒤집힙니다.

지금은 GB 값을 먼저 반올림한 뒤 >= 1을 검사해서, 실제로는 1GB 미만인 값도 "1GB"로 표시될 수 있습니다. PR 설명대로라면 경계 판정은 반올림 전 값으로 해야 합니다.

수정 예시
 export const formatDataLabel = (bytes: number): string => {
-  const gb = Math.round((bytes / 1e9) * 100) / 100;
-  if (gb >= 1) return `${gb}GB`;
+  const rawGb = bytes / 1e9;
+  if (rawGb >= 1) {
+    return `${Math.round(rawGb * 100) / 100}GB`;
+  }
   const mb = Math.round(bytes / 1e6);
   return `${mb}MB`;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatDataLabel = (bytes: number): string => {
const gb = Math.round((bytes / 1e9) * 100) / 100;
if (gb >= 1) return `${gb}GB`;
const mb = Math.round(bytes / 1e6);
return `${mb}MB`;
export const formatDataLabel = (bytes: number): string => {
const rawGb = bytes / 1e9;
if (rawGb >= 1) {
return `${Math.round(rawGb * 100) / 100}GB`;
}
const mb = Math.round(bytes / 1e6);
return `${mb}MB`;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/dataFormat.ts` around lines 20 - 24, The function formatDataLabel
currently rounds the GB value before checking the 1GB threshold which can cause
values just under 1GB to be labeled "1GB"; change formatDataLabel to first
compute the exact GB value (e.g., gbExact = bytes / 1e9) and use gbExact >= 1
for the branch decision, and only then format/round the displayed GB value
(e.g., round to two decimals) when returning `${...}GB`; otherwise fall back to
MB formatting (keep existing mb rounding logic). Ensure you update the logic in
the formatDataLabel function accordingly.

@haeni82 haeni82 merged commit e1c2db8 into develop Mar 19, 2026
2 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.

1 participant