diff --git a/src/components/ProjectDashboard.tsx b/src/components/ProjectDashboard.tsx index 36ac56e..78aa6ec 100644 --- a/src/components/ProjectDashboard.tsx +++ b/src/components/ProjectDashboard.tsx @@ -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({

- - - - - Project Completeness - - Overall progress of your application - - -
- {completionScore}% - = 70 ? 'default' : 'secondary'} className="text-sm"> - {completionScore >= 70 ? 'Ready to Export' : 'In Progress'} - -
- -

- {getCompletionMessage(completionScore)} -

-
-
+
} 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" /> } 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" /> } 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" /> } 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" /> } 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" /> } 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" />
@@ -152,135 +142,27 @@ export function ProjectDashboard({ } label="Playwright Tests" - value={playwrightTests.length} + value={metrics.playwrightCount} /> } label="Storybook Stories" - value={storybookStories.length} + value={metrics.storybookCount} /> } label="Unit Tests" - value={unitTests.length} + value={metrics.unitTestCount} /> } label="Flask Blueprints" - value={flaskConfig.blueprints.length} + value={metrics.blueprintCount} /> - {(totalModels === 0 || totalFiles === 0) && ( - - - - - Quick Tips - - - - {totalFiles === 0 && ( -

• Start by creating some code files in the Code Editor tab

- )} - {totalModels === 0 && ( -

• Define your data models in the Models tab to set up your database

- )} - {totalComponents === 0 && ( -

• Build your UI structure in the Components tab

- )} - {totalThemeVariants <= 1 && ( -

• Create additional theme variants (dark mode) in the Styling tab

- )} - {totalTests === 0 && ( -

• Add tests for better code quality and reliability

- )} -
-
- )} + ) } - -function StatCard({ - icon, - title, - value, - description, - color -}: { - icon: React.ReactNode - title: string - value: number - description: string - color: string -}) { - return ( - - -
-
-

{title}

-

{value}

-

{description}

-
-
{icon}
-
-
-
- ) -} - -function DetailRow({ - icon, - label, - value -}: { - icon: React.ReactNode - label: string - value: number -}) { - return ( -
-
- {icon} - {label} -
- {value} -
- ) -} - -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.' -} diff --git a/src/components/atoms/CompletionCard.tsx b/src/components/atoms/CompletionCard.tsx new file mode 100644 index 0000000..98ebed8 --- /dev/null +++ b/src/components/atoms/CompletionCard.tsx @@ -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 ( + + + + + Project Completeness + + Overall progress of your application + + +
+ {completionScore}% + + {isReadyToExport ? 'Ready to Export' : 'In Progress'} + +
+ +

{completionMessage}

+
+
+ ) +} diff --git a/src/components/atoms/DetailRow.tsx b/src/components/atoms/DetailRow.tsx new file mode 100644 index 0000000..7d46e46 --- /dev/null +++ b/src/components/atoms/DetailRow.tsx @@ -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 ( +
+
+ {icon} + {label} +
+ {value} +
+ ) +} diff --git a/src/components/atoms/TipsCard.tsx b/src/components/atoms/TipsCard.tsx new file mode 100644 index 0000000..eaa6794 --- /dev/null +++ b/src/components/atoms/TipsCard.tsx @@ -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 ( + + + + + Quick Tips + + + + {visibleTips.map((tip, index) => ( +

• {tip.message}

+ ))} +
+
+ ) +} diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index 500a917..c521c4b 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -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' diff --git a/src/hooks/ui/index.ts b/src/hooks/ui/index.ts index 6944a71..2e34ed1 100644 --- a/src/hooks/ui/index.ts +++ b/src/hooks/ui/index.ts @@ -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' diff --git a/src/hooks/ui/use-dashboard-metrics.ts b/src/hooks/ui/use-dashboard-metrics.ts new file mode 100644 index 0000000..bcd4ac7 --- /dev/null +++ b/src/hooks/ui/use-dashboard-metrics.ts @@ -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]) +} diff --git a/src/hooks/ui/use-dashboard-tips.ts b/src/hooks/ui/use-dashboard-tips.ts new file mode 100644 index 0000000..b1de091 --- /dev/null +++ b/src/hooks/ui/use-dashboard-tips.ts @@ -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]) +}