Skip to content

Screen: Dashboard (대시보드)

진입 화면. App = 로그인 후 첫 도착 (bottom nav 홈 탭). Web = 비로그인 랜딩 (/).

💡 시각 미리보기:

플랫폼별 차이

항목App (Flutter)Web (Next.js)
인증로그인 필수비로그인 한정 (로그인 UI 없음)
워치리스트있음 (chips + 공시 필터)없음
QuotaQuotaIndicator floating없음
추천 종목EmptyWatchlist 상태에서만 (워치리스트 = 0)프리셋 칩 = 최근 7일 공시(요약 보유) 발생 종목 (API), 필터
공시 list워치리스트 기반 + 무한 스크롤summary 있는 최근 20건 (페이징 없음, 종목 필터 가능)
종목 선택watchlist 추가 (API 호출)필터 — 해당 종목 공시만 (/?corp=), 추가/이동 X
더보기무한 스크롤AppCTA 모달 (앱 다운로드 유도)
Navbottom nav 3탭 (실동작)없음 (FloatingNavBar 제거)
뷰포트모바일 디바이스모바일 폭 고정 (max-w-[420px], desktop 도 동일)

App — 화면 구성

영역컴포넌트조건
AppBar(시스템, title="공시")항상
종목 추가 검색StockSearch항상
워치리스트 칩 rowWatchStockChip × N워치리스트 ≥ 1
본문 — 공시 목록DisclosureItem × N (페이지네이션)워치리스트 ≥ 1
본문 — 온보딩EmptyWatchlist워치리스트 = 0
FloatingQuotaIndicator📋 target — 현재 dashboard 미구현 (detail 화면에만 존재), 추가는 별도 PR

App — 상태 분기

  • S1. 정상 (워치리스트 ≥ 1) — 검색박스 + 칩 row + DisclosureItem list (무한 스크롤)
  • S2. 빈 워치리스트 (신규 사용자) — 검색박스 + EmptyWatchlist (온보딩 + 추천 8개 + 최근 공시 피드)
  • S3. 로딩 — 워치리스트 / 대시보드 둘 다 spinner
  • S4. 에러 — 영역별 메시지 + 재시도 안내

App — 진입 시 API 호출 (waterfall)

로그인 후 대시보드 마운트 시 — 병렬:

  • GET /api/v1/watchlist → 워치리스트 칩 row 렌더 결정 (S1 vs S2)
  • GET /api/v1/dashboard/disclosures?page=0&size=20 → DisclosureItem list (마운트 직후 항상 호출 — DashboardDisclosuresController.build()Future.microtask(reload), 워치리스트 0/N 무관)
  • (📋 target) GET /api/v1/quota → QuotaIndicator — dashboard 에 quota 추가 시

페이지네이션: page0-based (백엔드 queryInt(r,"page",0) + mobile fetchDashboardPage(page=0)). 첫 진입 = page 0.

워치리스트 = 0 (EmptyWatchlist): 추가로 GET /api/v1/disclosures/recent?limit=10 → 전체 최신 공시 피드

App — 액션

종목 추가 (S2 → S1 전환)

  1. 검색 박스 입력 → 300ms debounce → GET /api/v1/stocks/search?query=<q>
  2. 드롭다운에서 선택 → POST /api/v1/watchlist {corp_code}
  3. 성공 → 워치리스트 reload + dashboard disclosures reload (페이지 리셋)

추천 칩 (EmptyWatchlist 상태)

  1. 추천 칩 탭 → POST /api/v1/watchlist {corp_code}
  2. 성공 → S1 으로 전환

종목 제거

  1. WatchStockChip 의 X 탭 → DELETE /api/v1/watchlist/{corp_code}
  2. 성공 → 워치리스트 reload + dashboard disclosures reload
  3. 마지막 종목 제거 시 S2 (EmptyWatchlist) 로 전환

공시 탭

DisclosureItem 탭 → screen-disclosure-detail (예정) 진입.

Pull to refresh

워치리스트 + 대시보드 disclosures 동시 reload.

무한 스크롤

스크롤 끝 도달 → GET /api/v1/dashboard/disclosures?page=N+1&size=20

Quota 인디케이터 탭

breakdown dialog (Modal/M4) 표시.


Web — 화면 구성

웹은 summary 있는 공시만 노출 + 종목 필터 중심. 종목 선택(검색 or 프리셋 칩)은 워치리스트 추가가 아니라 URL 쿼리(/?corp={corpCode}) 필터 — SSR 재렌더, 공유 가능.

영역컴포넌트조건
AppBarAppBar (title="공시")항상
종목 검색SearchBox항상 (선택 시 /?corp= 필터)
프리셋 칩SuggestedStocks (최근 7일 공시 발생 종목, API)항상 (필터 활성 칩은 selected 표기 + ✕)
본문 — 공시 목록DisclosureItem × ≤20 (summary 있는 것만)항상
더보기 CTA"더 많은 공시는 앱에서" 버튼항상 (목록 하단)
Footer© + 정책 링크 (privacy / terms / support)항상

❌ FloatingNavBar 제거 (웹 전체) — 단일 대시보드 + 필터 구조라 탭 네비 불필요.

Web — 상태 분기

