Skip to content

Commit b8c3db5

Browse files
authored
Merge pull request #9 from prgrms-aibe-devcourse/feat/6-workspace-elem-landing-page
feat: 워크스페이스 랜딩 페이지 재설정 및 페이지 요소 여백 재설정
2 parents 91227dd + 380b0b0 commit b8c3db5

3 files changed

Lines changed: 99 additions & 139 deletions

File tree

src/app/components/ChannelPanel.tsx

Lines changed: 69 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,33 @@ interface Channel {
2424
threads: Thread[];
2525
}
2626

27+
interface RepositoryRef {
28+
id: string;
29+
name: string;
30+
}
31+
2732
interface ChannelPanelProps {
33+
repositories?: RepositoryRef[];
2834
onOpenThread?: (message: any) => void;
2935
onOpenInvite?: () => void;
3036
}
3137

32-
export function ChannelPanel({ onOpenThread, onOpenInvite }: ChannelPanelProps) {
38+
const REPO_SEED_THREADS: Record<string, Thread[]> = {
39+
'secureflow': [
40+
{ id: 101, user: '김진현', avatar: '🎨', message: '로그인 페이지 채팅형 전환 애니메이션 확인 부탁드려요.', time: '오늘 10:42', replies: 2, lastReply: '안현' },
41+
{ id: 102, user: '안현', avatar: '👩‍💻', message: '크게 보기 모드에서 헤더 덮는 부분까지 맞췄습니다.', time: '오늘 10:48', replies: 0 }
42+
],
43+
'aichat': [
44+
{ id: 201, user: '김진필', avatar: '👨‍💻', message: '회원 탈퇴와 워크스페이스 삭제 API 명세 추가 예정입니다.', time: '오늘 09:55', replies: 1, lastReply: 'CodeDock' },
45+
{ id: 202, user: 'CodeDock', avatar: 'CD', message: '리포지토리 연동 해제 정책도 문서 목록에 연결해둘게요.', time: '오늘 09:58', replies: 0 }
46+
],
47+
'dashboard': [
48+
{ id: 301, user: '김재준', avatar: '👨‍💼', message: '새로운 디자인 토큰 추가했습니다. 색상 조합이 정말 좋네요!', time: '오늘 14:20', replies: 2, lastReply: '김진현' },
49+
{ id: 302, user: '김진현', avatar: '🎨', message: 'UI 컴포넌트 라이브러리 마이그레이션 완료했습니다.', time: '오늘 14:35', replies: 0 }
50+
]
51+
};
52+
53+
export function ChannelPanel({ repositories = [], onOpenThread, onOpenInvite }: ChannelPanelProps) {
3354
const [selectedChannel, setSelectedChannel] = useState<string>('general');
3455
const [teamChannelsOpen, setTeamChannelsOpen] = useState(true);
3556
const [editingChannelId, setEditingChannelId] = useState<string | null>(null);
@@ -50,114 +71,35 @@ export function ChannelPanel({ onOpenThread, onOpenInvite }: ChannelPanelProps)
5071
const responderTypingTimerRef = useRef<number | null>(null);
5172
const fileInputRef = useRef<HTMLInputElement | null>(null);
5273
const imageInputRef = useRef<HTMLInputElement | null>(null);
53-
const [channelList, setChannelList] = useState<Channel[]>([
54-
{
55-
id: 'general',
56-
name: '일반',
57-
unread: 3,
58-
threads: [
59-
{
60-
id: 1,
61-
user: '김재준',
62-
avatar: '👨‍💼',
63-
message: '이번 주 스프린트 계획 공유드립니다',
64-
time: '10:23 AM',
65-
replies: 3,
66-
lastReply: '안현'
67-
},
68-
{
69-
id: 2,
70-
user: '김진필',
71-
avatar: '👨‍💻',
72-
message: '새로운 API 엔드포인트 추가했습니다. /api/v2/users 확인해주세요',
73-
time: '11:45 AM',
74-
replies: 5,
75-
lastReply: '김재준'
76-
}
77-
]
78-
},
79-
{
80-
id: 'review-room',
81-
name: '리뷰 룸',
82-
unread: 2,
83-
threads: [
84-
{
85-
id: 7,
86-
user: 'CodeDock',
87-
avatar: 'CD',
88-
message: 'PR #234 인증 변경 파일을 먼저 묶었어요. 위험 신호 3건을 확인해 주세요.',
89-
time: '11:12 AM',
90-
replies: 4,
91-
lastReply: '김준우'
92-
},
93-
{
94-
id: 8,
95-
user: '김준우',
96-
avatar: 'PR',
97-
message: 'rate limit 빠진 부분만 체크리스트로 빼줘.',
98-
time: '11:15 AM',
99-
replies: 2,
100-
lastReply: 'CodeDock'
101-
}
102-
]
103-
},
104-
{
105-
id: 'frontend',
106-
name: '프론트엔드',
107-
unread: 0,
108-
threads: [
109-
{
110-
id: 3,
111-
user: '김진현',
112-
avatar: '🎨',
113-
message: '새로운 컴포넌트 라이브러리 도입을 고려해보면 어떨까요?',
114-
time: '2:15 PM',
115-
replies: 8,
116-
lastReply: '김진필'
117-
},
118-
{
119-
id: 4,
120-
user: '안현',
121-
avatar: '👩‍💻',
122-
message: '다크모드 구현 완료했습니다',
123-
time: '3:30 PM',
124-
replies: 2,
125-
lastReply: '김진현'
126-
}
127-
]
128-
},
129-
{
130-
id: 'backend',
131-
name: '백엔드',
132-
unread: 1,
133-
threads: [
134-
{
135-
id: 5,
136-
user: '김진필',
137-
avatar: '👨‍💻',
138-
message: 'DB 최적화 작업 진행 중입니다',
139-
time: '4:00 PM',
140-
replies: 1,
141-
lastReply: '김재준'
142-
}
143-
]
144-
},
145-
{
146-
id: 'design',
147-
name: '디자인',
148-
unread: 0,
149-
threads: [
150-
{
151-
id: 6,
152-
user: '김진현',
153-
avatar: '🎨',
154-
message: '새로운 디자인 토큰 추가했습니다',
155-
time: '5:00 PM',
156-
replies: 0
157-
}
158-
]
159-
}
160-
]);
74+
const [channelList, setChannelList] = useState<Channel[]>(() => {
75+
const fixedChannels: Channel[] = [
76+
{
77+
id: 'general',
78+
name: '일반',
79+
unread: 3,
80+
threads: [
81+
{ id: 1, user: '김재준', avatar: '👨‍💼', message: '이번 주 스프린트 계획 공유드립니다', time: '10:23 AM', replies: 3, lastReply: '안현' },
82+
{ id: 2, user: '김진필', avatar: '👨‍💻', message: '새로운 API 엔드포인트 추가했습니다. /api/v2/users 확인해주세요', time: '11:45 AM', replies: 5, lastReply: '김재준' }
83+
]
84+
},
85+
{
86+
id: 'review-room',
87+
name: '리뷰 룸',
88+
unread: 2,
89+
threads: [
90+
{ id: 7, user: 'CodeDock', avatar: 'CD', message: 'PR #234 인증 변경 파일을 먼저 묶었어요. 위험 신호 3건을 확인해 주세요.', time: '11:12 AM', replies: 4, lastReply: '김준우' },
91+
{ id: 8, user: '김준우', avatar: 'PR', message: 'rate limit 빠진 부분만 체크리스트로 빼줘.', time: '11:15 AM', replies: 2, lastReply: 'CodeDock' }
92+
]
93+
}
94+
];
95+
const repoChannels: Channel[] = repositories.map(repo => ({
96+
id: repo.id,
97+
name: repo.name,
98+
unread: REPO_SEED_THREADS[repo.id] ? 1 : 0,
99+
threads: REPO_SEED_THREADS[repo.id] ?? []
100+
}));
101+
return [...fixedChannels, ...repoChannels];
102+
});
161103

162104
const handleDeleteChannelClick = (channelId: string) => {
163105
if (channelId === 'general') return;
@@ -257,6 +199,24 @@ export function ChannelPanel({ onOpenThread, onOpenInvite }: ChannelPanelProps)
257199
};
258200
}, []);
259201

