- {/* File List */}
-
-
-
Analyzed Files
-
- {data.map((item) => (
-
setSelectedFile(item)}
- className={`p-3 rounded-lg cursor-pointer transition-colors ${
- selectedFile?.id === item.id
- ? 'bg-blue-100 border-2 border-blue-300'
- : 'bg-gray-50 hover:bg-gray-100 border-2 border-transparent'
- }`}
- >
-
- {item.file_name}
-
-
- {item.file_path}
-
-
- {formatAge(item.age)}
-
-
- ))}
-
-
-
-
- {/* Metrics Details */}
-
- {selectedFile ? (
-
-
-
{selectedFile.file_name}
-
- 📁 {selectedFile.file_path}
-
-
- 🕒 Analyzed {selectedFile.readable_timestamp}
-
-
-
-
- {Object.entries(selectedFile.metrics).map(([key, value]) => {
- if (key === 'method_type_count' && typeof value === 'object' && value !== null) {
- return (
-
-
Method Types
-
-
- Public:
- {(value as any).public}
-
-
- Private:
- {(value as any).private}
-
-
-
- );
- }
-
- return (
-
-
- {key.replace(/_/g, ' ')}
-
-
- {formatMetricValue(key, value)}
-
-
- );
- })}
-
- {/* File Info */}
-
-
File Size
-
- {selectedFile.metrics.file_size ? `${(selectedFile.metrics.file_size / 1024).toFixed(2)} KB` : 'N/A'}
-
-
-
-
-
File Extension
-
- {selectedFile.metrics.file_extension || 'N/A'}
-
-
-
-
- ) : (
-
-
Select a file to view its metrics
-
- )}
-
+
+
+
+
+
+
+
+
+
+ {selectedFile ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
diff --git a/spicecloud/pages/utils/styles.ts b/spicecloud/pages/utils/styles.ts
new file mode 100644
index 0000000..5c8c2f0
--- /dev/null
+++ b/spicecloud/pages/utils/styles.ts
@@ -0,0 +1,313 @@
+export const styles = {
+ container: {
+ minHeight: '100vh',
+ background: 'linear-gradient(135deg, #f0e3c8, #e3d6b0, #d7b377)',
+ fontFamily: 'system-ui, -apple-system, sans-serif',
+ color: '#000000'
+ },
+ header: {
+ background: 'rgba(215, 179, 119, 0.9)',
+ backdropFilter: 'blur(10px)',
+ borderBottom: '1px solid rgba(160, 130, 60, 0.8)',
+ position: 'sticky' as const,
+ top: 0,
+ zIndex: 40,
+ padding: '1rem 1.5rem',
+ color: '#000000',
+ },
+ headerContent: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ maxWidth: '1200px',
+ margin: '0 auto'
+ },
+ title: {
+ fontSize: '1.5rem',
+ fontWeight: 'bold',
+ color: '#000000',
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.75rem'
+ },
+ subtitle: {
+ color: 'rgba(0, 0, 0, 0.7)',
+ marginTop: '0.25rem'
+ },
+ refreshButton: {
+ padding: '0.5rem 1rem',
+ borderRadius: '0.5rem',
+ fontWeight: '500',
+ transition: 'all 0.2s',
+ border: 'none',
+ cursor: 'pointer',
+ color: '#000000',
+ },
+ mainContent: {
+ maxWidth: '1200px',
+ margin: '0 auto',
+ padding: '2rem 1.5rem',
+ display: 'grid',
+ gridTemplateColumns: 'minmax(300px, 1fr) 2fr',
+ gap: '2rem'
+ },
+ sidebar: {
+ background: 'rgba(215, 179, 119, 0.25)',
+ backdropFilter: 'blur(10px)',
+ borderRadius: '1rem',
+ padding: '1.5rem',
+ border: '1px solid rgba(160, 130, 60, 0.5)',
+ height: 'fit-content',
+ position: 'sticky' as const,
+ top: '6rem',
+ color: '#000000'
+ },
+ sidebarTitle: {
+ fontSize: '1.25rem',
+ fontWeight: '600',
+ color: '#000000',
+ marginBottom: '1.5rem',
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.5rem'
+ },
+ fileList: {
+ display: 'flex',
+ flexDirection: 'column' as const,
+ gap: '0.75rem',
+ maxHeight: '60vh',
+ overflowY: 'auto' as const
+ },
+ fileItem: {
+ padding: '1rem',
+ borderRadius: '0.75rem',
+ cursor: 'pointer',
+ transition: 'all 0.2s',
+ border: '1px solid rgba(160, 130, 60, 0.3)',
+ background: 'rgba(215, 179, 119, 0.1)',
+ color: '#000000',
+ },
+ fileItemSelected: {
+ background: 'linear-gradient(45deg, #d97304, #a35402)',
+ color: '#000000',
+ transform: 'scale(1.02)',
+ boxShadow: '0 10px 25px rgba(217, 115, 4, 0.5)'
+ },
+ fileItemDefault: {
+ background: 'rgba(215, 179, 119, 0.1)',
+ color: '#000000'
+ },
+ fileInfo: {
+ display: 'flex',
+ alignItems: 'flex-start',
+ gap: '0.75rem'
+ },
+ fileIcon: {
+ fontSize: '1.25rem'
+ },
+ fileName: {
+ fontWeight: '500',
+ fontSize: '0.875rem',
+ marginBottom: '0.25rem'
+ },
+ filePath: {
+ fontSize: '0.75rem',
+ opacity: 0.7,
+ marginBottom: '0.5rem'
+ },
+ fileAge: {
+ fontSize: '0.75rem',
+ opacity: 0.7,
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.25rem'
+ },
+ metricsArea: {
+ display: 'flex',
+ flexDirection: 'column' as const,
+ gap: '1.5rem'
+ },
+ fileHeader: {
+ background: 'linear-gradient(135deg, rgba(215, 179, 119, 0.3), rgba(160, 130, 60, 0.2))',
+ backdropFilter: 'blur(10px)',
+ borderRadius: '1rem',
+ padding: '1.5rem',
+ border: '1px solid rgba(160, 130, 60, 0.5)',
+ color: '#000000'
+ },
+ fileHeaderContent: {
+ display: 'flex',
+ alignItems: 'flex-start',
+ gap: '1rem'
+ },
+ fileHeaderIcon: {
+ fontSize: '2.5rem'
+ },
+ fileHeaderTitle: {
+ fontSize: '1.5rem',
+ fontWeight: 'bold',
+ color: '#000000',
+ marginBottom: '0.5rem'
+ },
+ fileHeaderPath: {
+ color: 'rgba(0, 0, 0, 0.7)',
+ fontSize: '0.875rem',
+ marginBottom: '0.5rem'
+ },
+ fileHeaderMeta: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '1rem',
+ fontSize: '0.875rem',
+ color: 'rgba(0, 0, 0, 0.5)'
+ },
+ metricsGrid: {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
+ gap: '1.5rem'
+ },
+ metricCard: {
+ borderRadius: '1rem',
+ padding: '1.5rem',
+ border: '1px solid rgba(160, 130, 60, 0.5)',
+ backdropFilter: 'blur(10px)',
+ background: 'rgba(217, 115, 4, 0.15)',
+ transition: 'all 0.2s',
+ cursor: 'default',
+ color: '#000000'
+ },
+ metricHeader: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.75rem',
+ marginBottom: '1rem',
+ color: '#000000'
+ },
+ metricIcon: {
+ fontSize: '1.5rem',
+ transition: 'transform 0.2s'
+ },
+ metricTitle: {
+ fontWeight: '600',
+ color: '#000000',
+ fontSize: '1.125rem'
+ },
+ metricValue: {
+ fontSize: '2rem',
+ fontWeight: 'bold',
+ color: '#000000',
+ marginBottom: '0.5rem'
+ },
+ progressBar: {
+ width: '100%',
+ height: '0.5rem',
+ background: 'rgba(160, 130, 60, 0.3)',
+ borderRadius: '0.25rem',
+ marginTop: '0.75rem',
+ overflow: 'hidden'
+ },
+ progressFill: {
+ height: '100%',
+ background: 'linear-gradient(90deg, #d97304, #a35402)',
+ borderRadius: '0.25rem',
+ transition: 'width 1s ease'
+ },
+ methodTypeCard: {
+ background: 'linear-gradient(135deg, rgba(217, 115, 4, 0.2), rgba(161, 84, 2, 0.2))',
+ border: '1px solid rgba(217, 115, 4, 0.2)',
+ color: '#000000'
+ },
+ methodTypeList: {
+ display: 'flex',
+ flexDirection: 'column' as const,
+ gap: '0.75rem'
+ },
+ methodTypeItem: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: '0.75rem',
+ background: 'rgba(160, 130, 60, 0.1)',
+ borderRadius: '0.5rem',
+ color: '#000000'
+ },
+ methodTypeLabel: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.5rem'
+ },
+ methodTypeDot: {
+ width: '0.75rem',
+ height: '0.75rem',
+ borderRadius: '50%'
+ },
+ methodTypeText: {
+ color: '#000000'
+ },
+ methodTypeValue: {
+ fontSize: '1.25rem',
+ fontWeight: 'bold',
+ color: '#000000'
+ },
+ indentationCard: {
+ background: 'linear-gradient(135deg, rgba(217, 115, 4, 0.2), rgba(161, 84, 2, 0.2))',
+ border: '1px solid rgba(217, 115, 4, 0.2)',
+ color: '#000000'
+ },
+ indentationList: {
+ display: 'flex',
+ flexDirection: 'column' as const,
+ gap: '0.75rem'
+ },
+ indentationItem: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ color: '#000000'
+ },
+ indentationLabel: {
+ color: '#000000'
+ },
+ indentationValue: {
+ fontSize: '1.125rem',
+ fontWeight: '600',
+ color: '#000000'
+ },
+ loading: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ minHeight: '100vh',
+ background: 'linear-gradient(135deg, #f0e3c8, #e3d6b0, #d7b377)',
+ color: '#000000'
+ },
+ loadingSpinner: {
+ width: '4rem',
+ height: '4rem',
+ border: '4px solid rgba(217, 115, 4, 0.3)',
+ borderTop: '4px solid #d97304',
+ borderRadius: '50%',
+ animation: 'spin 1s linear infinite'
+ },
+ emptyState: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: '24rem',
+ color: '#000000'
+ },
+ emptyStateContent: {
+ textAlign: 'center' as const
+ },
+ emptyStateIcon: {
+ fontSize: '3.75rem',
+ marginBottom: '1rem',
+ opacity: 0.5
+ },
+ emptyStateText: {
+ color: 'rgba(0, 0, 0, 0.7)',
+ fontSize: '1.25rem'
+ },
+ fileIconImg: { width: '1.5rem', height: '1.5rem', objectFit: 'contain' as const },
+ fileHeaderImg: { width: '3rem', height: '3rem', objectFit: 'contain' as const }
+};
\ No newline at end of file
diff --git a/spicecloud/pages/utils/types.ts b/spicecloud/pages/utils/types.ts
new file mode 100644
index 0000000..86a681d
--- /dev/null
+++ b/spicecloud/pages/utils/types.ts
@@ -0,0 +1,10 @@
+export interface MetricData {
+ id: string;
+ hash: string;
+ timestamp: number;
+ file_name: string;
+ file_path: string;
+ metrics: any;
+ age: number;
+ readable_timestamp: string;
+}
\ No newline at end of file
diff --git a/spicecloud/pages/utils/useData.ts b/spicecloud/pages/utils/useData.ts
new file mode 100644
index 0000000..4065dc2
--- /dev/null
+++ b/spicecloud/pages/utils/useData.ts
@@ -0,0 +1,57 @@
+import { useState, useEffect } from 'react';
+import { MetricData } from './types';
+
+export const useData = () => {
+ const [data, setData] = useState
([]);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchData = async () => {
+ try {
+ setLoading(true);
+
+ // Adjust the route if your endpoint is different
+ const res = await fetch('/api/files');
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+
+ const json = await res.json();
+
+ const files = json.map((f: any) => ({
+ ...f,
+ age: Date.now() - f.timestamp,
+ readable_timestamp: new Date(f.timestamp).toLocaleString('pt-BR'),
+ // If comment_ratio comes as string ("16.23%") and you prefer number:
+ metrics: {
+ ...f.metrics,
+ comment_ratio: typeof f.metrics.comment_ratio === 'string'
+ ? parseFloat(f.metrics.comment_ratio) / 100
+ : f.metrics.comment_ratio
+ }
+ }));
+
+ setData(files);
+ if (files.length && !selectedFile) setSelectedFile(files[0]);
+ } catch (err) {
+ setError('Erro ao buscar dados');
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ const interval = setInterval(fetchData, 10000);
+ return () => clearInterval(interval);
+ }, []);
+
+ return {
+ data,
+ selectedFile,
+ setSelectedFile,
+ loading,
+ error,
+ fetchData
+ };
+};
\ No newline at end of file
diff --git a/spicecloud/pages/utils/utils.ts b/spicecloud/pages/utils/utils.ts
new file mode 100644
index 0000000..3144011
--- /dev/null
+++ b/spicecloud/pages/utils/utils.ts
@@ -0,0 +1,60 @@
+export const getFileIconSrc = (ext: string): string => {
+ switch (ext) {
+ case '.py': return '/python-logo.png';
+ case '.js': return '/javascript-logo.png';
+ case '.rb': return '/ruby-logo.png';
+ case '.go': return '/go-logo.png';
+ default: return '/file-icon.png';
+ }
+};
+
+export const formatMetricValue = (key: string, value: any): string => {
+ if (typeof value === 'object' && value !== null) {
+ return JSON.stringify(value, null, 2);
+ }
+ if (key.includes('ratio') && typeof value === 'number') {
+ return `${(value * 100).toFixed(1)}%`;
+ }
+ return String(value);
+};
+
+export const getMetricIcon = (key: string): string => {
+ if (key.includes('line_count')) return '📄';
+ if (key.includes('function_count')) return '⚡';
+ if (key.includes('comment')) return '💬';
+ if (key.includes('dependencies')) return '📦';
+ if (key.includes('indentation')) return '📐';
+ if (key.includes('method')) return '🔧';
+ if (key.includes('ratio')) return '📊';
+ return '📋';
+};
+
+export const getMetricColor = (key: string): string => {
+ if (key.includes('count') || key.includes('line'))
+ return 'linear-gradient(135deg, #e3d6b0, #c9b67a)';
+ if (key.includes('ratio'))
+ return 'linear-gradient(135deg, #d97304, #a35402)';
+ if (key.includes('dependencies'))
+ return 'linear-gradient(135deg, #a67c23, #6b4c1b)';
+ if (key.includes('indentation'))
+ return 'linear-gradient(135deg, #d7b377, #f0e3c8)';
+ if (key.includes('method'))
+ return 'linear-gradient(135deg, #a35402, #d97304)';
+ return 'linear-gradient(135deg, #6b4c1b, #4a2c0b)';
+};
+
+export const formatAge = (age: number): string => {
+ const hours = Math.floor(age / (1000 * 60 * 60));
+ const minutes = Math.floor((age % (1000 * 60 * 60)) / (1000 * 60));
+
+ if (hours > 0) {
+ return `${hours}h ${minutes}m ago`;
+ }
+ return `${minutes}m ago`;
+};
+
+export const formatLabel = (key: string): string => {
+ return key
+ .replace(/_/g, ' ')
+ .replace(/\b\w/g, l => l.toUpperCase());
+};
\ No newline at end of file
diff --git a/spicecloud/public/file-icon.png b/spicecloud/public/file-icon.png
new file mode 100644
index 0000000..e95bb65
Binary files /dev/null and b/spicecloud/public/file-icon.png differ
diff --git a/spicecloud/public/go-logo.png b/spicecloud/public/go-logo.png
new file mode 100644
index 0000000..d6d98c3
Binary files /dev/null and b/spicecloud/public/go-logo.png differ
diff --git a/spicecloud/public/javascript-logo.png b/spicecloud/public/javascript-logo.png
new file mode 100644
index 0000000..7b5ffab
Binary files /dev/null and b/spicecloud/public/javascript-logo.png differ
diff --git a/spicecloud/public/python-logo.png b/spicecloud/public/python-logo.png
new file mode 100644
index 0000000..49ea8f5
Binary files /dev/null and b/spicecloud/public/python-logo.png differ
diff --git a/spicecloud/public/ruby-logo.png b/spicecloud/public/ruby-logo.png
new file mode 100644
index 0000000..797167a
Binary files /dev/null and b/spicecloud/public/ruby-logo.png differ
diff --git a/spicecloud/public/spicecode-logo.png b/spicecloud/public/spicecode-logo.png
new file mode 100644
index 0000000..71dd73f
Binary files /dev/null and b/spicecloud/public/spicecode-logo.png differ
diff --git a/spicecloud/tailwind.config.js b/spicecloud/tailwind.config.js
index 47bc0ba..8bfaecf 100644
--- a/spicecloud/tailwind.config.js
+++ b/spicecloud/tailwind.config.js
@@ -1,9 +1,9 @@
-/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
- './pages/**/*.{js,ts,jsx,tsx,mdx}',
- './components/**/*.{js,ts,jsx,tsx,mdx}',
- './app/**/*.{js,ts,jsx,tsx,mdx}',
+ "./pages/**/*.{js,ts,jsx,tsx}",
+ "./components/**/*.{js,ts,jsx,tsx}",
+ "./app/**/*.{js,ts,jsx,tsx}",
+ // Add your specific paths here
],
theme: {
extend: {},