reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Overview
Start
Next.js 설치
Private install
Core Concepts
아키텍처
Composition Patterns
Accessibility
Keyboard Patterns
Styling
Theme System
Advanced Patterns
Build & Operate
Skills
AI Integration
CLI (opt surface add)
Dependency Graph
Tools
Canvas Catalog
Theme Builder
Form Builder
Templates
Templates
Releases
Release Notes
Oopt-ui
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

ProjectDashboard

surface

Vercel 스타일 프로젝트 개요 영역. DashboardGrid + DeploymentTimeline + EnvPanel 조합.

컴포넌트 의존 관계

깊이
▼ USES (4)ProjectDashboarddashboard-griddeployment-timelineenv-panelloading-overlay
100%

기본 사용

my-awesome-app

Next.js 애플리케이션

프로젝트 통계

총 배포1,234↑ +12.5%
에러율0.23%↓ -5.2%
평균 지연시간45ms→ +2ms
가동 시간99.9%↑ +0.1%

최근 배포

환경 변수

테스트 커버리지

2026년 2월 4일6/6 통과
6성공
0실패
6전체
  • 통계 그리드를 렌더링한다
  • 배포 타임라인을 렌더링한다
  • 환경 변수 패널을 렌더링한다
  • 커스텀 제목을 표시한다
  • header와 actions 슬롯을 렌더링한다
  • 빈 상태를 표시한다

ProjectDashboard Props

Prop타입기본값설명
statsStatCard[]—통계 카드 데이터 배열
deploymentsDeploymentStep[]—배포 단계 배열
envGroupsEnvGroupDef[]—환경 변수 그룹 배열
statsColumnsnumber3통계 그리드 컬럼 수
statsTitlestring"프로젝트 통계"통계 섹션 제목
deploymentsTitlestring"최근 배포"배포 섹션 제목
envTitlestring"환경 변수"환경 변수 섹션 제목
headerReactNode—상단 헤더 영역 콘텐츠
actionsReactNode—헤더 우측 액션 버튼 영역
classNamestring—최외곽 컨테이너 CSS 클래스

Surface 설치

CLI가 공식 배포 채널입니다. 필요한 Surface를 프로젝트로 복사한 뒤 직접 수정할 수 있습니다.

bash
npx @reopt-ai/opt-cli surface add project-dashboard

Consumer target

복사된 파일은 components/surfaces 아래에 저장됩니다.

tsx
import { ProjectDashboard } from "@/components/surfaces/project-dashboard";

Registry metadata

설명
Vercel 스타일 프로젝트 개요 영역. DashboardGrid + DeploymentTimeline + EnvPanel 조합.
파일 수
1개
Registry dependencies
없음
Package dependencies
lucide-react
태그
dashboard
Install notes
  • Install additional packages: lucide-react.

포함 파일

  • project-dashboard.tsx→project-dashboard.tsx
Surface 소스 보기
project-dashboard.tsx
import type { ReactNode } from "react";
import { ChartColumn } from "lucide-react";
import { DashboardGrid, DeploymentTimeline, EnvPanel, SurfaceLayout, cn, OPT_TEXT_PRIMARY, OPT_TEXT_SECONDARY } from "@reopt-ai/opt-ui";
import type { StatCard as StatCardType, DeploymentStep, EnvGroupDef } from "@reopt-ai/opt-ui";

/** Localized labels for the project dashboard component. */
export interface ProjectDashboardLabels {
  stats?: string;
  deployments?: string;
  env?: string;
  emptyTitle?: string;
  emptyDescription?: string;
}

const defaultLabels: Required<ProjectDashboardLabels> = {
  stats: "프로젝트 통계",
  deployments: "최근 배포",
  env: "환경 변수",
  emptyTitle: "데이터가 없습니다",
  emptyDescription:
    "프로젝트 통계, 배포 이력, 환경 변수 중 하나 이상을 전달하세요.",
};

