Generated by Spark: Load more of UI from JSON declarations and break up large components into atomic and create hooks as needed

This commit is contained in:
2026-01-17 12:30:47 +00:00
committed by GitHub
parent cd4164deb8
commit 55114937a7
8 changed files with 290 additions and 176 deletions

View File

@@ -1,6 +1,4 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import {
Code,
Database,
@@ -10,12 +8,13 @@ import {
Play,
Cube,
FileText,
CheckCircle,
Warning
} from '@phosphor-icons/react'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig, NextJsConfig } from '@/types/project'
import { SeedDataStatus } from '@/components/atoms'
import { SeedDataStatus, DetailRow, CompletionCard, TipsCard } from '@/components/atoms'
import { GitHubBuildStatus } from '@/components/molecules/GitHubBuildStatus'
import { StatCard } from '@/components/molecules'
import { useDashboardMetrics } from '@/hooks/ui/use-dashboard-metrics'
import { useDashboardTips } from '@/hooks/ui/use-dashboard-tips'
interface ProjectDashboardProps {
files: ProjectFile[]
@@ -29,29 +28,36 @@ interface ProjectDashboardProps {
nextjsConfig: NextJsConfig
}
export function ProjectDashboard({
files,
models,
components,
theme,
playwrightTests,
storybookStories,
unitTests,
flaskConfig,
nextjsConfig,
}: ProjectDashboardProps) {
const totalFiles = files.length
const totalModels = models.length
const totalComponents = components.length
const totalThemeVariants = theme?.variants?.length || 0
const totalEndpoints = flaskConfig.blueprints.reduce((acc, bp) => acc + bp.endpoints.length, 0)
const totalTests = playwrightTests.length + storybookStories.length + unitTests.length
export function ProjectDashboard(props: ProjectDashboardProps) {
const {
files,
models,
components,
theme,
playwrightTests,
storybookStories,
unitTests,
flaskConfig,
nextjsConfig,
} = props
const completionScore = calculateCompletionScore({
files: totalFiles,
models: totalModels,
components: totalComponents,
tests: totalTests,
const metrics = useDashboardMetrics({
files,
models,
components,
theme,
playwrightTests,
storybookStories,
unitTests,
flaskConfig,
})
const tips = useDashboardTips({
totalFiles: metrics.totalFiles,
totalModels: metrics.totalModels,
totalComponents: metrics.totalComponents,
totalThemeVariants: metrics.totalThemeVariants,
totalTests: metrics.totalTests,
})
return (
@@ -63,74 +69,58 @@ export function ProjectDashboard({
</p>
</div>
<Card className="bg-gradient-to-br from-primary/10 to-accent/10 border-primary/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle size={24} weight="duotone" className="text-primary" />
Project Completeness
</CardTitle>
<CardDescription>Overall progress of your application</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-4xl font-bold">{completionScore}%</span>
<Badge variant={completionScore >= 70 ? 'default' : 'secondary'} className="text-sm">
{completionScore >= 70 ? 'Ready to Export' : 'In Progress'}
</Badge>
</div>
<Progress value={completionScore} className="h-3" />
<p className="text-sm text-muted-foreground">
{getCompletionMessage(completionScore)}
</p>
</CardContent>
</Card>
<CompletionCard
completionScore={metrics.completionScore}
completionMessage={metrics.completionMessage}
isReadyToExport={metrics.isReadyToExport}
/>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<StatCard
icon={<Code size={24} weight="duotone" />}
title="Code Files"
value={totalFiles}
description={`${totalFiles} file${totalFiles !== 1 ? 's' : ''} in your project`}
value={metrics.totalFiles}
description={`${metrics.totalFiles} file${metrics.totalFiles !== 1 ? 's' : ''} in your project`}
color="text-blue-500"
/>
<StatCard
icon={<Database size={24} weight="duotone" />}
title="Database Models"
value={totalModels}
description={`${totalModels} Prisma model${totalModels !== 1 ? 's' : ''} defined`}
value={metrics.totalModels}
description={`${metrics.totalModels} Prisma model${metrics.totalModels !== 1 ? 's' : ''} defined`}
color="text-purple-500"
/>
<StatCard
icon={<Tree size={24} weight="duotone" />}
title="Components"
value={totalComponents}
description={`${totalComponents} component${totalComponents !== 1 ? 's' : ''} in tree`}
value={metrics.totalComponents}
description={`${metrics.totalComponents} component${metrics.totalComponents !== 1 ? 's' : ''} in tree`}
color="text-green-500"
/>
<StatCard
icon={<PaintBrush size={24} weight="duotone" />}
title="Theme Variants"
value={totalThemeVariants}
description={`${totalThemeVariants} theme${totalThemeVariants !== 1 ? 's' : ''} configured`}
value={metrics.totalThemeVariants}
description={`${metrics.totalThemeVariants} theme${metrics.totalThemeVariants !== 1 ? 's' : ''} configured`}
color="text-pink-500"
/>
<StatCard
icon={<Flask size={24} weight="duotone" />}
title="API Endpoints"
value={totalEndpoints}
description={`${totalEndpoints} Flask endpoint${totalEndpoints !== 1 ? 's' : ''}`}
value={metrics.totalEndpoints}
description={`${metrics.totalEndpoints} Flask endpoint${metrics.totalEndpoints !== 1 ? 's' : ''}`}
color="text-orange-500"
/>
<StatCard
icon={<Cube size={24} weight="duotone" />}
title="Tests"
value={totalTests}
description={`${totalTests} test${totalTests !== 1 ? 's' : ''} written`}
value={metrics.totalTests}
description={`${metrics.totalTests} test${metrics.totalTests !== 1 ? 's' : ''} written`}
color="text-cyan-500"
/>
</div>
@@ -152,135 +142,27 @@ export function ProjectDashboard({
<DetailRow
icon={<Play size={18} />}
label="Playwright Tests"
value={playwrightTests.length}
value={metrics.playwrightCount}
/>
<DetailRow
icon={<FileText size={18} />}
label="Storybook Stories"
value={storybookStories.length}
value={metrics.storybookCount}
/>
<DetailRow
icon={<Cube size={18} />}
label="Unit Tests"
value={unitTests.length}
value={metrics.unitTestCount}
/>
<DetailRow
icon={<Flask size={18} />}
label="Flask Blueprints"
value={flaskConfig.blueprints.length}
value={metrics.blueprintCount}
/>
</CardContent>
</Card>
{(totalModels === 0 || totalFiles === 0) && (
<Card className="bg-yellow-500/10 border-yellow-500/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Warning size={24} weight="duotone" className="text-yellow-500" />
Quick Tips
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
{totalFiles === 0 && (
<p> Start by creating some code files in the Code Editor tab</p>
)}
{totalModels === 0 && (
<p> Define your data models in the Models tab to set up your database</p>
)}
{totalComponents === 0 && (
<p> Build your UI structure in the Components tab</p>
)}
{totalThemeVariants <= 1 && (
<p> Create additional theme variants (dark mode) in the Styling tab</p>
)}
{totalTests === 0 && (
<p> Add tests for better code quality and reliability</p>
)}
</CardContent>
</Card>
)}
<TipsCard tips={tips} />
</div>
)
}
function StatCard({
icon,
title,
value,
description,
color
}: {
icon: React.ReactNode
title: string
value: number
description: string
color: string
}) {
return (
<Card>
<CardContent className="pt-6">
<div className="flex items-start justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">{title}</p>
<p className="text-3xl font-bold">{value}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
<div className={color}>{icon}</div>
</div>
</CardContent>
</Card>
)
}
function DetailRow({
icon,
label,
value
}: {
icon: React.ReactNode
label: string
value: number
}) {
return (
<div className="flex items-center justify-between py-2 border-b border-border last:border-0">
<div className="flex items-center gap-2">
<span className="text-muted-foreground">{icon}</span>
<span className="text-sm font-medium">{label}</span>
</div>
<Badge variant="secondary">{value}</Badge>
</div>
)
}
function calculateCompletionScore(data: {
files: number
models: number
components: number
tests: number
}): number {
const weights = {
files: 25,
models: 25,
components: 25,
tests: 25,
}
const scores = {
files: Math.min(data.files / 5, 1) * weights.files,
models: Math.min(data.models / 3, 1) * weights.models,
components: Math.min(data.components / 5, 1) * weights.components,
tests: Math.min(data.tests / 5, 1) * weights.tests,
}
return Math.round(
scores.files + scores.models + scores.components + scores.tests
)
}
function getCompletionMessage(score: number): string {
if (score >= 90) return 'Excellent! Your project is comprehensive and ready to deploy.'
if (score >= 70) return 'Great progress! Your project has most essential features.'
if (score >= 50) return 'Good start! Keep adding more features and tests.'
if (score >= 30) return 'Getting there! Add more components and models.'
return 'Just starting! Begin by creating models and components.'
}

View File

@@ -0,0 +1,38 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { CheckCircle } from '@phosphor-icons/react'
interface CompletionCardProps {
completionScore: number
completionMessage: string
isReadyToExport: boolean
}
export function CompletionCard({
completionScore,
completionMessage,
isReadyToExport
}: CompletionCardProps) {
return (
<Card className="bg-gradient-to-br from-primary/10 to-accent/10 border-primary/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CheckCircle size={24} weight="duotone" className="text-primary" />
Project Completeness
</CardTitle>
<CardDescription>Overall progress of your application</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-4xl font-bold">{completionScore}%</span>
<Badge variant={isReadyToExport ? 'default' : 'secondary'} className="text-sm">
{isReadyToExport ? 'Ready to Export' : 'In Progress'}
</Badge>
</div>
<Progress value={completionScore} className="h-3" />
<p className="text-sm text-muted-foreground">{completionMessage}</p>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,20 @@
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
interface DetailRowProps {
icon: React.ReactNode
label: string
value: number
}
export function DetailRow({ icon, label, value }: DetailRowProps) {
return (
<div className="flex items-center justify-between py-2 border-b border-border last:border-0">
<div className="flex items-center gap-2">
<span className="text-muted-foreground">{icon}</span>
<span className="text-sm font-medium">{label}</span>
</div>
<Badge variant="secondary">{value}</Badge>
</div>
)
}

View File

@@ -0,0 +1,28 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Warning } from '@phosphor-icons/react'
interface TipsCardProps {
tips: Array<{ message: string; show: boolean }>
}
export function TipsCard({ tips }: TipsCardProps) {
const visibleTips = tips.filter(tip => tip.show)
if (visibleTips.length === 0) return null
return (
<Card className="bg-yellow-500/10 border-yellow-500/20">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Warning size={24} weight="duotone" className="text-yellow-500" />
Quick Tips
</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
{visibleTips.map((tip, index) => (
<p key={index}> {tip.message}</p>
))}
</CardContent>
</Card>
)
}

View File

@@ -22,5 +22,6 @@ export { BindingIndicator } from './BindingIndicator'
export { StatCard } from './StatCard'
export { LoadingState } from './LoadingState'
export { EmptyState } from './EmptyState'
export { DetailRow } from './DetailRow'
export { CompletionCard } from './CompletionCard'
export { TipsCard } from './TipsCard'

View File

@@ -2,3 +2,5 @@ export { useDataBinding } from './use-data-binding'
export { useEventHandlers } from './use-event-handlers'
export { useSchemaLoader } from './use-schema-loader'
export { useComponentRegistry } from './use-component-registry'
export { useDashboardMetrics } from './use-dashboard-metrics'
export { useDashboardTips } from './use-dashboard-tips'

View File

@@ -0,0 +1,94 @@
import { useMemo } from 'react'
import { ProjectFile, PrismaModel, ComponentNode, ThemeConfig, PlaywrightTest, StorybookStory, UnitTest, FlaskConfig } from '@/types/project'
interface DashboardMetrics {
totalFiles: number
totalModels: number
totalComponents: number
totalThemeVariants: number
totalEndpoints: number
totalTests: number
playwrightCount: number
storybookCount: number
unitTestCount: number
blueprintCount: number
completionScore: number
completionMessage: string
isReadyToExport: boolean
}
interface UseDashboardMetricsProps {
files: ProjectFile[]
models: PrismaModel[]
components: ComponentNode[]
theme: ThemeConfig
playwrightTests: PlaywrightTest[]
storybookStories: StorybookStory[]
unitTests: UnitTest[]
flaskConfig: FlaskConfig
}
function calculateCompletionScore(metrics: {
files: number
models: number
components: number
tests: number
}): number {
let score = 0
if (metrics.files > 0) score += 25
if (metrics.models > 0) score += 25
if (metrics.components > 0) score += 25
if (metrics.tests > 0) score += 25
return score
}
function getCompletionMessage(score: number): string {
if (score >= 90) return 'Excellent! Your project is complete and ready for export.'
if (score >= 70) return 'Great progress! Add a few more tests to reach 100%.'
if (score >= 50) return 'Making good progress. Keep adding features and tests.'
if (score >= 25) return 'Good start! Continue building your application.'
return 'Just getting started. Create files, models, and components to begin.'
}
export function useDashboardMetrics({
files,
models,
components,
theme,
playwrightTests,
storybookStories,
unitTests,
flaskConfig,
}: UseDashboardMetricsProps): DashboardMetrics {
return useMemo(() => {
const totalFiles = files.length
const totalModels = models.length
const totalComponents = components.length
const totalThemeVariants = theme?.variants?.length || 0
const totalEndpoints = flaskConfig.blueprints.reduce((acc, bp) => acc + bp.endpoints.length, 0)
const totalTests = playwrightTests.length + storybookStories.length + unitTests.length
const completionScore = calculateCompletionScore({
files: totalFiles,
models: totalModels,
components: totalComponents,
tests: totalTests,
})
return {
totalFiles,
totalModels,
totalComponents,
totalThemeVariants,
totalEndpoints,
totalTests,
playwrightCount: playwrightTests.length,
storybookCount: storybookStories.length,
unitTestCount: unitTests.length,
blueprintCount: flaskConfig.blueprints.length,
completionScore,
completionMessage: getCompletionMessage(completionScore),
isReadyToExport: completionScore >= 70,
}
}, [files, models, components, theme, playwrightTests, storybookStories, unitTests, flaskConfig])
}

View File

@@ -0,0 +1,49 @@
import { useMemo } from 'react'
interface DashboardTip {
message: string
show: boolean
}
interface UseDashboardTipsProps {
totalFiles: number
totalModels: number
totalComponents: number
totalThemeVariants: number
totalTests: number
}
export function useDashboardTips({
totalFiles,
totalModels,
totalComponents,
totalThemeVariants,
totalTests,
}: UseDashboardTipsProps): DashboardTip[] {
return useMemo(() => {
const tips: DashboardTip[] = [
{
message: 'Start by creating some code files in the Code Editor tab',
show: totalFiles === 0,
},
{
message: 'Define your data models in the Models tab to set up your database',
show: totalModels === 0,
},
{
message: 'Build your UI structure in the Components tab',
show: totalComponents === 0,
},
{
message: 'Create additional theme variants (dark mode) in the Styling tab',
show: totalThemeVariants <= 1,
},
{
message: 'Add tests for better code quality and reliability',
show: totalTests === 0,
},
]
return tips.filter(tip => tip.show)
}, [totalFiles, totalModels, totalComponents, totalThemeVariants, totalTests])
}