202+
useEffect(() => {
203+
const repoIds = new Set(repositories.map(r => r.id));
204+
setChannelList(prev => {
205+
const fixed = prev.filter(ch => ch.id === 'general' || ch.id === 'review-room');
206+
const repoChannels = repositories.map(repo => {
207+
const existing = prev.find(ch => ch.id === repo.id);
208+
if (existing) return { ...existing, name: repo.name };
209+
return { id: repo.id, name: repo.name, unread: 0, threads: [] };
210+
});
211+
return [...fixed, ...repoChannels];
212+
});
213+
setSelectedChannel(prev => {
214+
if (prev === 'general' || prev === 'review-room') return prev;
215+
if (!repoIds.has(prev)) return 'general';
216+
return prev;
217+
});
218+
}, [repositories]);
219+
260220
const triggerResponderTyping = () => {
261221
if (responderTypingTimerRef.current) {
262222
window.clearTimeout(responderTypingTimerRef.current);
@@ -379,8 +339,6 @@ export function ChannelPanel({ onOpenThread, onOpenInvite }: ChannelPanelProps)
379339

380340
const getChannelIcon = (channelId: string) => {
381341
if (channelId === 'review-room') return GitPullRequest;
382-
if (channelId === 'frontend') return Code2;
383-
if (channelId === 'backend') return Database;
384342
return Hash;
385343
};
386344

src/app/components/OverviewPanel.tsx

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -202,31 +202,33 @@ export function OverviewPanel({ repositories, selectedRepositoryId, onSelectRepo
202202
<div ref={scrollRootRef} className="codedock-scrollbar-hidden flex h-full min-h-0 flex-col overflow-y-auto px-6 py-6">
203203
<div className="mb-5 flex items-start justify-between gap-4">
204204
<div>
205-
<button
206-
type="button"
207-
onClick={() => setActiveRepositoryId(null)}
208-
className="mb-4 inline-flex items-center gap-2 rounded-full border-0 px-3 py-2 tracking-tight transition-all hover:scale-[1.02]"
209-
style={{
210-
background: "rgba(32, 227, 255, 0.08)",
211-
border: "1px solid rgba(32, 227, 255, 0.18)",
212-
color: "var(--neon-cyan)",
213-
fontSize: 12,
214-
fontWeight: 950,
215-
cursor: "pointer"
216-
}}
217-
>
218-
<ArrowLeft size={14} />
219-
통합 개요
220-
</button>
221-
<div className="mb-3 inline-flex items-center gap-2 rounded-full px-3 py-1.5" style={{
222-
background: "rgba(57, 255, 136, 0.10)",
223-
border: "1px solid rgba(57, 255, 136, 0.22)",
224-
color: "var(--matrix-green)"
225-
}}>
226-
<span className="h-2 w-2 rounded-full" style={{ background: "var(--matrix-green)" }} />
227-
<span className="tracking-tight" style={{ fontSize: 12, fontWeight: 950 }}>
228-
리포지토리 개요
229-
</span>
205+
<div className="flex items-center gap-3 mb-4">
206+
<button
207+
type="button"
208+
onClick={() => setActiveRepositoryId(null)}
209+
className="inline-flex items-center gap-2 rounded-full border-0 px-3 py-2 tracking-tight transition-all hover:scale-[1.02]"
210+
style={{
211+
background: "rgba(32, 227, 255, 0.08)",
212+
border: "1px solid rgba(32, 227, 255, 0.18)",
213+
color: "var(--neon-cyan)",
214+
fontSize: 12,
215+
fontWeight: 950,
216+
cursor: "pointer"
217+
}}
218+
>
219+
<ArrowLeft size={14} />
220+
통합 개요
221+
</button>
222+
<div className="inline-flex items-center gap-2 rounded-full px-3 py-1.5" style={{
223+
background: "rgba(57, 255, 136, 0.10)",
224+
border: "1px solid rgba(57, 255, 136, 0.22)",
225+
color: "var(--matrix-green)"
226+
}}>
227+
<span className="h-2 w-2 rounded-full" style={{ background: "var(--matrix-green)" }} />
228+
<span className="tracking-tight" style={{ fontSize: 12, fontWeight: 950 }}>
229+
리포지토리 개요
230+
</span>
231+
</div>
230232
</div>
231233
<h2 className="m-0 mb-2 tracking-[-0.065em]" style={{ color: "var(--white)", fontSize: 38, fontWeight: 950 }}>
232234
{activeRepository.name}
@@ -235,7 +237,7 @@ export function OverviewPanel({ repositories, selectedRepositoryId, onSelectRepo
235237
{activeDetails.summary}
236238
</p>
237239
</div>
238-
<div className="rounded-2xl px-4 py-3 text-right" style={{
240+
<div className="mt-10 -mr-2 rounded-2xl px-4 py-3 text-center" style={{
239241
background: "rgba(234, 247, 255, 0.045)",
240242
border: "1px solid rgba(32, 227, 255, 0.14)"
241243
}}>

src/app/pages/ChatPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ export function ChatPage() {
360360
const [repositoryFormMode, setRepositoryFormMode] = useState<"create" | "edit" | null>(null);
361361
const [editingRepositoryId, setEditingRepositoryId] = useState<string | null>(null);
362362
const [repositoryNameInput, setRepositoryNameInput] = useState("");
363-
const [selectedChannel, setSelectedChannel] = useState<string>('general');
363+
const [selectedChannel, setSelectedChannel] = useState<string>('overview');
364364
const [messages, setMessages] = useState<Record<string, any[]>>(initialMessages);
365365
const [selectedPR, setSelectedPR] = useState<any>(null);
366366
const [selectedIssue, setSelectedIssue] = useState<any>(null);
@@ -1182,7 +1182,7 @@ export function ChatPage() {
11821182
) : selectedChannel === 'docs' ? (
11831183
<DocsPage embedded />
11841184
) : selectedChannel === 'general' ? (
1185-
<ChannelPanel onOpenThread={handleOpenThread} onOpenInvite={() => setTeamInviteOpen(true)} />
1185+
<ChannelPanel repositories={repositories} onOpenThread={handleOpenThread} onOpenInvite={() => setTeamInviteOpen(true)} />
11861186
) : selectedChannel === 'team' ? (
11871187
<TeamPanel
11881188
onInvite={() => setTeamInviteOpen(true)}

0 commit comments

Comments
 (0)