비로그인 한정. 필터 유무(?corp)에 따라:

  • 로딩 — SSR (Next.js force-dynamic), fetch 실패 시 "공시 목록을 불러올 수 없습니다."
  • 빈 결과 — 필터 없음: "아직 등록된 공시가 없습니다." / 필터 활성: "이 종목의 최근 공시가 없습니다." (종목명 비의존 — 직접 /?corp= 진입 + 0건 시 종목명 복원 불가하므로 추가 API 호출 없이 닫음)
  • 정상 — 공시 list 표시 (필터 활성 시 헤딩 "{종목명} 공시" — 종목명은 응답 disclosure 의 corp_name 에서 취득 + "전체 보기" reset)

Web — 진입 시 API 호출

SSR 시점 (Next.js force-dynamic + fetchCache: default-cache, revalidate=60) — 병렬:

  • GET /api/v1/web/disclosures?limit=20[&corpCode={corp}] → DisclosureItem list (summary-only. corpCode?corp 쿼리 있을 때만)
  • GET /api/v1/web/active-stocks?days=7 → 프리셋 칩 (최근 7일 summary 있는 공시 발생 종목)

모바일과 분리된 웹 전용 엔드포인트(/api/v1/web/*). 성격이 다름 — 비로그인 · summary-only · 필터 · 앱 유도. 모바일 /recent · /dashboard/disclosures 는 불변. 백엔드 설계는 PR2.

Web — 액션

프리셋 칩 탭

  1. SuggestedStocks chip 탭 → router.push('/?corp={corp_code}')
  2. 해당 종목의 summary 있는 최근 20건으로 필터 (SSR 재렌더)
  3. watchlist API 호출 안 함 — 비로그인 · 추가 개념 없음. 활성 칩 재탭(✕) 또는 "전체 보기" → / 로 필터 해제

종목 검색

  1. SearchBox 입력 → /api/stocks/search (Next.js route handler) → 백엔드 /api/v1/stocks/search
  2. 결과 선택 (selectStock) → router.push('/?corp={corp_code}') 필터 (페이지 이동 X — 프리셋 칩과 동일)

공시 탭

DisclosureItem 탭 → /disclosures/[rceptNo] (공시 상세 페이지, 비로그인 가능, 분석 본문은 일부 마스킹).

더보기

"더 많은 공시는 앱에서" 버튼 탭 → AppCTA 모달 (App Store / Google Play, 현재 disabled placeholder). 웹은 페이징 없이 최근 20건만 제공 — 그 이상은 앱 유도.


액션 카탈로그 (전체)

액션endpointmethodrequestresponseAppWeb
워치리스트 조회/api/v1/watchlistGET[WatchStock]
워치리스트 추가/api/v1/watchlistPOST{corp_code}WatchStock
워치리스트 제거/api/v1/watchlist/{corp_code}DELETE204
종목 검색/api/v1/stocks/searchGET?query=[StockSearchResult]✅ (route handler 경유)
대시보드 공시 페이지/api/v1/dashboard/disclosuresGET?page&sizeDashboardPage
최근 공시 (전체)/api/v1/disclosures/recentGET?limit[DashboardDisclosure]✅ (EmptyWatchlist)
웹 공시 목록 (summary-only)/api/v1/web/disclosuresGET?limit&corpCode[DashboardDisclosure]✅ (메인 list, PR2)
웹 프리셋 종목/api/v1/web/active-stocksGET?days&limit[StockSearchResult]✅ (프리셋 칩, PR2)
Quota/api/v1/quotaGETQuotaStatus

Edge cases

App

  • 인증 만료 (401): requireAuth 흐름으로 auth-gate 띄움 → flow-auth-gate (예정)
  • bootstrapped 안 됨: 첫 cold start /auth/me 응답까지 ~2초 폴링, 그 동안 화면 보이지만 보호된 액션 차단
  • 동시 등록/제거 race: 마지막 reload 가 진실 — 중간 상태 UI 일시 깜빡임 허용
  • 종목 검색 결과 0건: 드롭다운 자체 hide (별도 "결과 없음" 메시지 X)
  • 광고 보상 사용 시: weeklyUsed 음수 가능, QuotaIndicator label remaining > 10 표시 (bar clamp)

Web

  • 백엔드 다운 / 403: getJSONSoft 가 null 삼킴 → "공시 목록을 불러올 수 없습니다." Empty 표시 (에러 throw 안 함)
  • CF 빌드 시 prerender: force-dynamic 으로 회피 — Worker secret 살아있는 런타임 시점 fetch
  • Smart App Banner (iOS Safari): apple-itunes-app meta 가 App Store 진입 유도 (앱 등록 전엔 placeholder app-id, 배너 안 뜸)

관련 docs

Status

  • ✅ App — 검색 / 워치리스트 / 공시 list / 페이지네이션 (0-based) 구현 일치
  • 📋 Web 개편 (진행 중, 0520) — 종목 필터 + summary-only + 프리셋(active-stocks) + FloatingNavBar 제거 + 더보기 AppCTA. 디자인/스펙 = 본 PR, 백엔드(/api/v1/web/*) = PR2, FE = PR3
  • 📋 /companies/[corpCode] 페이지 제거 (PR3) — 종목 필터가 흡수. 검색/프리셋 선택 + disclosure-detail HeaderCard 의 기업 링크 모두 /?corp={corpCode} 로 통일 (PR3 에서 /companies/* 진입점 전부 전환 — 잔존 링크 404 방지)
  • 📋 target (현재 미구현, 별도 PR) — dashboard 에 QuotaIndicator floating 추가. 현재 quota 위젯은 disclosure-detail 화면에만 존재
  • 📋 상대 시간 라벨 ("오늘 오후 5:30" / "어제") — 디자인 박제, BE created_at 노출 미구현