mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
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:
@@ -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.'
|
||||
}
|
||||
|
||||
38
src/components/atoms/CompletionCard.tsx
Normal file
38
src/components/atoms/CompletionCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
20
src/components/atoms/DetailRow.tsx
Normal file
20
src/components/atoms/DetailRow.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
28
src/components/atoms/TipsCard.tsx
Normal file
28
src/components/atoms/TipsCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
94
src/hooks/ui/use-dashboard-metrics.ts
Normal file
94
src/hooks/ui/use-dashboard-metrics.ts
Normal 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])
|
||||
}
|
||||
49
src/hooks/ui/use-dashboard-tips.ts
Normal file
49
src/hooks/ui/use-dashboard-tips.ts
Normal 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])
|
||||
}
|
||||
Reference in New Issue
Block a user