From 3e3725751083cc9a52628dea44913bab96fd3796 Mon Sep 17 00:00:00 2001 From: sumin0423 Date: Thu, 12 Nov 2026 23:47:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=88=98=EC=9E=85=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9E=85=EA=B8=88=20=ED=98=84=ED=99=A9=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/income-card.svg | 26 ++++++ assets/images/income-gradient.svg | 19 ++++ docs/income-status.md | 27 ++++++ src/app/(tabs)/income.tsx | 17 +++- src/app/dev/income.tsx | 13 +++ src/features/income/IncomeScreen.tsx | 132 +++++++++++++++++++++++++++ src/features/income/previewData.ts | 45 +++++++++ src/features/income/styles.ts | 101 ++++++++++++++++++++ src/features/income/types.ts | 27 ++++++ 9 files changed, 405 insertions(+), 2 deletions(-) create mode 100644 assets/images/income-card.svg create mode 100644 assets/images/income-gradient.svg create mode 100644 docs/income-status.md create mode 100644 src/app/dev/income.tsx create mode 100644 src/features/income/IncomeScreen.tsx create mode 100644 src/features/income/previewData.ts create mode 100644 src/features/income/styles.ts create mode 100644 src/features/income/types.ts diff --git a/assets/images/income-card.svg b/assets/images/income-card.svg new file mode 100644 index 0000000..8033552 --- /dev/null +++ b/assets/images/income-card.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/income-gradient.svg b/assets/images/income-gradient.svg new file mode 100644 index 0000000..332a7ce --- /dev/null +++ b/assets/images/income-gradient.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/income-status.md b/docs/income-status.md new file mode 100644 index 0000000..9413f90 --- /dev/null +++ b/docs/income-status.md @@ -0,0 +1,27 @@ +# T-15 수입·입금 현황 + +## 구현 범위 + +- 수입 탭의 PlaceholderScreen을 실제 IncomeScreen으로 교체 +- 월 교육비·입금·미수금 요약, 학생별 청구 회차·금액·입금 대기/일부 입금/입금완료 표시 +- 학부모의 입금 신고와 실제 받은 금액을 구분. 신고만으로 입금완료로 전환하지 않음 +- 카드 스켈레톤 로딩, 빈 목록, 오류 재시도 표시 +- 원본 SVG에서 요약 그라데이션과 일러스트만 추출하고 숫자·학생 정보는 동적으로 렌더링 +- 기존 하단 탭 및 인증용 메모리 상태를 재사용. 정산 리포트 및 다른 탭 화면은 수정하지 않음 + +## API 상태 + +API는 미확정입니다. `IncomeState`/`IncomeData`는 화면 모델이며 실제 계약이 아닙니다. 개발 환경에서만 확인용 데이터를 사용합니다. production은 실제 조회 연동 전까지 준비 중 안내를 표시하고 임의의 금액을 노출하지 않습니다. + +월 집계와 목록은 별도로 전달할 수 있게 구분했습니다. 정상 예시는 청구 1,200,000원, 입금 400,000원, 미수금 800,000원입니다. 빈 화면 시안에도 같은 요약 금액이 남아 있어 빈 목록 미리보기는 이를 유지합니다. 실제 API 연동 시에는 조회 범위와 집계·목록 정합성을 확인해야 합니다. + +부모 입금 알림·예약 알림 전송, 은행 계좌 조회, 입금 확인·금액 변경은 연결하지 않았습니다. 알림 문구와 날짜는 확인용 데이터이며 실제 예약·전송을 뜻하지 않습니다. + +## 확인 경로 + +- `/dev/income`: 실제 수입 탭에서 정상 상태 확인 +- `/dev/income?state=loading`: 고정 스켈레톤 +- `/dev/income?state=empty`: 빈 목록 +- `/dev/income?state=error`: 조회 실패 및 재시도 + +개발용 진입점은 프로필이 없으면 메모리에 미리보기 프로필을 설정해 기존 탭으로 이동합니다. production에서는 이 진입을 허용하지 않습니다. 실제 수입 탭은 `/income`이며 다른 탭의 이동 구조·공통 탭 아이콘 구현은 유지합니다. diff --git a/src/app/(tabs)/income.tsx b/src/app/(tabs)/income.tsx index 5a43d35..76fba3a 100644 --- a/src/app/(tabs)/income.tsx +++ b/src/app/(tabs)/income.tsx @@ -1,4 +1,17 @@ -import PlaceholderScreen from '@/features/main/PlaceholderScreen'; +import { useState } from 'react'; +import { Tabs, useLocalSearchParams } from 'expo-router'; +import { IncomeScreen } from '@/features/income/IncomeScreen'; +import { incomePreviewState } from '@/features/income/previewData'; export default function Screen() { - return ; + const { preview } = useLocalSearchParams<{ preview?: string }>(); + const [retried, setRetried] = useState(false); + const state = __DEV__ + ? incomePreviewState(retried ? 'normal' : preview) + : { status: 'unavailable' as const }; + return ( + <> + + setRetried(true)} /> + + ); } diff --git a/src/app/dev/income.tsx b/src/app/dev/income.tsx new file mode 100644 index 0000000..f43533e --- /dev/null +++ b/src/app/dev/income.tsx @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import { Redirect, useLocalSearchParams } from 'expo-router'; +import { useOnboarding } from '@/features/onboarding/OnboardingProvider'; +export default function IncomePreview() { + const { state } = useLocalSearchParams<{ state?: string }>(); + const { profile, setProfile } = useOnboarding(); + useEffect(() => { + if (__DEV__ && !profile.name) setProfile({ name: '미리보기', subjects: [] }); + }, [profile.name, setProfile]); + if (!__DEV__) return ; + if (!profile.name) return null; + return ; +} diff --git a/src/features/income/IncomeScreen.tsx b/src/features/income/IncomeScreen.tsx new file mode 100644 index 0000000..6611d67 --- /dev/null +++ b/src/features/income/IncomeScreen.tsx @@ -0,0 +1,132 @@ +import { Image } from 'expo-image'; +import { ScrollView, Text, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { ActionButton } from '@/components/ActionButton'; +import { s } from './styles'; +import { paymentStatus, won } from './types'; +import type { IncomeInvoice, IncomeState } from './types'; +import gradient from '../../../assets/images/income-gradient.svg'; +import card from '../../../assets/images/income-card.svg'; + +function InvoiceCard({ invoice }: { invoice: IncomeInvoice }) { + const status = paymentStatus(invoice); + const paid = status === '입금완료'; + const note = + invoice.receivedAmount > 0 && !paid + ? `받은 금액 ${won(invoice.receivedAmount)}` + : !paid && invoice.parentReportedAt + ? `학부모님이 [입금했어요]를 눌렀어요 · ${invoice.parentReportedAt}` + : !paid + ? invoice.reminderText + : undefined; + return ( + + + {invoice.studentName} + + {status} + + + + 다음 {invoice.sessionCount}회차 + {won(invoice.billedAmount)} + + {note && ( + + {note} + + )} + + ); +} +export function IncomeScreen({ state, onRetry }: { state: IncomeState; onRetry?: () => void }) { + return ( + + + + + 수입 + + + + {state.status === 'loading' ? ( + + + + + + {[0, 1, 2, 3].map((i) => ( + + ))} + + + ) : state.status === 'ready' ? ( + <> + + + + 이번달 교육비 + {won(state.data.summary.expectedAmount)} + + + + 입금 {won(state.data.summary.receivedAmount)} + + + 미수금 {won(state.data.summary.outstandingAmount)} + + + + {state.data.invoices.length > 0 && ( + + ⓘ + + 학부모님이 [입금했어요]를 누르면 알려 드려요. 통장을 확인하고 체크해 주세요. + + + )} + + + 학생별 + + {state.data.invoices.length === 0 ? ( + + 이번달 청구가 아직 없어요 + + ) : ( + + {state.data.invoices.map((invoice) => ( + + ))} + + )} + + + ) : ( + + + {state.status === 'error' + ? state.message + : '수입 정보를 준비 중이에요. 연동 후 입금 현황을 확인할 수 있어요.'} + + {state.status === 'error' && onRetry && ( + + )} + + )} + + + + ); +} diff --git a/src/features/income/previewData.ts b/src/features/income/previewData.ts new file mode 100644 index 0000000..8efbe25 --- /dev/null +++ b/src/features/income/previewData.ts @@ -0,0 +1,45 @@ +import type { IncomeData, IncomeState } from './types'; +export const previewIncome: IncomeData = { + summary: { expectedAmount: 1200000, receivedAmount: 400000, outstandingAmount: 800000 }, + invoices: [ + { + id: 'income-1', + studentName: '박지후', + sessionCount: 8, + billedAmount: 360000, + receivedAmount: 0, + parentReportedAt: '9/15', + }, + { + id: 'income-2', + studentName: '윤서아', + sessionCount: 8, + billedAmount: 360000, + receivedAmount: 0, + reminderText: '안 들어오면 다음 수업 전날 오전 9시에 선생님께 알려 드려요', + }, + { + id: 'income-3', + studentName: '이도윤', + sessionCount: 4, + billedAmount: 200000, + receivedAmount: 120000, + }, + { + id: 'income-4', + studentName: '김하린', + sessionCount: 8, + billedAmount: 280000, + receivedAmount: 280000, + }, + ], +}; +export function incomePreviewState(value?: string): IncomeState { + if (value === 'loading') return { status: 'loading' }; + if (value === 'error') + return { status: 'error', message: '수입 정보를 불러오지 못했어요. 다시 시도해 주세요.' }; + // 빈 화면 SVG에도 월 요약 금액이 남아 있어 시각 확인용으로 동일하게 유지합니다. + if (value === 'empty') + return { status: 'ready', data: { summary: { ...previewIncome.summary }, invoices: [] } }; + return { status: 'ready', data: previewIncome }; +} diff --git a/src/features/income/styles.ts b/src/features/income/styles.ts new file mode 100644 index 0000000..4846c1a --- /dev/null +++ b/src/features/income/styles.ts @@ -0,0 +1,101 @@ +import { StyleSheet, Platform } from 'react-native'; +import { primitive, semantic as c } from '@/styles/colors'; +import { typography as t } from '@/styles/typography'; +import { padding, radius, gap, grid } from '@/styles/measurement'; +export const s = StyleSheet.create({ + safe: { flex: 1, alignItems: 'center', backgroundColor: c.surface.input }, + phone: { flex: 1, width: '100%', maxWidth: 390, paddingTop: Platform.OS === 'web' ? 44 : 0 }, + header: { height: 67, justifyContent: 'center', paddingHorizontal: grid.margin }, + title: { ...t.heading[1].semiBold, color: c.text.default.strong }, + scroll: { flex: 1 }, + content: { paddingHorizontal: grid.margin, paddingBottom: padding[16] }, + summary: { + height: 128, + borderRadius: radius.large, + overflow: 'hidden', + padding: padding[16], + justifyContent: 'space-between', + backgroundColor: c.action.default, + }, + gradient: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }, + summaryLabel: { ...t.caption[1].semiBold, color: c.text.default.inverse, marginTop: gap[4] }, + amount: { + ...t.heading[1].bold, + color: c.text.default.inverse, + marginTop: gap[4], + paddingRight: 60, + }, + totals: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: gap[8], + alignItems: 'center', + paddingBottom: gap[4], + }, + totalText: { ...t.body[5].semiBold, color: c.text.default.inverse }, + separator: { width: 1, height: 12, backgroundColor: c.border.default }, + illustration: { position: 'absolute', right: padding[16], top: 32, width: 64, height: 80 }, + info: { + marginTop: gap[20], + minHeight: 48, + borderRadius: radius.small, + borderWidth: 0.5, + borderColor: c.border.focused, + backgroundColor: c.surface.brand, + padding: padding[8], + flexDirection: 'row', + alignItems: 'center', + gap: gap[8], + }, + infoIcon: { fontSize: 14, color: c.text.brand.assistive }, + infoText: { ...t.caption[1].regular, color: c.text.brand.assistive, flex: 1 }, + students: { marginTop: gap[20] }, + sectionTitle: { + ...t.body[5].medium, + color: c.text.default.normal, + marginLeft: padding[8], + marginBottom: gap[8], + }, + list: { gap: gap[4] }, + card: { padding: padding[16], backgroundColor: c.surface.subtle, borderRadius: radius.medium }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + gap: gap[8], + }, + student: { ...t.body[2].semiBold, color: c.text.default.neutral, flex: 1 }, + badge: { + paddingHorizontal: padding[8], + paddingVertical: padding[4], + borderRadius: radius.small, + backgroundColor: c.surface.warning, + }, + paidBadge: { backgroundColor: c.surface.success }, + badgeText: { ...t.caption[1].medium, color: c.text.feedback.warning }, + paidText: { color: c.text.feedback.success }, + bill: { marginTop: gap[12] }, + sessions: { ...t.body[5].medium, color: c.text.default.neutral }, + billAmount: { ...t.body[4].semiBold, lineHeight: 20, color: c.text.default.neutral }, + note: { + borderTopWidth: 1, + borderColor: c.border.default, + marginTop: gap[8], + paddingTop: padding[8], + }, + noteText: { ...t.caption[1].regular, color: c.text.feedback.warning }, + empty: { backgroundColor: c.surface.subtle, borderRadius: radius.medium, padding: padding[16] }, + emptyText: { ...t.caption[1].regular, color: c.text.default.weak }, + skeleton: { backgroundColor: primitive.gray[100], borderRadius: radius.small }, + skeletonSummary: { height: 128, borderRadius: radius.large }, + skeletonInfo: { height: 32, marginTop: gap[20] }, + skeletonTitle: { width: 43, height: 22, marginTop: gap[20], marginBottom: gap[8] }, + skeletonCard: { height: 138, marginBottom: gap[4], borderRadius: radius.medium }, + error: { + padding: padding[24], + backgroundColor: c.surface.subtle, + borderRadius: radius.medium, + gap: gap[16], + }, + errorText: { ...t.body[4].regular, color: c.text.default.weak }, +}); diff --git a/src/features/income/types.ts b/src/features/income/types.ts new file mode 100644 index 0000000..0976e8c --- /dev/null +++ b/src/features/income/types.ts @@ -0,0 +1,27 @@ +export type IncomeInvoice = { + id: string; + studentName: string; + sessionCount: number; + billedAmount: number; + receivedAmount: number; + parentReportedAt?: string; + reminderText?: string; +}; +/** API 미확정: 화면 데이터 모델입니다. summary는 목록과 독립적인 집계 응답을 받을 수 있게 구분합니다. */ +export type IncomeData = { + summary: { expectedAmount: number; receivedAmount: number; outstandingAmount: number }; + invoices: IncomeInvoice[]; +}; +export type IncomeState = + | { status: 'loading' } + | { status: 'ready'; data: IncomeData } + | { status: 'error'; message: string } + | { status: 'unavailable' }; +export function paymentStatus(invoice: IncomeInvoice) { + if (invoice.receivedAmount >= invoice.billedAmount) return '입금완료'; + if (invoice.receivedAmount > 0) return '일부 입금'; + return '입금 대기'; +} +export function won(value: number) { + return `${new Intl.NumberFormat('ko-KR').format(value)}원`; +} From 80584026eba8d49636a7eb8a5df1c74eee2e1990 Mon Sep 17 00:00:00 2001 From: sumin0423 Date: Fri, 13 Nov 2026 00:08:34 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20=EC=83=81?= =?UTF-8?q?=EB=8B=A8=20=EC=95=8C=EB=A6=BC=20=EB=B0=B0=EB=84=88=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/push-banner-icon.svg | 41 +++++++ docs/push-banner.md | 41 +++++++ docs/screenshots/push-banner.png | Bin 0 -> 9597 bytes src/app/_layout.tsx | 9 +- src/app/dev/push-banner.tsx | 114 ++++++++++++++++++ src/components/PushBanner.tsx | 84 +++++++++++++ .../notifications/PushBannerProvider.tsx | 89 ++++++++++++++ 7 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 assets/images/push-banner-icon.svg create mode 100644 docs/push-banner.md create mode 100644 docs/screenshots/push-banner.png create mode 100644 src/app/dev/push-banner.tsx create mode 100644 src/components/PushBanner.tsx create mode 100644 src/features/notifications/PushBannerProvider.tsx diff --git a/assets/images/push-banner-icon.svg b/assets/images/push-banner-icon.svg new file mode 100644 index 0000000..5cd1c69 --- /dev/null +++ b/assets/images/push-banner-icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/push-banner.md b/docs/push-banner.md new file mode 100644 index 0000000..1ded3ed --- /dev/null +++ b/docs/push-banner.md @@ -0,0 +1,41 @@ +# 공통 PushBanner + +`pushBanner.svg`, `pushBanner 표시 방식.svg`를 기준으로 앱 내부 상단 배너를 구현했습니다. OS 상태바는 그리지 않고 Safe Area 아래에 배너를 표시합니다. 기본 좌우 여백 10, 너비 최대 370, 최소 높이 82, 원본 아이콘·그림자를 적용했습니다. + +## 구성 + +- `src/components/PushBanner.tsx`: 브랜드·시각·제목·본문과 탭/접근성 닫기 동작 +- `src/features/notifications/PushBannerProvider.tsx`: 전역 표시, 자동 닫기, 순차 큐 +- 루트 레이아웃에서 Provider 적용. 기존 화면 UI와 무관하게 `usePushBanner()` 호출 가능 + +```tsx +const { show, dismiss } = usePushBanner(); +const id = show({ + id: 'lesson-ended-123', + title: '윤서아 학생 수업이 끝났어요', + message: '잊기 전에 8회차를 기록해 주세요', + timeLabel: '지금', + durationMs: 5000, + onPress: () => { + /* 확정된 화면 경로로 이동 */ + }, +}); +dismiss(id); +``` + +- 기본 표시 5초, `durationMs: 0`이면 유지. 시안에는 시간이 정해져 있지 않아 변경 가능하도록 구성 +- 배너 탭 시 닫은 후 콜백 실행. 콜백이 없는 알림은 탭 시 닫기만 수행 +- 여러 알림은 동시에 겹치지 않고 순서대로 표시. 동일 ID는 기존 항목 갱신. 최대 5개까지 보관하며 초과 신규 항목은 추가하지 않음 +- 앱이 백그라운드이면 배너를 숨기고 타이머 정지, 복귀 시 현재 배너의 표시 시간을 다시 시작 +- 자동 닫기와 AppState 구독은 Provider 종료 시 정리 +- 원본처럼 닫기 X 버튼은 표시하지 않으며 접근성 닫기 동작을 제공 + +## 미리보기 + +`/dev/push-banner`에서 최초 시안 배너를 유지한 상태로 확인합니다. 배너 탭, 5초 자동 닫기, 순차 알림, 긴 문구 및 닫기를 확인할 수 있습니다. 개발 전용이며 production에서는 홈으로 이동합니다. + +## API 연결 전 범위 + +푸시 API·FCM·APNs·Web Push·알림 권한·백그라운드/잠금화면 수신은 연결하지 않았습니다. 실제 수업 종료 시 자동으로 알림을 생성하지 않습니다. API/수신 이벤트 확정 후 안전하게 변환한 문자열과 화면 이동 콜백을 `show()`에 전달해야 합니다. API에서 받은 임의 URL을 직접 실행하지 않습니다. + +이 배너는 일반 앱 화면 위의 인앱 오버레이입니다. OS 푸시 시스템을 흉내 내거나 네이티브 Modal 위 노출을 보장하는 구현이 아닙니다. diff --git a/docs/screenshots/push-banner.png b/docs/screenshots/push-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..99d1fc35b1873ebf05138d2b6ba0121549bae2a6 GIT binary patch literal 9597 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 literal 0 HcmV?d00001 diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index 5d16365..1df1185 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -2,12 +2,17 @@ import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { OnboardingProvider } from '@/features/onboarding/OnboardingProvider'; +import { PushBannerProvider } from '@/features/notifications/PushBannerProvider'; export default function RootLayout() { return ( - - + + + + ); diff --git a/src/app/dev/push-banner.tsx b/src/app/dev/push-banner.tsx new file mode 100644 index 0000000..f793eef --- /dev/null +++ b/src/app/dev/push-banner.tsx @@ -0,0 +1,114 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Redirect } from 'expo-router'; +import { ScrollView, StyleSheet, Text, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { ActionButton } from '@/components/ActionButton'; +import { usePushBanner } from '@/features/notifications/PushBannerProvider'; +import { semantic as c } from '@/styles/colors'; +import { typography as t } from '@/styles/typography'; +export default function BannerPreview() { + const { show, dismiss } = usePushBanner(); + const ids = useRef([]); + const [opened, setOpened] = useState(false); + const sample = useCallback( + (durationMs = 0) => { + const id = show({ + title: '윤서아 학생 수업이 끝났어요', + message: '잊기 전에 8회차를 기록해 주세요', + durationMs, + onPress: () => setOpened(true), + }); + ids.current.push(id); + }, + [show], + ); + useEffect(() => { + if (__DEV__) sample(); + const ownIds = ids.current; + return () => { + ownIds.forEach((id) => dismiss(id)); + }; + }, [sample, dismiss]); + if (!__DEV__) return ; + function clear() { + ids.current.forEach((id) => dismiss(id)); + ids.current.length = 0; + } + return ( + + + + 배너 미리보기 + + + 화면 위쪽에 알림이 표시됩니다. 배너를 누르면 닫히고 확인 동작을 실행해요. + + {opened && ( + + 배너를 눌렀어요. 실제 회차 기록 이동은 연결 전입니다. + + )} + + { + clear(); + sample(); + }} + /> + { + clear(); + sample(5000); + }} + /> + { + clear(); + sample(2000); + const id = show({ + title: '두 번째 알림', + message: '여러 알림은 겹치지 않고 차례로 표시돼요.', + durationMs: 0, + }); + ids.current.push(id); + }} + /> + { + clear(); + const id = show({ + title: + '학생의 수업 기록과 다음 숙제를 확인해 주세요. 긴 제목도 화면 너비에 맞춰 표시됩니다.', + message: '긴 본문은 두 줄까지 표시하고 나머지는 줄임표로 표시해요.', + durationMs: 0, + }); + ids.current.push(id); + }} + /> + + + + + ); +} +const s = StyleSheet.create({ + safe: { flex: 1, alignItems: 'center', backgroundColor: c.surface.input }, + content: { + width: '100%', + maxWidth: 390, + paddingHorizontal: 24, + paddingTop: 180, + paddingBottom: 40, + }, + heading: { ...t.heading[1].semiBold, color: c.text.default.strong, marginBottom: 12 }, + body: { ...t.body[4].regular, color: c.text.default.weak, marginBottom: 24 }, + result: { ...t.body[4].medium, color: c.text.brand.assistive, marginBottom: 16 }, + buttons: { gap: 12 }, +}); diff --git a/src/components/PushBanner.tsx b/src/components/PushBanner.tsx new file mode 100644 index 0000000..3c5ae9a --- /dev/null +++ b/src/components/PushBanner.tsx @@ -0,0 +1,84 @@ +import { Image } from 'expo-image'; +import { Pressable, StyleSheet, Text, View } from 'react-native'; +import { semantic as c } from '@/styles/colors'; +import { typography as t } from '@/styles/typography'; +import { radius, padding, gap } from '@/styles/measurement'; +import icon from '../../assets/images/push-banner-icon.svg'; +export type PushBannerProps = { + title: string; + message: string; + timeLabel?: string; + onPress: () => void; + onDismiss: () => void; +}; +/** 앱 내부 배너입니다. OS 푸시 권한이나 실제 알림 수신 기능을 포함하지 않습니다. */ +export function PushBanner({ + title, + message, + timeLabel = '지금', + onPress, + onDismiss, +}: PushBannerProps) { + return ( + + { + if (event.nativeEvent.actionName === 'dismiss') onDismiss(); + }} + onAccessibilityEscape={onDismiss} + onPress={onPress} + style={({ pressed }) => [s.card, pressed && s.pressed]} + > + + + + SUDAM + {timeLabel} + + + {title} + + + {message} + + + + + ); +} +const s = StyleSheet.create({ + shadow: { + borderRadius: radius.large, + backgroundColor: c.surface.subtle, + boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.18)', + elevation: 6, + }, + card: { + minHeight: 82, + borderRadius: radius.large, + paddingHorizontal: 14, + paddingVertical: padding[12], + flexDirection: 'row', + alignItems: 'center', + gap: 10, + backgroundColor: c.surface.subtle, + }, + pressed: { opacity: 0.85 }, + icon: { width: 38, height: 38 }, + content: { flex: 1, minWidth: 0 }, + meta: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 2, + gap: gap[8], + }, + brand: { ...t.caption[1].semiBold, color: c.text.default.strong }, + time: { ...t.caption[1].regular, color: c.text.default.strong }, + title: { ...t.body[3].semiBold, color: c.text.default.normal }, + message: { ...t.caption[1].regular, color: c.text.default.normal }, +}); diff --git a/src/features/notifications/PushBannerProvider.tsx b/src/features/notifications/PushBannerProvider.tsx new file mode 100644 index 0000000..70eae83 --- /dev/null +++ b/src/features/notifications/PushBannerProvider.tsx @@ -0,0 +1,89 @@ +import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'; +import type { ReactNode } from 'react'; +import { AppState, Platform, StyleSheet, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { PushBanner } from '@/components/PushBanner'; +export type BannerNotification = { + id?: string; + title: string; + message: string; + timeLabel?: string; + /** 기본 5초. 0이면 자동으로 닫지 않습니다. */ durationMs?: number; + onPress?: () => void; +}; +type Entry = BannerNotification & { id: string; revision: number }; +type ContextValue = { + show: (notification: BannerNotification) => string; + dismiss: (id?: string) => void; +}; +const Context = createContext(null); +const MAX_QUEUE = 5; +export function PushBannerProvider({ children }: { children: ReactNode }) { + const [queue, setQueue] = useState([]); + const [appState, setAppState] = useState(AppState.currentState); + const sequence = useRef(0); + const active = queue[0]; + const insets = useSafeAreaInsets(); + const show = useCallback((notification: BannerNotification) => { + const revision = ++sequence.current; + const id = notification.id?.trim() || `banner-${revision}`; + const entry = { ...notification, id, revision }; + if (!notification.title.trim()) return id; + setQueue((current) => { + const exists = current.some((item) => item.id === id); + return exists + ? current.map((item) => (item.id === id ? entry : item)) + : [...current, entry].slice(0, MAX_QUEUE); + }); + return id; + }, []); + const dismiss = useCallback( + (id?: string) => + setQueue((current) => (id ? current.filter((item) => item.id !== id) : current.slice(1))), + [], + ); + useEffect(() => { + const sub = AppState.addEventListener('change', setAppState); + return () => sub.remove(); + }, []); + useEffect(() => { + if (!active || appState !== 'active') return; + const duration = active.durationMs ?? 5000; + if (!Number.isFinite(duration) || duration <= 0) return; + const timer = setTimeout(() => dismiss(active.id), duration); + return () => clearTimeout(timer); + }, [active, appState, dismiss]); + return ( + + {children} + {active && appState === 'active' && ( + + + dismiss(active.id)} + onPress={() => { + dismiss(active.id); + active.onPress?.(); + }} + /> + + + )} + + ); +} +export function usePushBanner() { + const context = useContext(Context); + if (!context) throw new Error('PushBannerProvider is required'); + return context; +} +const s = StyleSheet.create({ + layer: { position: 'absolute', left: 0, right: 0, zIndex: 1000, alignItems: 'center' }, + width: { width: '100%', maxWidth: 390, paddingHorizontal: 10 }, +}); From edb1221f5c21730e17c73472be6e0e13dcddeb3c Mon Sep 17 00:00:00 2001 From: sumin0423 Date: Fri, 13 Nov 2026 00:08:38 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=ED=95=98=EB=8B=A8=20=ED=83=AD=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B0=8F=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/images/navigation/home-active.svg | 3 ++ assets/images/navigation/home.svg | 3 ++ assets/images/navigation/income-active.svg | 4 +++ assets/images/navigation/income.svg | 4 +++ assets/images/navigation/reports-active.svg | 7 ++++ assets/images/navigation/reports.svg | 7 ++++ assets/images/navigation/schedule-active.svg | 6 ++++ assets/images/navigation/schedule.svg | 6 ++++ assets/images/navigation/students-active.svg | 5 +++ assets/images/navigation/students.svg | 5 +++ docs/navigation-icons.md | 9 +++++ docs/screenshots/income-navigation.png | Bin 0 -> 57393 bytes src/features/main/MainTabBar.tsx | 33 +++++++++++++++++-- 13 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 assets/images/navigation/home-active.svg create mode 100644 assets/images/navigation/home.svg create mode 100644 assets/images/navigation/income-active.svg create mode 100644 assets/images/navigation/income.svg create mode 100644 assets/images/navigation/reports-active.svg create mode 100644 assets/images/navigation/reports.svg create mode 100644 assets/images/navigation/schedule-active.svg create mode 100644 assets/images/navigation/schedule.svg create mode 100644 assets/images/navigation/students-active.svg create mode 100644 assets/images/navigation/students.svg create mode 100644 docs/navigation-icons.md create mode 100644 docs/screenshots/income-navigation.png diff --git a/assets/images/navigation/home-active.svg b/assets/images/navigation/home-active.svg new file mode 100644 index 0000000..80de17a --- /dev/null +++ b/assets/images/navigation/home-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/navigation/home.svg b/assets/images/navigation/home.svg new file mode 100644 index 0000000..3035cde --- /dev/null +++ b/assets/images/navigation/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/navigation/income-active.svg b/assets/images/navigation/income-active.svg new file mode 100644 index 0000000..a15b09f --- /dev/null +++ b/assets/images/navigation/income-active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/navigation/income.svg b/assets/images/navigation/income.svg new file mode 100644 index 0000000..50f47dd --- /dev/null +++ b/assets/images/navigation/income.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/navigation/reports-active.svg b/assets/images/navigation/reports-active.svg new file mode 100644 index 0000000..11cb463 --- /dev/null +++ b/assets/images/navigation/reports-active.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/navigation/reports.svg b/assets/images/navigation/reports.svg new file mode 100644 index 0000000..a5545cf --- /dev/null +++ b/assets/images/navigation/reports.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/navigation/schedule-active.svg b/assets/images/navigation/schedule-active.svg new file mode 100644 index 0000000..e61ff8d --- /dev/null +++ b/assets/images/navigation/schedule-active.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/navigation/schedule.svg b/assets/images/navigation/schedule.svg new file mode 100644 index 0000000..799d7fa --- /dev/null +++ b/assets/images/navigation/schedule.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/navigation/students-active.svg b/assets/images/navigation/students-active.svg new file mode 100644 index 0000000..3a81f7d --- /dev/null +++ b/assets/images/navigation/students-active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/navigation/students.svg b/assets/images/navigation/students.svg new file mode 100644 index 0000000..c7683e2 --- /dev/null +++ b/assets/images/navigation/students.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/navigation-icons.md b/docs/navigation-icons.md new file mode 100644 index 0000000..7f207d7 --- /dev/null +++ b/docs/navigation-icons.md @@ -0,0 +1,9 @@ +# 하단 탭 아이콘 + +홈·일정·학생·리포트·수입 SVG를 MainTabBar에 연결했습니다. icNav.svg는 교육비/통화 모양으로 수입 탭에 매핑했습니다. + +- 활성: 아이콘·글자 `#219BFF` +- 비활성: 아이콘 `#B5B9C0`, 글자 디자인 토큰의 weak 색상 +- 32px 아이콘 프레임, 기존 탭 이동·다시 누르기 이벤트 유지 +- SVG의 회색 면만 파란색으로 변경한 활성 에셋을 사용해 시계·리포트·통화 내부 흰색 디테일 보존 +- 상단 알림 배너와 하단 탭은 별도 기능입니다. 실제 탭 화면에는 표시되며 전체 화면/개발 미리보기 경로에는 자동으로 하단 탭을 추가하지 않습니다. diff --git a/docs/screenshots/income-navigation.png b/docs/screenshots/income-navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..28fc79796c6ff42c1e7aaf5f7015e0c225612997 GIT binary patch literal 57393 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 literal 0 HcmV?d00001 diff --git a/src/features/main/MainTabBar.tsx b/src/features/main/MainTabBar.tsx index b72dc93..68799c6 100644 --- a/src/features/main/MainTabBar.tsx +++ b/src/features/main/MainTabBar.tsx @@ -1,3 +1,14 @@ +import { Image } from 'expo-image'; +import homeIcon from '../../../assets/images/navigation/home.svg'; +import homeActiveIcon from '../../../assets/images/navigation/home-active.svg'; +import scheduleIcon from '../../../assets/images/navigation/schedule.svg'; +import scheduleActiveIcon from '../../../assets/images/navigation/schedule-active.svg'; +import studentsIcon from '../../../assets/images/navigation/students.svg'; +import studentsActiveIcon from '../../../assets/images/navigation/students-active.svg'; +import reportsIcon from '../../../assets/images/navigation/reports.svg'; +import reportsActiveIcon from '../../../assets/images/navigation/reports-active.svg'; +import incomeIcon from '../../../assets/images/navigation/income.svg'; +import incomeActiveIcon from '../../../assets/images/navigation/income-active.svg'; import type { BottomTabBarProps } from 'expo-router/js-tabs'; import { Pressable, StyleSheet, Text, View } from 'react-native'; import { primitive, semantic } from '@/styles/colors'; @@ -6,6 +17,13 @@ import { fontSize, fontWeight, letterSpacing, lineHeight } from '@/styles/typogr // 아이콘 프레임(여백 포함) 크기 const ICON_SIZE = 32; +const icons: Record = { + home: { inactive: homeIcon, active: homeActiveIcon }, + schedule: { inactive: scheduleIcon, active: scheduleActiveIcon }, + students: { inactive: studentsIcon, active: studentsActiveIcon }, + reports: { inactive: reportsIcon, active: reportsActiveIcon }, + income: { inactive: incomeIcon, active: incomeActiveIcon }, +}; export function MainTabBar({ state, @@ -18,6 +36,7 @@ export function MainTabBar({ {state.routes.map((route, index) => { const focused = index === state.index; + const icon = icons[route.name]; const { options } = descriptors[route.key]; const label = options.title ?? route.name; const onPress = () => { @@ -37,7 +56,17 @@ export function MainTabBar({ onPress={onPress} style={s.item} > - + {icon ? ( + + ) : ( + + )} {label} ); @@ -64,5 +93,5 @@ const s = StyleSheet.create({ textAlign: 'center', }, labelActive: { color: semantic.action.default }, - labelInactive: { color: semantic.icon.secondary }, + labelInactive: { color: semantic.text.default.weak }, });