bytelyst-devops-tools/dashboard/web/src/app/code-quality/page.tsx
root b35de88b08 feat(devops-web): fix responsive layout and add comprehensive dashboard pages
- Fix sidebar layout: use flexbox instead of margin-left approach
- Update sidebar to use responsive display (hidden on mobile, static on desktop)
- Fix mobile overlay z-index and positioning issues
- Add proper flex container structure to all pages
- Add new dashboard pages: health, metrics, system, env, code-quality, settings/cosmos
- Add comprehensive API client and type definitions
- Add error boundary and log viewer components
- Add test infrastructure with Vitest and Playwright
- Add Docker configuration and deployment scripts

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-11 03:10:31 +00:00

344 lines
15 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Play, CheckCircle, XCircle, AlertTriangle, FileText, Clock, Code2, Bug, Loader2 } from 'lucide-react';
import { runCodeQualityCheck, type CodeQualityReport, type CodeQualityIssue } from '@/lib/api';
import { SidebarNav } from '@/components/sidebar-nav';
export default function CodeQualityPage() {
const [projectPath, setProjectPath] = useState('');
const [projectId, setProjectId] = useState('');
const [selectedChecks, setSelectedChecks] = useState<Array<'typescript' | 'eslint' | 'build' | 'test'>>([
'typescript',
'eslint',
'build',
'test',
]);
const [loading, setLoading] = useState(false);
const [report, setReport] = useState<CodeQualityReport | null>(null);
const [error, setError] = useState<string | null>(null);
const handleRunCheck = async () => {
if (!projectPath || !projectId) {
setError('Please provide project ID and path');
return;
}
setLoading(true);
setError(null);
setReport(null);
try {
const result = await runCodeQualityCheck({
projectId,
projectPath,
checks: selectedChecks,
});
setReport(result);
} catch (err) {
setError('Failed to run code quality check');
console.error(err);
} finally {
setLoading(false);
}
};
const toggleCheck = (check: 'typescript' | 'eslint' | 'build' | 'test') => {
setSelectedChecks(prev =>
prev.includes(check) ? prev.filter(c => c !== check) : [...prev, check]
);
};
const getIssueIcon = (type: string) => {
switch (type) {
case 'error':
return <XCircle className="w-4 h-4 text-red-600" />;
case 'warning':
return <AlertTriangle className="w-4 h-4 text-yellow-600" />;
case 'info':
return <FileText className="w-4 h-4 text-blue-600" />;
default:
return null;
}
};
const getCategoryIcon = (category: string) => {
switch (category) {
case 'typescript':
return <Code2 className="w-4 h-4 text-blue-600" />;
case 'eslint':
return <FileText className="w-4 h-4 text-purple-600" />;
case 'build':
return <Play className="w-4 h-4 text-green-600" />;
case 'test':
return <Bug className="w-4 h-4 text-orange-600" />;
default:
return <FileText className="w-4 h-4 text-gray-600" />;
}
};
return (
<div className="flex min-h-screen bg-gray-50">
<SidebarNav />
<main className="flex-1 min-w-0 overflow-y-auto">
<div className="p-8 max-md:p-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Code Quality Analysis</h1>
<p className="text-gray-600 dark:text-gray-400 mt-2">
Run TypeScript, ESLint, build, and test checks on your projects
</p>
</div>
{/* Configuration */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">Configuration</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Project ID
</label>
<input
type="text"
value={projectId}
onChange={(e) => setProjectId(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="e.g., trading-service"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Project Path
</label>
<input
type="text"
value={projectPath}
onChange={(e) => setProjectPath(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
placeholder="e.g., /opt/bytelyst/learning_ai_invt_trdg"
/>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Checks to Run
</label>
<div className="flex flex-wrap gap-2">
{(['typescript', 'eslint', 'build', 'test'] as const).map((check) => (
<button
key={check}
onClick={() => toggleCheck(check)}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
selectedChecks.includes(check)
? 'bg-blue-600 text-white'
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
>
{check.charAt(0).toUpperCase() + check.slice(1)}
</button>
))}
</div>
</div>
<button
onClick={handleRunCheck}
disabled={loading || selectedChecks.length === 0}
className="flex items-center gap-2 px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Running Checks...
</>
) : (
<>
<Play className="w-4 h-4" />
Run Analysis
</>
)}
</button>
</div>
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 px-4 py-3 rounded-lg mb-6">
{error}
</div>
)}
{/* Results */}
{report && (
<div className="space-y-6">
{/* Summary */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">Summary</h2>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
<div className="text-3xl font-bold text-blue-600 dark:text-blue-400">
{report.summary.totalIssues}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Total Issues</div>
</div>
<div className="bg-red-50 dark:bg-red-900/20 p-4 rounded-lg">
<div className="text-3xl font-bold text-red-600 dark:text-red-400">
{report.summary.errors}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Errors</div>
</div>
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg">
<div className="text-3xl font-bold text-yellow-600 dark:text-yellow-400">
{report.summary.warnings}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Warnings</div>
</div>
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-lg">
<div className="text-3xl font-bold text-green-600 dark:text-green-400">
{report.categories.test.passed}
</div>
<div className="text-sm text-gray-600 dark:text-gray-400">Tests Passed</div>
</div>
</div>
</div>
{/* Categories */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-2 mb-4">
<Code2 className="w-5 h-5 text-blue-600" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">TypeScript</h3>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Errors:</span>
<span className="font-medium text-red-600">{report.categories.typescript.errors}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Warnings:</span>
<span className="font-medium text-yellow-600">{report.categories.typescript.warnings}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Duration:</span>
<span className="font-medium text-gray-900 dark:text-white">
{report.categories.typescript.duration}ms
</span>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-2 mb-4">
<FileText className="w-5 h-5 text-purple-600" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">ESLint</h3>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Errors:</span>
<span className="font-medium text-red-600">{report.categories.eslint.errors}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Warnings:</span>
<span className="font-medium text-yellow-600">{report.categories.eslint.warnings}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Duration:</span>
<span className="font-medium text-gray-900 dark:text-white">
{report.categories.eslint.duration}ms
</span>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-2 mb-4">
<Play className="w-5 h-5 text-green-600" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Build</h3>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Status:</span>
<span className={`font-medium ${report.categories.build.success ? 'text-green-600' : 'text-red-600'}`}>
{report.categories.build.success ? 'Success' : 'Failed'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Errors:</span>
<span className="font-medium text-red-600">{report.categories.build.errors}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Duration:</span>
<span className="font-medium text-gray-900 dark:text-white">
{report.categories.build.duration}ms
</span>
</div>
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center gap-2 mb-4">
<Bug className="w-5 h-5 text-orange-600" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Tests</h3>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Status:</span>
<span className={`font-medium ${report.categories.test.success ? 'text-green-600' : 'text-red-600'}`}>
{report.categories.test.success ? 'Success' : 'Failed'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Passed:</span>
<span className="font-medium text-green-600">{report.categories.test.passed}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Failed:</span>
<span className="font-medium text-red-600">{report.categories.test.failed}</span>
</div>
</div>
</div>
</div>
{/* Issues List */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">Issues</h2>
{report.issues.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<CheckCircle className="w-12 h-12 mx-auto mb-3 text-green-600" />
<p>No issues found!</p>
</div>
) : (
<div className="space-y-3">
{report.issues.map((issue) => (
<div
key={issue.id}
className="flex items-start gap-3 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg"
>
<div className="mt-0.5">{getIssueIcon(issue.type)}</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-gray-900 dark:text-white">{issue.file}</span>
{issue.line && (
<span className="text-sm text-gray-500 dark:text-gray-400">
:{issue.line}
{issue.column && `:${issue.column}`}
</span>
)}
<div className="ml-auto">{getCategoryIcon(issue.category)}</div>
</div>
<p className="text-sm text-gray-700 dark:text-gray-300">{issue.message}</p>
{issue.rule && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">Rule: {issue.rule}</p>
)}
</div>
</div>
))}
</div>
)}
</div>
</div>
)}
</div>
</main>
</div>
);
}