-
Notifications
You must be signed in to change notification settings - Fork 3
feat: 내 워크스페이스 페이지 구현 (#8) #10
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
518a078
feat: 내 워크스페이스 페이지 Mock 구현 (#8)
wjswlgh96 a9d9e9d
chore: Tailwind spacing 프리셋 검사 게이트 추가 (#8)
wjswlgh96 f9d6df8
design: 워크스페이스 카드 메타 행 좁은 화면 wrap 처리 (#8)
wjswlgh96 cd57478
design: 히어로 무빙 그라데이션 배경 크기 유틸리티로 정리 (#1)
wjswlgh96 3a245d7
fix: CodeRabbit 리뷰 반영 — 게이트 견고화 및 purpose 폴백 (#8)
wjswlgh96 20f95d0
merge: develop 병합 및 workspace 엔티티 통합 (#8)
wjswlgh96 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
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,87 @@ | ||
| /* eslint-disable no-console -- CLI 검사 스크립트로 콘솔 출력이 본 기능이다 */ | ||
| // Tailwind spacing 임의값 게이트 | ||
| // gap / p* / m* / space 계열에 `-[Npx]`를 쓰면 0.25 단위 프리셋(N÷4)으로 변환하도록 강제한다. | ||
| // 이 규칙은 eslint/prettier로 잡히지 않고 editor의 Tailwind IntelliSense 경고로만 뜨므로, | ||
| // CI(`npm run check`)에서 막아 누구든 놓치지 않게 한다. | ||
| import { readdirSync, readFileSync, statSync } from 'node:fs'; | ||
| import { join } from 'node:path'; | ||
|
|
||
| const ROOT = 'src'; | ||
|
|
||
| // spacing scale를 그대로 쓰는 유틸만 대상으로 한다 (width/height/inset 등 치수는 제외) | ||
| const SPACING_PREFIXES = [ | ||
| 'gap-x', | ||
| 'gap-y', | ||
| 'gap', | ||
| 'px', | ||
| 'py', | ||
| 'pt', | ||
| 'pb', | ||
| 'pl', | ||
| 'pr', | ||
| 'p', | ||
| 'mx', | ||
| 'my', | ||
| 'mt', | ||
| 'mb', | ||
| 'ml', | ||
| 'mr', | ||
| 'm', | ||
| 'space-x', | ||
| 'space-y', | ||
| ]; | ||
|
|
||
| // 예: gap-[15px], sm:p-[21px], -mt-[8px], group-hover:gap-x-[12px] | ||
| const PATTERN = new RegExp( | ||
| // variant prefix는 유한 반복({0,10})으로 바운딩해 중첩 quantifier 백트래킹(ReDoS)을 방지한다 | ||
| '(?:^|[\\s"\'\\x60])(?:[a-z][a-z-]*:){0,10}(-?)(' + | ||
| SPACING_PREFIXES.join('|') + | ||
| ')-\\[(\\d+(?:\\.\\d+)?)px\\]', | ||
| 'g', | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| function walk(dir) { | ||
| const files = []; | ||
| for (const entry of readdirSync(dir)) { | ||
| const full = join(dir, entry); | ||
| if (statSync(full).isDirectory()) files.push(...walk(full)); | ||
| else if (/\.(ts|tsx|js|jsx)$/.test(entry)) files.push(full); | ||
| } | ||
| return files; | ||
| } | ||
|
|
||
| // 주석(//, /* */) 안의 예시 클래스가 오탐되지 않도록 스캔 전 주석을 제거한다 (줄 번호는 유지) | ||
| function stripComments(source) { | ||
| return source | ||
| .replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, ' ')) | ||
| .replace(/\/\/[^\n]*/g, ''); | ||
| } | ||
|
|
||
| const violations = []; | ||
| for (const file of walk(ROOT)) { | ||
| const lines = stripComments(readFileSync(file, 'utf8')).split('\n'); | ||
| lines.forEach((line, index) => { | ||
| for (const match of line.matchAll(PATTERN)) { | ||
| const [, sign, prefix, px] = match; | ||
| violations.push({ | ||
| file, | ||
| line: index + 1, | ||
| from: `${sign}${prefix}-[${px}px]`, | ||
| to: `${sign}${prefix}-${Number(px) / 4}`, | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| if (violations.length > 0) { | ||
| console.error( | ||
| '\n✗ Tailwind spacing 임의값이 발견되었습니다. 0.25 단위 프리셋으로 변환하세요 (N÷4):\n', | ||
| ); | ||
| for (const v of violations) { | ||
| console.error(` ${v.file}:${v.line} ${v.from} → ${v.to}`); | ||
| } | ||
| console.error(`\n총 ${violations.length}건. 예) gap-[15px] → gap-3.75, p-[21px] → p-5.25\n`); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log('✓ Tailwind spacing 프리셋 검사 통과'); | ||
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,10 @@ | ||
| import type { Metadata } from 'next'; | ||
| import { WorkspacesPage } from '@/views/workspaces'; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: '내 워크스페이스 · Syncly', | ||
| }; | ||
|
|
||
| export default function Page() { | ||
| return <WorkspacesPage />; | ||
| } |
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
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,51 @@ | ||
| // get_my_workspaces RPC의 Mock 구현 | ||
| // 백엔드 준비 시 supabase.rpc('get_my_workspaces') 호출로 교체한다 (반환 shape 동일) | ||
| import type { WorkspaceSummary } from '../model/workspace.types'; | ||
|
|
||
| // updated_at은 호출 시점 기준 상대값으로 생성해 "최근 활동" 표기가 자연스럽게 유지되도록 한다 | ||
| type MockWorkspaceSeed = Omit<WorkspaceSummary, 'updated_at' | 'progress'> & { | ||
| activityMinutesAgo: number; | ||
| }; | ||
|
|
||
| const MOCK_WORKSPACE_SEEDS: MockWorkspaceSeed[] = [ | ||
| { | ||
| id: 'ws-capstone-design', | ||
| name: '캡스톤 디자인 팀', | ||
| purpose: 'team-project', | ||
| member_count: 5, | ||
| task_count: 12, | ||
| done_task_count: 7, | ||
| activityMinutesAgo: 10, | ||
| }, | ||
| { | ||
| id: 'ws-fitto-app', | ||
| name: 'Fitto 앱 개발팀', | ||
| purpose: 'side-project', | ||
| member_count: 4, | ||
| task_count: 24, | ||
| done_task_count: 18, | ||
| activityMinutesAgo: 60, | ||
| }, | ||
| { | ||
| id: 'ws-cafe-gray', | ||
| name: '카페 그레이 운영', | ||
| purpose: 'store-operation', | ||
| member_count: 6, | ||
| task_count: 8, | ||
| done_task_count: 5, | ||
| activityMinutesAgo: 180, | ||
| }, | ||
| ]; | ||
|
|
||
| export async function getMyWorkspaces(): Promise<WorkspaceSummary[]> { | ||
| const now = Date.now(); | ||
|
|
||
| return MOCK_WORKSPACE_SEEDS.map(({ activityMinutesAgo, ...summary }) => ({ | ||
| ...summary, | ||
| progress: | ||
| summary.task_count === 0 | ||
| ? 0 | ||
| : Math.round((summary.done_task_count / summary.task_count) * 100), | ||
| updated_at: new Date(now - activityMinutesAgo * 60_000).toISOString(), | ||
| })); | ||
| } |
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,35 @@ | ||
| // purpose별 표시 정보 — ERD에 템플릿 테이블이 없으므로 라벨/아이콘/포인트 컬러는 프론트에서 매핑한다 | ||
| import { GraduationCap, LayoutGrid, ShoppingBag, Zap } from 'lucide-react'; | ||
| import type { LucideIcon } from 'lucide-react'; | ||
| import type { WorkspacePurpose } from '../model/workspace.types'; | ||
|
|
||
| interface WorkspacePurposeMeta { | ||
| label: string; | ||
| icon: LucideIcon; | ||
| gradient: string; | ||
| } | ||
|
|
||
| export const WORKSPACE_PURPOSE_META: Record<WorkspacePurpose, WorkspacePurposeMeta> = { | ||
| 'team-project': { | ||
| label: '팀 프로젝트', | ||
| icon: GraduationCap, | ||
| gradient: 'linear-gradient(135deg, #8e51ff 0%, #615fff 100%)', | ||
| }, | ||
| 'side-project': { | ||
| label: '사이드 프로젝트', | ||
| icon: Zap, | ||
| gradient: 'linear-gradient(135deg, #2b7fff 0%, #00b8db 100%)', | ||
| }, | ||
| 'store-operation': { | ||
| label: '매장 운영', | ||
| icon: ShoppingBag, | ||
| gradient: 'linear-gradient(135deg, #fe9a00 0%, #ff6900 100%)', | ||
| }, | ||
| }; | ||
|
|
||
| // purpose가 매핑에 없을 때(백엔드 연동 후 값 불일치 등) 사용하는 중립 표시 정보 | ||
| export const FALLBACK_PURPOSE_META: WorkspacePurposeMeta = { | ||
| label: '워크스페이스', | ||
| icon: LayoutGrid, | ||
| gradient: 'linear-gradient(135deg, #7b7fa8 0%, #5b4ee8 100%)', | ||
| }; |
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 |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| // 목업 워크스페이스 데이터와 타입의 공개 API입니다. | ||
| export type { Workspace, WorkspacePurpose } from './model/workspace.types'; | ||
| // workspace 엔티티 Public API | ||
| export type { Workspace, WorkspacePurpose, WorkspaceSummary } from './model/workspace.types'; | ||
| export { mockWorkspace } from './model/mock-workspace'; | ||
| export { WORKSPACE_PURPOSE_META, FALLBACK_PURPOSE_META } from './config/purpose'; | ||
| export { getMyWorkspaces } from './api/get-my-workspaces'; |
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
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,14 @@ | ||
| // 상대 시간 포맷 — "방금 전 / N분 전 / N시간 전 / N일 전" | ||
| export function formatRelativeTime(isoDate: string): string { | ||
| const diffMs = Date.now() - new Date(isoDate).getTime(); | ||
| const minutes = Math.floor(diffMs / 60_000); | ||
|
|
||
| if (minutes < 1) return '방금 전'; | ||
| if (minutes < 60) return `${minutes}분 전`; | ||
|
|
||
| const hours = Math.floor(minutes / 60); | ||
| if (hours < 24) return `${hours}시간 전`; | ||
|
|
||
| const days = Math.floor(hours / 24); | ||
| return `${days}일 전`; | ||
| } |
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,2 @@ | ||
| // workspaces 뷰의 Public API | ||
| export { default as WorkspacesPage } from './ui/WorkspacesPage'; |
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,36 @@ | ||
| // 내 워크스페이스 페이지 — 헤더 + 참여 중인 워크스페이스 목록(빈 상태 포함)을 조립한다 | ||
| import Link from 'next/link'; | ||
| import { Plus } from 'lucide-react'; | ||
| import { Plus_Jakarta_Sans } from 'next/font/google'; | ||
| import { getMyWorkspaces } from '@/entities/workspace'; | ||
| import { WorkspaceList } from '@/widgets/workspace-list'; | ||
|
|
||
| // Figma 지정 폰트 — 한글은 시스템 폰트로 fallback된다 | ||
| const jakarta = Plus_Jakarta_Sans({ | ||
| subsets: ['latin'], | ||
| weight: ['400', '600', '700', '800'], | ||
| }); | ||
|
|
||
| export default async function WorkspacesPage() { | ||
| const workspaces = await getMyWorkspaces(); | ||
|
|
||
| return ( | ||
| <div className={`${jakarta.className} bg-brand-surface flex min-h-screen flex-col`}> | ||
| <div className="mx-auto flex w-full max-w-3xl flex-1 flex-col px-6 py-6"> | ||
| <header className="flex items-center justify-between gap-4"> | ||
| <div className="flex flex-col"> | ||
| <h1 className="text-brand-ink text-xl leading-7 font-bold">내 워크스페이스</h1> | ||
| <p className="text-brand-muted pt-0.5 text-sm leading-5">참여 중인 워크스페이스 목록</p> | ||
| </div> | ||
| <Link | ||
| href="/workspaces/new" | ||
| className="bg-brand flex shrink-0 items-center gap-2 rounded-[18px] px-4 py-2 text-sm font-semibold text-white" | ||
| > | ||
| <Plus className="size-4" aria-hidden />새 워크스페이스 | ||
| </Link> | ||
| </header> | ||
| <WorkspaceList workspaces={workspaces} /> | ||
| </div> | ||
| </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
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,2 @@ | ||
| // workspace-list 위젯의 Public API | ||
| export { default as WorkspaceList } from './ui/WorkspaceList'; |
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,24 @@ | ||
| import Image from 'next/image'; | ||
|
|
||
| // 참여 중인 워크스페이스가 없을 때의 빈 상태 | ||
| export default function EmptyWorkspaces() { | ||
| return ( | ||
| <div className="flex flex-1 flex-col items-center justify-center gap-3.75 py-20"> | ||
| <Image | ||
| src="/workspaces/empty-workspace.png" | ||
| alt="" | ||
| width={147} | ||
| height={88} | ||
| className="pointer-events-none opacity-[0.56] select-none" | ||
| /> | ||
| <h2 className="text-brand-ink text-2xl leading-[1.4] font-bold tracking-[-0.6px]"> | ||
| 아직 워크스페이스가 없어요 | ||
| </h2> | ||
| <p className="text-brand-muted text-center text-lg leading-[1.4] tracking-[-0.45px]"> | ||
| 팀과 함께 사용할 워크스페이스를 만들어 | ||
| <br /> | ||
| 프로젝트를 효율적으로 관리해보세요. | ||
| </p> | ||
| </div> | ||
| ); | ||
| } |
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.
Uh oh!
There was an error while loading. Please reload this page.