Repository navigation
feat: 홈 화면 고민 카테고리 TOP4 통계 컴포넌트 구현 #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
6eb7ed6
feat: 홈 화면 기본 구조 생성 (헤더, 일기 박스)
wjdalss21 f98f8f3
feat: Header variant 분리 (logo/title) 및 홈 화면 적용
wjdalss21 d959392
feat: 사건기록 페이지 생성 및 로고 헤더 적용 (/records)
wjdalss21 5a6643e
fix: diaryBox 고정 폭을 max-width + width 100%로 반응형 처리
wjdalss21 a04b781
feat: 감정일기 작성 버튼 /diary/new 네비게이션 연결
wjdalss21 6ab4d7e
feat: 홈 화면 캐릭터 이미지 교체 및 위치 조정
wjdalss21 5d3583d
feat: 홈 화면 고민 카테고리 TOP4 통계 섹션 구현
wjdalss21 3ee8a54
feat: 홈 화면 고민 카테고리 TOP4 통계 컴포넌트 구현
wjdalss21 cda4aa9
feat: 홈 화면 통계 섹션 빈 상태 플레이스홀더 및 구분선 추가
wjdalss21 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| @use '@/styles/abstracts/variables' as v; | ||
|
|
||
| .page { | ||
| position: relative; | ||
| } | ||
|
|
||
| .characterImage { | ||
| position: absolute; | ||
| top: 41px; | ||
| right: -20px; | ||
| width: 169px; | ||
| height: 138px; | ||
| object-fit: contain; | ||
| pointer-events: none; | ||
| z-index: 0; | ||
| } | ||
|
|
||
| .container { | ||
| position: relative; | ||
| z-index: 1; | ||
| display: flex; | ||
| flex-direction: column; | ||
| padding: 20px 16px; | ||
| gap: 16px; | ||
| } | ||
|
|
||
| // 인사 | ||
| .greeting { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| } | ||
|
|
||
| .userName { | ||
| font-size: v.$font-size-title-m; | ||
| font-weight: v.$font-weight-bold; | ||
| color: v.$color-black-700; | ||
| line-height: v.$line-height-title-m; | ||
| } | ||
|
|
||
| .greetingText { | ||
| font-size: v.$font-size-body-s; | ||
| font-weight: v.$font-weight-regular; | ||
| color: v.$color-black-400; | ||
| line-height: v.$line-height-body-s; | ||
| } | ||
|
|
||
| // 일기 박스 | ||
| .diaryBox { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| align-self: center; | ||
| width: 100%; | ||
| max-width: 365px; | ||
| height: 88px; | ||
| padding: 20px 16px; | ||
| border: 2px solid v.$color-black-700; | ||
| border-radius: 12px; | ||
| background-color: v.$color-primary-100; | ||
| box-sizing: border-box; | ||
| text-decoration: none; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| .diaryTextGroup { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| } | ||
|
|
||
| .diaryTitle { | ||
| font-size: v.$font-size-body-s; | ||
| font-weight: v.$font-weight-semibold; | ||
| color: v.$color-black-700; | ||
| line-height: v.$line-height-body-s; | ||
| } | ||
|
|
||
| .diarySubtitle { | ||
| font-size: v.$font-size-caption; | ||
| font-weight: v.$font-weight-regular; | ||
| color: v.$color-black-400; | ||
| line-height: v.$line-height-caption; | ||
| } | ||
|
|
||
| // 구분선 | ||
| .divider { | ||
| height: 8px; | ||
| background-color: v.$color-black-100; | ||
| margin-top: 26px; // gap(16) + 26 = 42px | ||
| margin-left: -16px; | ||
| margin-right: -16px; | ||
| } | ||
|
|
||
| .diaryButton { | ||
| width: 28px; | ||
| height: 28px; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-size: 20px; | ||
| font-weight: v.$font-weight-bold; | ||
| color: v.$color-black-700; | ||
| background: none; | ||
| border: none; | ||
| cursor: pointer; | ||
| flex-shrink: 0; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import Image from 'next/image' | ||
| import Link from 'next/link' | ||
| import { getServerSession } from 'next-auth' | ||
| import { authOptions } from '@/lib/auth' | ||
| import Header from '@/components/layout/Header' | ||
| import StatsCategorySection from '@/components/home/StatsCategorySection' | ||
| import styles from './page.module.scss' | ||
|
|
||
| export default async function HomePage() { | ||
| const session = await getServerSession(authOptions) | ||
| const userName = session?.user?.name ?? '사용자' | ||
|
|
||
| return ( | ||
| <div className={styles.page}> | ||
| <Header variant="logo" /> | ||
|
|
||
| <Image | ||
| src="/images/characters/character-home.png" | ||
| alt="말해부엉 캐릭터" | ||
| width={169} | ||
| height={138} | ||
| className={styles.characterImage} | ||
| /> | ||
|
|
||
| <main className={styles.container}> | ||
| {/* 인사 */} | ||
| <section className={styles.greeting}> | ||
| <p className={styles.userName}>{userName}님</p> | ||
| <p className={styles.greetingText}>오늘 감정은 어떠신가요?</p> | ||
| </section> | ||
|
|
||
| {/* 오늘의 일기 박스 */} | ||
| {/* 승규님 우선 버튼 클릭시 diary/new로 작성페이지를 임시로 지정해놨는데 작성페이지 이름 어떻게 하실건지 알려주시면 수정하겠습니다 */} | ||
| <Link href="/diary/new" className={styles.diaryBox} aria-label="감정일기 작성"> | ||
| <div className={styles.diaryTextGroup}> | ||
| <p className={styles.diaryTitle}>오늘의 일기를 적어보세요</p> | ||
| <p className={styles.diarySubtitle}>감정일기 작성하러가기</p> | ||
| </div> | ||
| <span className={styles.diaryButton} aria-hidden="true">+</span> | ||
| </Link> | ||
|
|
||
| {/* 고민 카테고리 TOP4 */} | ||
| <StatsCategorySection /> | ||
|
|
||
| {/* 구분선 */} | ||
| <div className={styles.divider} /> | ||
| </main> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import Header from '@/components/layout/Header' | ||
|
|
||
| export default function RecordsPage() { | ||
| return ( | ||
| <> | ||
| <Header variant="logo" /> | ||
| <main> | ||
| </main> | ||
| </> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| @use '@/styles/abstracts/variables' as v; | ||
| @use '@/styles/abstracts/functions' as fn; | ||
|
|
||
| .section { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: fn.r(12); | ||
| margin-top: fn.r(24); // container gap(16) + 24 = 40 (일기박스 ↔ 타이틀) | ||
| } | ||
|
|
||
| .title { | ||
| font-size: v.$font-size-title-s; | ||
| font-weight: v.$font-weight-bold; | ||
| line-height: v.$line-height-title-s; | ||
| color: var(--text-primary); | ||
| } | ||
|
|
||
| // 단일 스택 바 | ||
| .bar { | ||
| display: flex; | ||
| width: fn.r(346); | ||
| height: fn.r(12); | ||
| align-self: center; | ||
| overflow: hidden; | ||
| border-radius: 0; | ||
|
|
||
| &__segment { | ||
| height: 100%; | ||
|
|
||
| &--romance { background-color: var(--category-love-bg); } | ||
| &--work { background-color: var(--category-work-bg); } | ||
| &--friend { background-color: var(--category-friend-bg); } | ||
| &--family { background-color: var(--category-family-bg); } | ||
| &--placeholder { background-color: var(--bg-disabled); } | ||
| } | ||
| } | ||
|
|
||
| // 카테고리 레이블 2×2 그리드 | ||
| .labels { | ||
| display: grid; | ||
| grid-template-columns: 1fr 1fr; | ||
| gap: fn.r(8) fn.r(0); | ||
| margin-top: fn.r(1); // bar ↔ labels: 13px (gap 12 + 1 = 13) | ||
| justify-items: center; | ||
| } | ||
|
|
||
| .label { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: fn.r(4); | ||
|
|
||
| &__icon { | ||
| font-size: fn.r(14) !important; | ||
|
|
||
| &--romance { color: var(--category-love-text); } | ||
| &--work { color: var(--category-work-text); } | ||
| &--friend { color: var(--category-friend-text); } | ||
| &--family { color: var(--category-family-text); } | ||
| } | ||
|
|
||
| &__text { | ||
| font-size: v.$font-size-caption; | ||
| font-weight: v.$font-weight-regular; | ||
| line-height: v.$line-height-caption; | ||
|
|
||
| &--romance { color: var(--category-love-text); } | ||
| &--work { color: var(--category-work-text); } | ||
| &--friend { color: var(--category-friend-text); } | ||
| &--family { color: var(--category-family-text); } | ||
|
|
||
| &--bold { font-weight: v.$font-weight-bold; } | ||
| } | ||
|
|
||
| &__percent { | ||
| font-size: v.$font-size-caption; | ||
| font-weight: v.$font-weight-regular; | ||
| line-height: v.$line-height-caption; | ||
|
|
||
| &--romance { color: var(--category-love-text); } | ||
| &--work { color: var(--category-work-text); } | ||
| &--friend { color: var(--category-friend-text); } | ||
| &--family { color: var(--category-family-text); } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| 'use client'; | ||
|
|
||
| import FavoriteIcon from '@mui/icons-material/Favorite'; | ||
| import BusinessCenterIcon from '@mui/icons-material/BusinessCenter'; | ||
| import Diversity3Icon from '@mui/icons-material/Diversity3'; | ||
| import FamilyRestroomIcon from '@mui/icons-material/FamilyRestroom'; | ||
| import { useStatistics } from '@/hooks/useStatistics'; | ||
| import styles from './StatsCategorySection.module.scss'; | ||
|
|
||
| type CategoryKey = 'ROMANCE' | 'WORK' | 'FRIEND' | 'FAMILY'; | ||
|
|
||
| const CATEGORY_CONFIG: Record<CategoryKey, { label: string; icon: React.ElementType; styleKey: string }> = { | ||
| ROMANCE: { label: '연인관계', icon: FavoriteIcon, styleKey: 'romance' }, | ||
| WORK: { label: '직장관계', icon: BusinessCenterIcon, styleKey: 'work' }, | ||
| FRIEND: { label: '친구관계', icon: Diversity3Icon, styleKey: 'friend' }, | ||
| FAMILY: { label: '가족관계', icon: FamilyRestroomIcon, styleKey: 'family' }, | ||
| }; | ||
|
|
||
| const PLACEHOLDER_CATEGORIES = ['ROMANCE', 'WORK', 'FRIEND', 'FAMILY'] as const; | ||
|
|
||
| export default function StatsCategorySection() { | ||
| const { data, isLoading, isError } = useStatistics(); | ||
|
|
||
| const isEmpty = !data || data.total === 0; | ||
| const showPlaceholder = isLoading || isError || isEmpty; | ||
|
|
||
|
Comment on lines
+24
to
+26
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 401 에러 시 현재 수정 예시 export default function StatsCategorySection() {
const { data, isLoading, isError } = useStatistics();
const isEmpty = !data || data.total === 0;
- const showPlaceholder = isLoading || isError || isEmpty;
+ if (isError) return null;
+ const showPlaceholder = isLoading || isEmpty;Also applies to: 27-71 🤖 Prompt for AI Agents |
||
| return ( | ||
| <section className={styles.section}> | ||
| <h2 className={styles.title}>고민 카테고리 TOP4</h2> | ||
|
|
||
| {/* 단일 스택 바: 데이터 없거나 로딩/에러면 25%씩 회색 플레이스홀더 */} | ||
| <div className={styles.bar}> | ||
| {showPlaceholder | ||
| ? PLACEHOLDER_CATEGORIES.map((category) => ( | ||
| <div | ||
| key={category} | ||
| className={`${styles.bar__segment} ${styles['bar__segment--placeholder']}`} | ||
| style={{ width: '25%' }} | ||
| /> | ||
| )) | ||
| : data!.items.map(({ category, percentage }) => { | ||
| const { styleKey } = CATEGORY_CONFIG[category as CategoryKey]; | ||
| return ( | ||
| <div | ||
| key={category} | ||
| className={`${styles.bar__segment} ${styles[`bar__segment--${styleKey}`]}`} | ||
| style={{ width: `${percentage}%` }} | ||
| /> | ||
| ); | ||
| })} | ||
| </div> | ||
|
|
||
| {/* 카테고리 레이블 2×2 그리드: 아이콘 + 이름(볼드) + 퍼센트 */} | ||
| <div className={styles.labels}> | ||
| {PLACEHOLDER_CATEGORIES.map((category) => { | ||
| const { label, icon: Icon, styleKey } = CATEGORY_CONFIG[category]; | ||
| const percentage = showPlaceholder ? null : data!.items.find((i) => i.category === category)?.percentage ?? 0; | ||
| return ( | ||
| <div key={category} className={styles.label}> | ||
| <Icon className={`${styles.label__icon} ${styles[`label__icon--${styleKey}`]}`} /> | ||
| <span className={`${styles.label__text} ${styles[`label__text--${styleKey}`]} ${styles['label__text--bold']}`}> | ||
| {label} | ||
| </span> | ||
| <span className={`${styles.label__percent} ${styles[`label__percent--${styleKey}`]}`}> | ||
| {percentage === null ? '-' : `${percentage}%`} | ||
| </span> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
| </section> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: I5-Project/TALKY-OWL
Length of output: 2197
🏁 Script executed:
cat -n src/components/home/StatsCategorySection.tsx | head -30Repository: I5-Project/TALKY-OWL
Length of output: 1548
🏁 Script executed:
cat -n src/components/ui/CategoryFilter.tsx | head -30Repository: I5-Project/TALKY-OWL
Length of output: 1413
🏁 Script executed:
Repository: I5-Project/TALKY-OWL
Length of output: 46
🏁 Script executed:
Repository: I5-Project/TALKY-OWL
Length of output: 92
🏁 Script executed:
# Check next-env.d.ts which might provide ambient React types cat -n next-env.d.tsRepository: I5-Project/TALKY-OWL
Length of output: 368
🏁 Script executed:
Repository: I5-Project/TALKY-OWL
Length of output: 263
React.ElementType타입을 명시적 import로 변경하세요.파일에서
React네임스페이스를 import하지 않으면서React.ElementType을 사용하고 있습니다. 현재 TypeScript 설정에서는 타입 해석 오류가 발생할 수 있습니다. 다음과 같이 수정하세요:수정 예시
📝 Committable suggestion
🤖 Prompt for AI Agents