-
Notifications
You must be signed in to change notification settings - Fork 1
Feat(client): 대시보드 레이아웃 구현 #66
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
Changes from all commits
b3685d6
fa04f9f
568b22a
d0e1bf7
84faa8d
38700f0
f87db9d
ca0b933
5b6fb0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,47 @@ | ||
| import { REMIND_MOCK_DATA } from "@pages/remind/constants"; | ||
| import { Badge, Card } from "@pinback/design-system/ui"; | ||
| import { useState } from "react"; | ||
|
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. 배지 상태가 리스트/카운트에 반영되지 않습니다 배지 클릭 시 state는 바뀌지만 목록은 필터링되지 않고 배지 카운트도 하드코딩되어 있습니다. 최소한 현재 목데이터 기준(예: timeRemaining !== '완료'를 미열람으로 간주)으로 동작하도록 보완해 주세요. -import { useState } from "react";
+import { useMemo, useState } from "react";- const [activeBadge, setActiveBadge] = useState('all');
+ const [activeBadge, setActiveBadge] = useState<'all' | 'notRead'>('all');- const handleBadgeClick = (badgeType: string) => {
+ const handleBadgeClick = (badgeType: 'all' | 'notRead') => {
setActiveBadge(badgeType);
};- countNum={5}
+ countNum={counts.all}- countNum={10}
+ countNum={counts.notRead}-{REMIND_MOCK_DATA.map((data) => (
+{filtered.map((data) => (아래 코드를 Line 10 바로 아래에 추가해 주세요(새 라인 추가): // 임시 기준: '완료'가 아닌 항목을 '안 읽음'으로 간주
const filtered = useMemo(() => {
if (activeBadge === 'notRead') {
return REMIND_MOCK_DATA.filter((item) => item.timeRemaining !== '완료');
}
return REMIND_MOCK_DATA;
}, [activeBadge]);
const counts = useMemo(() => {
const all = REMIND_MOCK_DATA.length;
const notRead = REMIND_MOCK_DATA.filter((item) => item.timeRemaining !== '완료').length;
return { all, notRead };
}, []);Also applies to: 6-6, 8-10, 18-18, 24-24, 32-32 🤖 Prompt for AI Agents |
||
|
|
||
| const MyBookmark = () => { | ||
| return <div>MyBookmark</div>; | ||
| const [activeBadge, setActiveBadge] = useState('all'); | ||
|
|
||
| const handleBadgeClick = (badgeType: string) => { | ||
| setActiveBadge(badgeType); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col pl-[8rem] py-[5.2rem]"> | ||
| <p className="head3">나의 북마크</p> | ||
| <div className="mt-[3rem] flex gap-[2.4rem]"> | ||
| <Badge | ||
| text="전체보기" | ||
| countNum={5} | ||
| onClick={() => handleBadgeClick('all')} | ||
| isActive={activeBadge === 'all'} | ||
| /> | ||
| <Badge | ||
| text="안 읽음" | ||
| countNum={10} | ||
| onClick={() => handleBadgeClick('notRead')} | ||
| isActive={activeBadge === 'notRead'} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="scrollbar-hide mt-[2.6rem] flex flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth max-w-[104rem]"> | ||
| {/* TODO: API 연결 후 수정 */} | ||
| {REMIND_MOCK_DATA.map((data) => ( | ||
| <Card | ||
| key={data.id} | ||
| type="bookmark" | ||
| title={data.title} | ||
| content={data.content} | ||
| category={data.category} | ||
| date="2024.08.15" | ||
| /> | ||
|
Comment on lines
+37
to
+40
Member
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. 요거 static한 데이터!! 추후에 바꿔주세요
Member
Author
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. 넵~ 일단 카드에 쓰인 데이터 모두 mock data여서 api연결 후에 바꾸겠습니다 👍 |
||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MyBookmark; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,47 @@ | ||
| import { Badge, Card } from '@pinback/design-system/ui'; | ||
| import { useState } from 'react'; | ||
| import { REMIND_MOCK_DATA } from './constants'; | ||
|
|
||
| const Remind = () => { | ||
| return <div>Remind</div>; | ||
| const [activeBadge, setActiveBadge] = useState('notRead'); | ||
|
|
||
| const handleBadgeClick = (badgeType: string) => { | ||
| setActiveBadge(badgeType); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex flex-col pl-[8rem] py-[5.2rem]"> | ||
| <p className="head3">리마인드</p> | ||
| <div className="mt-[3rem] flex gap-[2.4rem]"> | ||
| <Badge | ||
| text="안 읽음" | ||
| countNum={5} | ||
| onClick={() => handleBadgeClick('notRead')} | ||
| isActive={activeBadge === 'notRead'} | ||
| /> | ||
| <Badge | ||
| text="읽음" | ||
| countNum={10} | ||
| onClick={() => handleBadgeClick('read')} | ||
| isActive={activeBadge === 'read'} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="scrollbar-hide mt-[2.6rem] flex flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth max-w-[104rem]"> | ||
| {/* TODO: API 연결 후 수정 */} | ||
| {REMIND_MOCK_DATA.map((data) => ( | ||
| <Card | ||
| key={data.id} | ||
| type="remind" | ||
| title={data.title} | ||
| content={data.content} | ||
| timeRemaining={data.timeRemaining} | ||
| category={data.category} | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Remind; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| export const REMIND_MOCK_DATA = [ | ||
| { | ||
| id: 1, | ||
| title: '리액트 쿼리 강의 듣기', | ||
| content: '1강부터 5강까지 완강하고 복습하기', | ||
| timeRemaining: '1시간 30분', | ||
| category: '공부', | ||
| }, | ||
| { | ||
| id: 2, | ||
| title: '장보기', | ||
| content: '우유, 계란, 채소 구매', | ||
| timeRemaining: '3시간 00분', | ||
| category: '생활', | ||
| }, | ||
| { | ||
| id: 3, | ||
| title: '프로젝트 기획서 초안 작성', | ||
| content: '주요 기능 및 화면 흐름 정리', | ||
| timeRemaining: '완료', | ||
| category: '업무', | ||
| }, | ||
| { | ||
| id: 4, | ||
| title: '헬스장 가기', | ||
| content: '하체 운동 루틴 진행', | ||
| timeRemaining: '5시간 10분', | ||
| category: '운동', | ||
| }, | ||
| { | ||
| id: 5, | ||
| title: '주간 회고 작성', | ||
| content: '이번 주에 배운 점과 개선할 점 정리', | ||
| timeRemaining: '완료', | ||
| category: '자기계발', | ||
| }, | ||
| { | ||
| id: 6, | ||
| title: '저녁 약속', | ||
| content: '강남역 2번 출구에서 친구 만나기', | ||
| timeRemaining: '8시간 00분', | ||
| category: '약속', | ||
| }, | ||
| { | ||
| id: 7, | ||
| title: '알고리즘 문제 풀기', | ||
| content: '백준 골드 문제 2개 풀기', | ||
| timeRemaining: '2시간 45분', | ||
| category: '공부', | ||
| }, | ||
| { | ||
| id: 8, | ||
| title: '이메일 회신', | ||
| content: 'A사에서 온 제휴 문의 메일 확인하고 회신', | ||
| timeRemaining: '완료', | ||
| category: '업무', | ||
| }, | ||
| { | ||
| id: 9, | ||
| title: '방 청소하기', | ||
| content: '책상 정리 및 바닥 청소기 돌리기', | ||
| timeRemaining: '0시간 50분', | ||
| category: '생활', | ||
| }, | ||
| { | ||
| id: 10, | ||
| title: 'TypeScript 스터디 준비', | ||
| content: '제네릭 파트 발표 자료 만들기', | ||
| timeRemaining: '10시간 00분', | ||
| category: '공부', | ||
| }, | ||
| { | ||
| id: 11, | ||
| title: '은행 업무 보기', | ||
| content: '만기 된 예금 재예치하기', | ||
| timeRemaining: '완료', | ||
| category: '금융', | ||
| }, | ||
| { | ||
| id: 12, | ||
| title: '블로그 글 작성', | ||
| content: '이번 주에 해결한 기술적 문제에 대해 포스팅하기', | ||
| timeRemaining: '1일 2시간', | ||
| category: '자기계발', | ||
| }, | ||
| { | ||
| id: 13, | ||
| title: '가족과 통화하기', | ||
| content: '부모님께 안부 전화드리기', | ||
| timeRemaining: '4시간 20분', | ||
| category: '약속', | ||
| }, | ||
| { | ||
| id: 14, | ||
| title: '주말 산책 계획', | ||
| content: '가까운 공원 산책 코스 찾아보기', | ||
| timeRemaining: '2일 0시간', | ||
| category: '여가', | ||
| }, | ||
| { | ||
| id: 15, | ||
| title: '팀 회의록 정리', | ||
| content: '어제 진행된 스프린트 계획 회의 내용 정리해서 공유', | ||
| timeRemaining: '완료', | ||
| category: '업무', | ||
| }, | ||
| { | ||
| id: 16, | ||
| title: '팀 회의록 정리', | ||
| content: '어제 진행된 스프린트 계획 회의 내용 정리해서 공유', | ||
| timeRemaining: '완료', | ||
| category: '업무', | ||
| }, | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,50 @@ | ||
| import { cva } from 'class-variance-authority'; | ||
| import { useState } from 'react'; | ||
| export interface BadgeProps { | ||
| text: string; | ||
| countNum?: number; | ||
| isActive: boolean; | ||
| onClick?: () => void; | ||
|
Comment on lines
4
to
+6
Member
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. 활성 제어를 위해서 추가가 필요했군녀
Member
Author
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. 맞아요! 실제 작동하는 로직은 외부에 책임을 두고 내부에서는 |
||
| } | ||
|
|
||
| const BadgeTxtStyleVariants = cva('sub3-b', { | ||
| variants: { | ||
| click: { | ||
| active: { | ||
| true: 'text-font-black-1', | ||
| false: 'text-font-ltgray-4', | ||
| } as const, | ||
| }, | ||
| defaultVariants: { | ||
| click: false, | ||
| active: false, | ||
| }, | ||
| }); | ||
|
|
||
| const BadgeStyleVariants = cva( | ||
| 'text-white-bg sub5-sb rounded-[0.4rem] px-[0.8rem] py-[0.4rem]', | ||
| { | ||
| variants: { | ||
| click: { | ||
| active: { | ||
| true: 'bg-main500', | ||
| false: 'bg-gray300', | ||
| } as const, | ||
| }, | ||
| defaultVariants: { | ||
| click: false, | ||
| active: false, | ||
| }, | ||
| } | ||
| ); | ||
| const Badge = ({ text, countNum }: BadgeProps) => { | ||
| const [isClick, setIsClick] = useState(false); | ||
|
|
||
| const Badge = ({ text, countNum, isActive, onClick }: BadgeProps) => { | ||
| return ( | ||
| <div | ||
| className="flex cursor-pointer items-center justify-center gap-[0.8rem]" | ||
| onClick={() => setIsClick(true)} | ||
| onClick={onClick} | ||
| > | ||
| <span className={BadgeTxtStyleVariants({ click: isClick })}>{text}</span> | ||
| <span className={BadgeStyleVariants({ click: isClick })}>{countNum}</span> | ||
| <span className={BadgeTxtStyleVariants({ active: isActive })}> | ||
| {text} | ||
| </span> | ||
| <span className={BadgeStyleVariants({ active: isActive })}> | ||
| {countNum} | ||
| </span> | ||
|
Comment on lines
+36
to
+47
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. 🛠️ Refactor suggestion div+onClick은 키보드 접근 불가 — 네이티브 button으로 교체 필요 현재 구조는 스크린리더/키보드 사용자가 접근하기 어렵습니다. design-system 컴포넌트이므로 초기에 바로잡는 게 좋습니다. -const Badge = ({ text, countNum, isActive, onClick }: BadgeProps) => {
+const Badge = ({ text, countNum, isActive = false, onClick, ...rest }: BadgeProps) => {
return (
- <div
- className="flex cursor-pointer items-center justify-center gap-[0.8rem]"
- onClick={onClick}
- >
+ <button
+ type="button"
+ aria-pressed={isActive}
+ onClick={onClick}
+ className="flex cursor-pointer items-center justify-center gap-[0.8rem] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-main500 focus-visible:ring-offset-2"
+ {...rest}
+ >
<span className={BadgeTxtStyleVariants({ active: isActive })}>
{text}
</span>
<span className={BadgeStyleVariants({ active: isActive })}>
{countNum}
</span>
- </div>
+ </button>
);
};🤖 Prompt for AI Agents |
||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
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.
💡 Verification agent
🧩 Analysis chain
불리언 리터럴 제거(축약형 사용) + 활성 상태 의도 확인
JSX에서는
isActive가true일 때 속성 축약이 가능합니다. 또한 이 배지가 Level 페이지에서 항상 활성 스타일이어야 하는지 의도를 한 번만 확인해주세요(토글/선택 개념이 아니라 상시 표시라면 OK).다음처럼 간소화 가능:
리팩터 후 디자인시스템 변경(
isActive필수)에 따라 다른 사용처 누락이 없는지 확인하려면 아래 스크립트를 실행하세요.🏁 Script executed:
Length of output: 84
기존 스크립트가 bash의 프로세스 치환과
rg --type=tsx인식 오류로 실패했습니다. 아래 스크립트로 Badge 사용처를 다시 조회하고isActive누락 여부를 확인해 주세요.🏁 Script executed:
Length of output: 178
아래 스크립트는 중괄호를 이스케이프하고 PCRE 옵션(-P)을 사용해 Badge import 파일을 찾은 뒤
isActive누락 여부를 확인합니다. 실행 후 결과를 공유해주세요.🏁 Script executed:
Length of output: 280
아래 스크립트로 Badge 사용처를 다시 검사하고,
isActive속성 누락 여부를 확인해주세요. 실행 후 결과를 공유해 주세요.🏁 Script executed:
Length of output: 60
아래 스크립트로 Badge 사용처 전부를 나열하고
isActive누락 여부를 동시에 확인해 보세요. 실행 후 결과를 공유해 주세요.🏁 Script executed:
Length of output: 387
Badge 컴포넌트 isActive prop 일관성 적용
isActive={true}→isActive로 축약isActiveprop 누락 → 디자인 시스템 변경(isActive필수)에 따라 추가 필요🤖 Prompt for AI Agents