diff --git a/src/App.tsx b/src/App.tsx index fa2ac8f..8bf5acd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -56,6 +56,13 @@ const TeamSpacePage = lazy(() => default: TeamSpacePage, })), ); +const GithubSubtabsPreviewPage = lazy(() => + import("@/pages/github-subtabs-preview-page").then( + ({ GithubSubtabsPreviewPage }) => ({ + default: GithubSubtabsPreviewPage, + }), + ), +); const PrVideoPage = lazy(() => import("@/pages/pr-video-page").then(({ PrVideoPage }) => ({ default: PrVideoPage, @@ -231,6 +238,10 @@ export function App() { } /> } /> } /> + } + /> } /> } /> diff --git a/src/features/team/components/github-subtabs-preview-panel.tsx b/src/features/team/components/github-subtabs-preview-panel.tsx new file mode 100644 index 0000000..25b9765 --- /dev/null +++ b/src/features/team/components/github-subtabs-preview-panel.tsx @@ -0,0 +1,877 @@ +import { + ExternalLink, + FileDiff, + GitPullRequest, + Github, + ListChecks, + Save, + Sparkles, + Trophy, + UserRound, + type LucideIcon, +} from "lucide-react"; +import { type ReactNode, useState } from "react"; + +import { AppPanel, AppPanelHeader } from "@/components/app-shell"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + createGithubSubtabPreviewContribution, + githubSubtabPreviewAvailableRepositories, + githubSubtabPreviewConnectedRepositories, + githubSubtabPreviewProjectGroup, + githubSubtabPreviewWeeklySummaries, +} from "@/features/team/lib/github-subtab-preview"; +import type { ProjectGroupMember } from "@/lib/types/project-group"; +import type { + GithubRepository, + GithubRepositoryContributionResponse, + GithubRepositoryContributor, + GithubWeeklySummary, +} from "@/lib/types/team-space"; +import { cn } from "@/lib/utils"; +import { formatDateTime } from "@/lib/utils/date"; + +const contributionNumberFormatter = new Intl.NumberFormat("ko-KR"); + +type GithubPreviewSubtabId = "contributions" | "weekly-summary" | "integration"; + +const githubPreviewSubtabs: { + icon: LucideIcon; + id: GithubPreviewSubtabId; + label: string; +}[] = [ + { icon: GitPullRequest, id: "contributions", label: "기여도" }, + { icon: Sparkles, id: "weekly-summary", label: "주간 요약" }, + { icon: Github, id: "integration", label: "연동" }, +]; + +export function GithubSubtabsPreviewPanel() { + const [selectedSubtab, setSelectedSubtab] = + useState("contributions"); + + return ( + + preview} + description="기여도, 주간 요약, 연동 설정을 분리해 확인해요." + eyebrow="GitHub App" + title="GitHub 운영" + /> +
+ + + {selectedSubtab === "contributions" ? ( + setSelectedSubtab("integration")} + /> + ) : null} + + {selectedSubtab === "weekly-summary" ? ( + + ) : null} + + {selectedSubtab === "integration" ? ( + + ) : null} +
+
+ ); +} + +function GithubPreviewSubtabList({ + onSelectSubtab, + selectedSubtab, +}: { + onSelectSubtab: (subtab: GithubPreviewSubtabId) => void; + selectedSubtab: GithubPreviewSubtabId; +}) { + return ( +
+ {githubPreviewSubtabs.map(({ icon: Icon, id, label }) => { + const selected = selectedSubtab === id; + + return ( + + ); + })} +
+ ); +} + +function GithubPreviewContributionsPanel({ + onOpenIntegration, +}: { + onOpenIntegration: () => void; +}) { + if (githubSubtabPreviewConnectedRepositories.length === 0) { + return ( + + + 연동 설정 + + } + description="연동 탭에서 GitHub App 설치와 저장소 선택을 마치면 저장소별 기여도가 표시돼요." + icon={GitPullRequest} + title="기여도를 보려면 GitHub 연동이 필요해요." + /> + ); + } + + return ( +
+
+
+

연결된 저장소

+

+ 팀 스페이스에 등록된 GitHub 저장소예요. +

+
+ + {githubSubtabPreviewConnectedRepositories.length}개 + +
+ +
+ {githubSubtabPreviewConnectedRepositories.map((repository) => ( + + ))} +
+
+ ); +} + +function GithubPreviewWeeklySummariesPanel() { + return ( +
+
+
+

팀원 주간 요약

+

+ GitHub PR과 이슈를 바탕으로 팀원별 최근 활동을 확인해요. +

+
+
+ 연결된 요약 + + {githubSubtabPreviewProjectGroup.members.length}명 + +
+
+ +
+ {githubSubtabPreviewProjectGroup.members.map((member) => ( + + ))} +
+
+ ); +} + +function GithubPreviewIntegrationPanel() { + const connectedRepositoryIds = new Set( + githubSubtabPreviewConnectedRepositories.map( + (repository) => repository.githubRepositoryId, + ), + ); + + return ( +
+
+ + + +
+ +
+
+

+ TeamPo GitHub App +

+

+ GitHub 조직이 팀 스페이스에 연결되어 있어요. +

+
+ +
+ +
+
+
+

저장소 설정

+

+ GitHub App이 접근할 수 있는 저장소 중 집계할 대상을 선택해요. +

+
+ + preview + +
+ +
+ {githubSubtabPreviewAvailableRepositories.map((repository) => ( + + ))} + +
+

+ {githubSubtabPreviewConnectedRepositories.length}개 저장소 선택됨 +

+ +
+
+
+
+ ); +} + +function GithubPreviewEmptyState({ + action, + description, + icon: Icon, + title, +}: { + action?: ReactNode; + description: string; + icon: LucideIcon; + title: string; +}) { + return ( +
+
+ +
+

{title}

+

+ {description} +

+ {action ?
{action}
: null} +
+ ); +} + +function GithubPreviewWeeklySummaryCard({ + member, +}: { + member: ProjectGroupMember; +}) { + const summary = githubSubtabPreviewWeeklySummaries[member.userId] ?? null; + + return ( +
+
+
+ +
+
+
+
+

+ {member.nickname} +

+

+ {member.memberRole} +

+
+ {summary ? ( + + PR {contributionNumberFormatter.format(summary.sourcePrCount)} + + ) : ( + 요약 대기 + )} +
+ + {summary ? ( + + ) : ( +

+ 아직 생성된 주간 요약이 없어요. +

+ )} +
+
+
+ ); +} + +function GithubPreviewWeeklySummaryBody({ + summary, +}: { + summary: GithubWeeklySummary; +}) { + return ( +
+
+

+ {formatDateTime(summary.periodStart)} -{" "} + {formatDateTime(summary.periodEnd)} +

+

+ {summary.summary.summary} +

+
+ +
+ + +
+ + + +
+ ); +} + +function GithubPreviewSummaryList({ + items, + title, +}: { + items: string[]; + title: string; +}) { + if (items.length === 0) { + return null; + } + + return ( +
+

{title}

+
    + {items.slice(0, 3).map((item) => ( +
  • + + {item} +
  • + ))} +
+
+ ); +} + +function GithubPreviewStatusCard({ + label, + ready, + value, +}: { + label: string; + ready: boolean; + value: string; +}) { + return ( +
+
+

+ {label} +

+ + {ready ? "ready" : "pending"} + +
+

+ {value} +

+
+ ); +} + +function GithubPreviewRepositoryContributionCard({ + repository, +}: { + repository: GithubRepository; +}) { + const contribution = createGithubSubtabPreviewContribution(repository); + const sortedContributors = [...contribution.contributors].sort( + (left, right) => right.contributionScore - left.contributionScore, + ); + const totals = calculateGithubContributionTotals(contribution); + + return ( +
+ + +
+ + +
+ + + +
+ +
+ {sortedContributors.map((contributor, index) => ( + + ))} +
+
+
+ ); +} + +function calculateGithubContributionTotals( + contribution: GithubRepositoryContributionResponse, +) { + return contribution.contributors.reduce( + (totals, contributor) => ({ + changedFiles: totals.changedFiles + contributor.changedFiles, + highestContributionScore: Math.max( + totals.highestContributionScore, + contributor.contributionScore, + ), + contributionScore: + totals.contributionScore + contributor.contributionScore, + linkedIssueCount: totals.linkedIssueCount + contributor.linkedIssueCount, + mergedPrCount: totals.mergedPrCount + contributor.mergedPrCount, + }), + { + changedFiles: 0, + highestContributionScore: 0, + contributionScore: 0, + linkedIssueCount: 0, + mergedPrCount: 0, + }, + ); +} + +function calculateContributionSharePercent({ + contributionScore, + maxContributionScore, +}: { + contributionScore: number; + maxContributionScore: number; +}) { + if (maxContributionScore <= 0 || contributionScore <= 0) { + return 0; + } + + return Math.round((contributionScore / maxContributionScore) * 100); +} + +function getContributionShareClass(sharePercent: number) { + if (sharePercent >= 92) { + return "w-full"; + } + + if (sharePercent >= 78) { + return "w-5/6"; + } + + if (sharePercent >= 62) { + return "w-2/3"; + } + + if (sharePercent >= 46) { + return "w-1/2"; + } + + if (sharePercent >= 30) { + return "w-1/3"; + } + + if (sharePercent > 0) { + return "w-1/5"; + } + + return "w-0"; +} + +function getRepositoryScoreFillClass(contributionScore: number) { + if (contributionScore >= 100) { + return "w-full"; + } + + if (contributionScore >= 80) { + return "w-5/6"; + } + + if (contributionScore >= 60) { + return "w-2/3"; + } + + if (contributionScore >= 35) { + return "w-1/2"; + } + + if (contributionScore > 0) { + return "w-1/3"; + } + + return "w-0"; +} + +function GithubPreviewContributionOverview({ + topContributor, + totals, +}: { + topContributor: GithubRepositoryContributor | null; + totals: ReturnType; +}) { + const scoreFillClass = getRepositoryScoreFillClass(totals.contributionScore); + + return ( +
+
+
+

+

+
+

+ {contributionNumberFormatter.format(totals.contributionScore)} +

+

+ PR {contributionNumberFormatter.format(totals.mergedPrCount)} · + 이슈 {contributionNumberFormatter.format(totals.linkedIssueCount)} +

+
+
+ + +
+ ); +} + +function GithubPreviewContributionStat({ + icon: Icon, + label, + value, +}: { + icon: LucideIcon; + label: string; + value: number; +}) { + return ( +
+
+ +

{label}

+
+

+ {contributionNumberFormatter.format(value)} +

+
+ ); +} + +function GithubPreviewContributorRow({ + contributor, + maxContributionScore, + rank, +}: { + contributor: GithubRepositoryContributor; + maxContributionScore: number; + rank: number; +}) { + const sharePercent = calculateContributionSharePercent({ + contributionScore: contributor.contributionScore, + maxContributionScore, + }); + const shareClass = getContributionShareClass(sharePercent); + + return ( +
+
+
+ {rank === 1 ? : rank} +
+
+
+
+

+ @{contributor.githubUsername} +

+

+ PR{" "} + {contributionNumberFormatter.format(contributor.mergedPrCount)}{" "} + · 이슈{" "} + {contributionNumberFormatter.format( + contributor.linkedIssueCount, + )} +

+
+ + {contributionNumberFormatter.format( + contributor.contributionScore, + )} + 점 + +
+ +
+
+
+ +
+ + + + +
+
+
+
+ ); +} + +function GithubPreviewContributorMetric({ + label, + tone = "neutral", + value, +}: { + label: string; + tone?: "negative" | "neutral" | "positive"; + value: string; +}) { + const toneClass = { + negative: "text-destructive", + neutral: "text-brand-ink", + positive: "text-chart-3", + }[tone]; + + return ( +
+
{label}
+
+ {value} +
+
+ ); +} + +function GithubPreviewRepositoryOption({ + repository, + selected, +}: { + repository: GithubRepository; + selected: boolean; +}) { + return ( + + ); +} diff --git a/src/features/team/components/real-github-installation-panel.tsx b/src/features/team/components/real-github-installation-panel.tsx index d1985a4..332347c 100644 --- a/src/features/team/components/real-github-installation-panel.tsx +++ b/src/features/team/components/real-github-installation-panel.tsx @@ -12,7 +12,7 @@ import { UserRound, type LucideIcon, } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { type ReactNode, useEffect, useMemo, useState } from "react"; import { AppPanel, AppPanelHeader } from "@/components/app-shell"; import { Badge } from "@/components/ui/badge"; @@ -48,6 +48,18 @@ import { formatDateTime } from "@/lib/utils/date"; const contributionNumberFormatter = new Intl.NumberFormat("ko-KR"); +type GithubSubtabId = "contributions" | "weekly-summary" | "integration"; + +const githubSubtabs: { + icon: LucideIcon; + id: GithubSubtabId; + label: string; +}[] = [ + { icon: GitPullRequest, id: "contributions", label: "기여도" }, + { icon: Sparkles, id: "weekly-summary", label: "주간 요약" }, + { icon: Github, id: "integration", label: "연동" }, +]; + function areNumberSelectionsEqual(left: number[], right: number[]) { if (left.length !== right.length) { return false; @@ -81,16 +93,22 @@ export function RealGithubInstallationPanel({ ); const createInstallUrlMutation = useCreateGithubAppInstallationUrlMutation(); const setGithubRepositoriesMutation = useSetGithubRepositoriesMutation(); + const [selectedGithubSubtab, setSelectedGithubSubtab] = + useState("contributions"); const [feedback, setFeedback] = useState(null); const githubStatus = githubStatusQuery.data; const isGithubConnected = githubStatus?.connected === true; + const shouldLoadGithubRepositories = + isGithubConnected && selectedGithubSubtab !== "weekly-summary"; const githubRepositoriesQuery = useGithubRepositoriesQuery( projectGroup.projectGroupId, - isGithubConnected, + shouldLoadGithubRepositories, ); const availableGithubRepositoriesQuery = useAvailableGithubRepositoriesQuery( projectGroup.projectGroupId, - isGithubConnected && canManageGithubInstallation, + isGithubConnected && + canManageGithubInstallation && + selectedGithubSubtab === "integration", ); const connectedRepositories = githubRepositoriesQuery.data?.repositories ?? []; @@ -151,9 +169,15 @@ export function RealGithubInstallationPanel({ canChangeGithubRepositories && hasRepositorySelectionChanged; const showGithubPolicyNotice = + selectedGithubSubtab === "integration" && githubStatusQuery.isSuccess && canManageGithubInstallation && !hasGithubRepositorySelection; + const shouldOpenGithubRepositorySetup = + githubStatusQuery.isSuccess && + isGithubConnected && + canManageGithubInstallation && + !hasGithubRepositorySelection; useEffect(() => { if (!githubRepositoriesQuery.data) { @@ -163,6 +187,16 @@ export function RealGithubInstallationPanel({ setSelectedGithubRepositoryIds(connectedRepositoryIds); }, [connectedRepositoryIds, githubRepositoriesQuery.data]); + useEffect(() => { + if (!shouldOpenGithubRepositorySetup) { + return; + } + + setSelectedGithubSubtab((current) => + current === "contributions" ? "integration" : current, + ); + }, [shouldOpenGithubRepositorySetup]); + function handleCreateInstallUrl() { setFeedback(null); createInstallUrlMutation.mutate(projectGroup.projectGroupId, { @@ -211,6 +245,11 @@ export function RealGithubInstallationPanel({ ); } + function handleGithubSubtabSelect(subtab: GithubSubtabId) { + setSelectedGithubSubtab(subtab); + setFeedback(null); + } + return ( } - description="GitHub 조직과 저장소 연결 상태를 확인해요." + description="기여도, 주간 요약, 연동 설정을 분리해 확인해요." eyebrow="GitHub App" - title="GitHub 조직 연결" + title="GitHub 운영" />
@@ -243,53 +282,13 @@ export function RealGithubInstallationPanel({ /> ) : null} -
- - - -
- - {showGithubPolicyNotice ? : null} - -
-
-

- TeamPo GitHub App -

-

- {githubStatus?.connected - ? "GitHub 조직이 팀 스페이스에 연결되어 있어요." - : "호스트가 GitHub App 설치를 시작할 수 있어요."} -

-
- -
+ - {isGithubConnected ? ( -
+ {selectedGithubSubtab === "contributions" ? ( + isGithubConnected ? (
@@ -325,10 +324,28 @@ export function RealGithubInstallationPanel({ {githubRepositoriesQuery.isSuccess && connectedRepositories.length === 0 ? ( -
-

- 아직 팀 스페이스에 등록된 GitHub 저장소가 없어요. -

+
+
+

+ 등록된 GitHub 저장소가 없어요. +

+

+ {canManageGithubInstallation + ? "연동 설정에서 집계할 저장소를 선택하면 기여도가 표시돼요." + : "호스트가 연동 설정에서 집계할 저장소를 선택해야 해요."} +

+
+ {canManageGithubInstallation ? ( + + ) : null}
) : null} @@ -342,117 +359,258 @@ export function RealGithubInstallationPanel({ ))}
- -
-
-
-

- 저장소 설정 -

-

- GitHub App이 접근할 수 있는 저장소 중 집계할 대상을 - 선택해요. -

-
- handleGithubSubtabSelect("integration")} + type="button" + variant="outline" > - {canManageGithubInstallation ? "editable" : "read only"} - + + 연동 설정 + + } + description="연동 탭에서 GitHub App 설치와 저장소 선택을 마치면 저장소별 기여도가 표시돼요." + icon={GitPullRequest} + title="기여도를 보려면 GitHub 연동이 필요해요." + /> + ) + ) : null} + + {selectedGithubSubtab === "weekly-summary" ? ( + + ) : null} + + {selectedGithubSubtab === "integration" ? ( +
+
+ + + +
+ + {showGithubPolicyNotice ? : null} + +
+
+

+ TeamPo GitHub App +

+

+ {githubStatus?.connected + ? "GitHub 조직이 팀 스페이스에 연결되어 있어요." + : "호스트가 GitHub App 설치를 시작할 수 있어요."} +

+ +
- {canManageGithubInstallation ? ( -
- {availableGithubRepositoriesQuery.isLoading ? ( - } - message="선택 가능한 저장소를 불러오고 있어요." - /> - ) : null} - - {availableGithubRepositoriesQuery.error ? ( - - ) : null} - - {availableGithubRepositoriesQuery.isSuccess && - availableRepositories.length === 0 ? ( -
-

- GitHub App이 접근할 수 있는 저장소가 없어요. -

-
- ) : null} - - {configurableRepositories.map((repository) => { - const selected = selectedGithubRepositoryIdSet.has( - repository.githubRepositoryId, - ); - const available = availableGithubRepositoryIdSet.has( - repository.githubRepositoryId, - ); - - return ( - +
+
+

+ 저장소 설정 +

+

+ GitHub App이 접근할 수 있는 저장소 중 집계할 대상을 + 선택해요. +

+
+ + {canManageGithubInstallation ? "editable" : "read only"} + +
+ + {canManageGithubInstallation ? ( +
+ {availableGithubRepositoriesQuery.isLoading ? ( + } + message="선택 가능한 저장소를 불러오고 있어요." /> - ); - })} + ) : null} -
-

- {selectedGithubRepositoryIds.length}개 저장소 선택됨 -

- + ); + })} + +
+

+ {selectedGithubRepositoryIds.length}개 저장소 선택됨 +

+ +
-
- ) : ( -
-

- 호스트만 GitHub 저장소 설정을 변경할 수 있어요. -

-
- )} -
+ ) : ( +
+

+ 호스트만 GitHub 저장소 설정을 변경할 수 있어요. +

+
+ )} +
+ ) : null}
) : null} - -
); } +function RealGithubSubtabList({ + onSelectSubtab, + selectedSubtab, +}: { + onSelectSubtab: (subtab: GithubSubtabId) => void; + selectedSubtab: GithubSubtabId; +}) { + return ( +
+ {githubSubtabs.map(({ icon: Icon, id, label }) => { + const selected = selectedSubtab === id; + + return ( + + ); + })} +
+ ); +} + +function RealGithubSubtabEmptyState({ + action, + description, + icon: Icon, + title, +}: { + action?: ReactNode; + description: string; + icon: LucideIcon; + title: string; +}) { + return ( +
+
+ +
+

{title}

+

+ {description} +

+ {action ?
{action}
: null} +
+ ); +} + function RealGithubWeeklySummariesPanel({ isGithubConnected, members, diff --git a/src/features/team/lib/github-subtab-preview.ts b/src/features/team/lib/github-subtab-preview.ts new file mode 100644 index 0000000..c1f45cc --- /dev/null +++ b/src/features/team/lib/github-subtab-preview.ts @@ -0,0 +1,189 @@ +import type { MyProjectGroup } from "@/lib/types/project-group"; +import type { + GithubRepository, + GithubWeeklySummary, +} from "@/lib/types/team-space"; + +export const githubSubtabPreviewProjectGroup = { + currentUserId: 1, + members: [ + { + admin: true, + groupRole: "HOST", + level: 3, + memberRole: "FRONTEND", + nickname: "queue_runner", + profileImage: "https://i.pravatar.cc/240?img=12", + temperature: 50, + userId: 1, + }, + { + admin: false, + groupRole: "MEMBER", + level: 4, + memberRole: "BACKEND", + nickname: "api_builder", + profileImage: null, + temperature: 53, + userId: 2, + }, + { + admin: false, + groupRole: "MEMBER", + level: 3, + memberRole: "FRONTEND", + nickname: "pixel_runner", + profileImage: null, + temperature: 49, + userId: 3, + }, + ], + projectDescription: + "랜덤 팀 매칭 이후 팀이 바로 움직일 수 있도록 규칙과 체크리스트를 정리합니다.", + projectGroupId: 10, + projectMvp: "매칭 요청, 수락/거절, 팀 스페이스 홈, 첫 체크리스트 운영", + projectName: "Blue Sprint", + projectTitle: "Team-po 팀 운영 MVP", +} satisfies MyProjectGroup; + +export const githubSubtabPreviewAvailableRepositories: GithubRepository[] = [ + { + fullName: "team-po-labs/client", + githubRepositoryId: 100, + repoName: "client", + }, + { + fullName: "team-po-labs/server", + githubRepositoryId: 200, + repoName: "server", + }, + { + fullName: "team-po-labs/product-notes", + githubRepositoryId: 300, + repoName: "product-notes", + }, +]; + +export const githubSubtabPreviewConnectedRepositories = + githubSubtabPreviewAvailableRepositories.slice(0, 2); + +export function createGithubSubtabPreviewContribution( + repository: GithubRepository, +) { + const isServerRepository = repository.githubRepositoryId === 200; + + return { + contributors: [ + { + additions: isServerRepository ? 1240 : 860, + changedFiles: isServerRepository ? 42 : 34, + contributionScore: isServerRepository ? 55 : 40, + deletions: isServerRepository ? 310 : 190, + githubUserId: isServerRepository ? 503 : 501, + githubUsername: isServerRepository ? "server-runner" : "dev-a", + linkedIssueCount: isServerRepository ? 3 : 2, + mergedPrCount: isServerRepository ? 4 : 3, + userId: 1, + }, + { + additions: 420, + changedFiles: 18, + contributionScore: 25, + deletions: 80, + githubUserId: 502, + githubUsername: "dev-b", + linkedIssueCount: 1, + mergedPrCount: 2, + userId: 2, + }, + ], + fullName: repository.fullName, + githubRepositoryId: repository.githubRepositoryId, + repoName: repository.repoName, + }; +} + +export const githubSubtabPreviewWeeklySummaries: Record< + number, + GithubWeeklySummary +> = { + 1: { + periodEnd: "2026-06-16T14:59:59.000Z", + periodStart: "2026-06-10T15:00:00.000Z", + sourceIssueCount: 4, + sourcePrCount: 7, + summary: { + followUpSuggestions: [ + "주간 요약 상세 API 응답의 빈 상태를 한 번 더 점검하기", + "GitHub App 설치 완료 후 첫 동기화 안내 문구 정리하기", + ], + issueHighlights: [ + "팀 스페이스 GitHub 운영 탭 정보 구조 개선", + "주간 요약 상세 조회 API 연결", + ], + mainActivities: [ + "GitHub 탭을 기여도, 주간 요약, 연동으로 분리", + "저장소별 PR 기여도 카드의 정보 밀도 조정", + "리뷰 피드백 반영을 위한 프리뷰 라우트 구성", + ], + pullRequestHighlights: [ + "feat(team): add github weekly summary detail api", + "feat(team): add github panel subtabs", + ], + summary: + "GitHub 운영 화면의 정보 구조를 정리하고, 주간 요약 API와 프리뷰 흐름을 연결했어요.", + }, + weeklyGithubSummaryId: 4101, + }, + 2: { + periodEnd: "2026-06-16T14:59:59.000Z", + periodStart: "2026-06-10T15:00:00.000Z", + sourceIssueCount: 2, + sourcePrCount: 4, + summary: { + followUpSuggestions: [ + "서버 OpenAPI 변경분과 FE 타입 이름 맞추기", + "권한 오류 응답 메시지 케이스 추가 확인하기", + ], + issueHighlights: [ + "GitHub weekly summary endpoint 검토", + "팀 스페이스 저장소 목록 동기화 확인", + ], + mainActivities: [ + "GitHub summary API 계약을 점검", + "저장소 연결 상태와 집계 대상 응답을 검증", + ], + pullRequestHighlights: [ + "feat(team): expose weekly github summaries", + "fix(team): align repository sync response", + ], + summary: + "GitHub 요약과 저장소 연동 API 계약을 맞추고, FE에서 사용할 응답 형태를 정리했어요.", + }, + weeklyGithubSummaryId: 4102, + }, + 3: { + periodEnd: "2026-06-16T14:59:59.000Z", + periodStart: "2026-06-10T15:00:00.000Z", + sourceIssueCount: 1, + sourcePrCount: 3, + summary: { + followUpSuggestions: [ + "모바일에서 서브탭 전환 간격 확인하기", + "기여도 카드 숫자 포맷을 실제 데이터로 다시 점검하기", + ], + issueHighlights: ["GitHub 탭 과밀도 개선", "로그인 없는 UI 프리뷰 요청"], + mainActivities: [ + "GitHub 내부 서브탭 상호작용을 점검", + "프리뷰 화면의 fixture 데이터를 보강", + ], + pullRequestHighlights: [ + "feat(team): add github panel subtabs", + "fix(team): stabilize github preview route", + ], + summary: + "GitHub 화면을 더 빠르게 훑어볼 수 있도록 탭 구조와 프리뷰 상태를 다듬었어요.", + }, + weeklyGithubSummaryId: 4103, + }, +}; diff --git a/src/pages/github-subtabs-preview-page.tsx b/src/pages/github-subtabs-preview-page.tsx new file mode 100644 index 0000000..0dc681f --- /dev/null +++ b/src/pages/github-subtabs-preview-page.tsx @@ -0,0 +1,24 @@ +import { GithubSubtabsPreviewPanel } from "@/features/team/components/github-subtabs-preview-panel"; + +export function GithubSubtabsPreviewPage() { + return ( +
+
+
+

+ Preview +

+

+ GitHub 탭 세부 보기 +

+

+ 로그인 없이 GitHub 내부 탭 구조와 각 상태를 확인하는 미리보기 + 화면입니다. +

+
+ + +
+
+ ); +}