/** Props for the project dashboard component. */
export interface ProjectDashboardProps {
  stats?: StatCardType[];
  deployments?: DeploymentStep[];
  envGroups?: EnvGroupDef[];
  statsColumns?: number;
  statsTitle?: string;
  deploymentsTitle?: string;
  envTitle?: string;
  header?: ReactNode;
  actions?: ReactNode;
  /** Custom visualization section rendered below stats (e.g., TrendChart, CalendarHeatmap) */
  chartSection?: ReactNode;
  className?: string;
  loading?: boolean;
  labels?: ProjectDashboardLabels;
}

/** Renders the project dashboard component. */
export function ProjectDashboard({
  stats,
  deployments,
  envGroups,
  statsColumns = 3,
  statsTitle,
  deploymentsTitle,
  envTitle,
  header,
  actions,
  chartSection,
  className,
  loading = false,
  labels: customLabels,
}: ProjectDashboardProps) {
  const labels = { ...defaultLabels, ...customLabels };
  const hasStats = stats && stats.length > 0;
  const hasDeployments = deployments && deployments.length > 0;
  const hasEnvGroups = envGroups && envGroups.length > 0;

  return (
    <SurfaceLayout
      loading={loading}
      className={className}
      data-opt-id="3E50W"
      data-opt-slug="project-dashboard.ProjectDashboard"
    >
      {/* 헤더 영역 */}
      {(header || actions) && (
        <div className="flex items-center justify-between">
          {header && <div className="gap-element flex flex-col">{header}</div>}
          {actions && (
            <div className="gap-element flex items-center">{actions}</div>
          )}
        </div>
      )}

      {/* 통계 그리드 */}
      {hasStats && (
        <section aria-labelledby="stats-heading">
          <h3
            id="stats-heading"
            className="text-text-primary mb-4 text-lg font-semibold"
          >
            {statsTitle ?? labels.stats}
          </h3>
          <DashboardGrid stats={stats} columns={statsColumns} />
        </section>
      )}

      {/* 차트/시각화 영역 */}
      {chartSection}

      {/* 배포 및 환경변수 2컬럼 레이아웃 */}
      {(hasDeployments || hasEnvGroups) && (
        <div
          className={cn(
            "gap-section grid",
            hasDeployments && hasEnvGroups
              ? "lg:grid-cols-2"
              : "lg:grid-cols-1",
          )}
        >
          {/* 배포 타임라인 */}
          {hasDeployments && (
            <section
              aria-labelledby="deployments-heading"
              className="border-border bg-surface rounded-xl border p-6"
            >
              <h3
                id="deployments-heading"
                className="text-text-primary mb-4 text-lg font-semibold"
              >
                {deploymentsTitle ?? labels.deployments}
              </h3>
              <DeploymentTimeline steps={deployments} title="" />
            </section>
          )}

          {/* 환경변수 패널 */}
          {hasEnvGroups && (
            <section
              aria-labelledby="env-heading"
              className="border-border bg-surface rounded-xl border p-6"
            >
              <h3
                id="env-heading"
                className="text-text-primary mb-4 text-lg font-semibold"
              >
                {envTitle ?? labels.env}
              </h3>
              <EnvPanel groups={envGroups} />
            </section>
          )}
        </div>
      )}

      {/* 빈 상태 */}
      {!hasStats && !hasDeployments && !hasEnvGroups && !chartSection && (
        <div className="flex flex-col items-center justify-center py-16 text-center">
          <ChartColumn className="mb-4 size-10" aria-hidden />
          <h3 className={cn("text-lg font-medium", OPT_TEXT_PRIMARY)}>
            {labels.emptyTitle}
          </h3>
          <p className={cn("mt-1 text-sm", OPT_TEXT_SECONDARY)}>
            {labels.emptyDescription}
          </p>
        </div>
      )}
    </SurfaceLayout>
  );
}