roadmap-kit 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +612 -27
- package/dashboard/dist/assets/index-BDNWoUOj.js +521 -0
- package/dashboard/dist/assets/index-BHRTkw53.css +1 -0
- package/dashboard/dist/index.html +9 -7
- package/dashboard/index.html +7 -5
- package/dashboard/server.js +35 -10
- package/dashboard/src/App.jsx +678 -218
- package/dashboard/src/components/SharedResources.jsx +16 -16
- package/dashboard/src/components/TechnicalDebt.jsx +14 -11
- package/dashboard/src/index.css +795 -275
- package/dashboard/src/main.jsx +2 -2
- package/dashboard/src/themes.js +50 -0
- package/dashboard/tailwind.config.js +7 -1
- package/dashboard/vite.config.js +3 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-BzYzLB7u.css +0 -1
- package/dashboard/dist/assets/index-DIonhzlK.js +0 -506
package/dashboard/src/App.jsx
CHANGED
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
HelpCircle, GitCommit, Bot, Wrench, Activity, Grid, Play,
|
|
9
9
|
ArrowRight, Minus as MinusIcon, PlusCircle, MinusCircle, GitBranch,
|
|
10
10
|
Globe, Command, Columns, List, Link2, MessageSquare, Timer,
|
|
11
|
-
BarChart3, FileDown, History, Bell, Keyboard, Filter
|
|
11
|
+
BarChart3, FileDown, History, Bell, Keyboard, Filter, Palette,
|
|
12
|
+
Sun, Moon
|
|
12
13
|
} from 'lucide-react';
|
|
14
|
+
import { themes, DEFAULT_THEME, getTheme } from './themes.js';
|
|
13
15
|
|
|
14
16
|
// ============ TOAST NOTIFICATION SYSTEM ============
|
|
15
17
|
const ToastContext = createContext();
|
|
@@ -75,6 +77,86 @@ const useToast = () => {
|
|
|
75
77
|
return context.toast;
|
|
76
78
|
};
|
|
77
79
|
|
|
80
|
+
// ============ THEME SYSTEM ============
|
|
81
|
+
const ThemeContext = createContext();
|
|
82
|
+
|
|
83
|
+
function ThemeProvider({ children, initialTheme = DEFAULT_THEME, initialColorMode = 'light' }) {
|
|
84
|
+
const [currentTheme, setCurrentTheme] = useState(initialTheme);
|
|
85
|
+
const [colorMode, setColorModeState] = useState(initialColorMode);
|
|
86
|
+
|
|
87
|
+
// Apply theme and color mode to document
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
document.documentElement.setAttribute('data-theme', currentTheme);
|
|
90
|
+
// Apply light/dark class
|
|
91
|
+
if (colorMode === 'dark') {
|
|
92
|
+
document.documentElement.classList.add('dark');
|
|
93
|
+
document.documentElement.classList.remove('light');
|
|
94
|
+
} else {
|
|
95
|
+
document.documentElement.classList.add('light');
|
|
96
|
+
document.documentElement.classList.remove('dark');
|
|
97
|
+
}
|
|
98
|
+
}, [currentTheme, colorMode]);
|
|
99
|
+
|
|
100
|
+
const setTheme = useCallback(async (themeId) => {
|
|
101
|
+
if (!themes[themeId]) return;
|
|
102
|
+
setCurrentTheme(themeId);
|
|
103
|
+
|
|
104
|
+
// Save to server
|
|
105
|
+
try {
|
|
106
|
+
await fetch('/api/auth/profile', {
|
|
107
|
+
method: 'PUT',
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
body: JSON.stringify({ theme: themeId })
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error('Error saving theme:', err);
|
|
113
|
+
}
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
const setColorMode = useCallback(async (mode) => {
|
|
117
|
+
if (!['light', 'dark'].includes(mode)) return;
|
|
118
|
+
setColorModeState(mode);
|
|
119
|
+
|
|
120
|
+
// Save to server
|
|
121
|
+
try {
|
|
122
|
+
await fetch('/api/auth/profile', {
|
|
123
|
+
method: 'PUT',
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
body: JSON.stringify({ colorMode: mode })
|
|
126
|
+
});
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error('Error saving color mode:', err);
|
|
129
|
+
}
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const toggleColorMode = useCallback(() => {
|
|
133
|
+
setColorMode(colorMode === 'dark' ? 'light' : 'dark');
|
|
134
|
+
}, [colorMode, setColorMode]);
|
|
135
|
+
|
|
136
|
+
const value = useMemo(() => ({
|
|
137
|
+
theme: currentTheme,
|
|
138
|
+
themeConfig: getTheme(currentTheme),
|
|
139
|
+
setTheme,
|
|
140
|
+
themes,
|
|
141
|
+
colorMode,
|
|
142
|
+
setColorMode,
|
|
143
|
+
toggleColorMode,
|
|
144
|
+
isDark: colorMode === 'dark'
|
|
145
|
+
}), [currentTheme, setTheme, colorMode, setColorMode, toggleColorMode]);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<ThemeContext.Provider value={value}>
|
|
149
|
+
{children}
|
|
150
|
+
</ThemeContext.Provider>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const useTheme = () => {
|
|
155
|
+
const context = useContext(ThemeContext);
|
|
156
|
+
if (!context) throw new Error('useTheme must be used within ThemeProvider');
|
|
157
|
+
return context;
|
|
158
|
+
};
|
|
159
|
+
|
|
78
160
|
// ============ GLOBAL SEARCH MODAL ============
|
|
79
161
|
function GlobalSearchModal({ isOpen, onClose, roadmap, onNavigate, t }) {
|
|
80
162
|
const [query, setQuery] = useState('');
|
|
@@ -237,9 +319,9 @@ function useKeyboardShortcuts(handlers) {
|
|
|
237
319
|
return;
|
|
238
320
|
}
|
|
239
321
|
|
|
240
|
-
// Number keys 1-
|
|
322
|
+
// Number keys 1-8 for tab navigation
|
|
241
323
|
if (!e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
242
|
-
if (e.key >= '1' && e.key <= '
|
|
324
|
+
if (e.key >= '1' && e.key <= '8') {
|
|
243
325
|
e.preventDefault();
|
|
244
326
|
handlers.onTabChange?.(parseInt(e.key) - 1);
|
|
245
327
|
return;
|
|
@@ -318,7 +400,7 @@ const translations = {
|
|
|
318
400
|
setup: 'SETUP',
|
|
319
401
|
features: 'FEATURES',
|
|
320
402
|
resources: 'RESOURCES',
|
|
321
|
-
debt: 'DEBT',
|
|
403
|
+
debt: 'TECH DEBT',
|
|
322
404
|
info: 'INFO',
|
|
323
405
|
settings: 'SETTINGS',
|
|
324
406
|
help: 'HELP'
|
|
@@ -432,7 +514,39 @@ const translations = {
|
|
|
432
514
|
effort: 'Effort',
|
|
433
515
|
source: 'Source',
|
|
434
516
|
bySeverity: 'BY SEVERITY',
|
|
435
|
-
items: 'items'
|
|
517
|
+
items: 'items',
|
|
518
|
+
// Severity levels
|
|
519
|
+
high: 'HIGH',
|
|
520
|
+
medium: 'MEDIUM',
|
|
521
|
+
low: 'LOW',
|
|
522
|
+
// Status
|
|
523
|
+
status: 'STATUS',
|
|
524
|
+
pending: 'PENDING',
|
|
525
|
+
inProgress: 'IN PROGRESS',
|
|
526
|
+
resolved: 'RESOLVED',
|
|
527
|
+
// Actions
|
|
528
|
+
filter: 'FILTER',
|
|
529
|
+
all: 'ALL',
|
|
530
|
+
clear: 'CLEAR',
|
|
531
|
+
addDebt: 'ADD DEBT',
|
|
532
|
+
addFirstDebt: 'ADD FIRST DEBT',
|
|
533
|
+
noMatch: 'NO ITEMS MATCH FILTERS',
|
|
534
|
+
// Details
|
|
535
|
+
description: 'DESCRIPTION',
|
|
536
|
+
impact: 'IMPACT',
|
|
537
|
+
solution: 'PROPOSED SOLUTION',
|
|
538
|
+
affectedFiles: 'AFFECTED FILES',
|
|
539
|
+
feature: 'FEATURE',
|
|
540
|
+
task: 'TASK',
|
|
541
|
+
priority: 'PRIORITY',
|
|
542
|
+
notEstimated: 'Not estimated',
|
|
543
|
+
// Dates
|
|
544
|
+
created: 'Created',
|
|
545
|
+
resolvedAt: 'Resolved',
|
|
546
|
+
assigned: 'Assigned',
|
|
547
|
+
due: 'Due',
|
|
548
|
+
// Schema
|
|
549
|
+
jsonSchema: 'JSON SCHEMA FOR TECHNICAL DEBT'
|
|
436
550
|
},
|
|
437
551
|
// Info
|
|
438
552
|
info: {
|
|
@@ -664,7 +778,7 @@ const translations = {
|
|
|
664
778
|
setup: 'ASISTENTE',
|
|
665
779
|
features: 'ESTADO',
|
|
666
780
|
resources: 'RECURSOS',
|
|
667
|
-
debt: 'DEUDA',
|
|
781
|
+
debt: 'DEUDA TÉCNICA',
|
|
668
782
|
info: 'INFO',
|
|
669
783
|
settings: 'CONFIG',
|
|
670
784
|
help: 'AYUDA'
|
|
@@ -778,7 +892,39 @@ const translations = {
|
|
|
778
892
|
effort: 'Esfuerzo',
|
|
779
893
|
source: 'Origen',
|
|
780
894
|
bySeverity: 'POR SEVERIDAD',
|
|
781
|
-
items: 'elementos'
|
|
895
|
+
items: 'elementos',
|
|
896
|
+
// Niveles de severidad
|
|
897
|
+
high: 'ALTA',
|
|
898
|
+
medium: 'MEDIA',
|
|
899
|
+
low: 'BAJA',
|
|
900
|
+
// Estado
|
|
901
|
+
status: 'ESTADO',
|
|
902
|
+
pending: 'PENDIENTE',
|
|
903
|
+
inProgress: 'EN PROGRESO',
|
|
904
|
+
resolved: 'RESUELTO',
|
|
905
|
+
// Acciones
|
|
906
|
+
filter: 'FILTRAR',
|
|
907
|
+
all: 'TODOS',
|
|
908
|
+
clear: 'LIMPIAR',
|
|
909
|
+
addDebt: 'AÑADIR DEUDA',
|
|
910
|
+
addFirstDebt: 'AÑADIR PRIMERA DEUDA',
|
|
911
|
+
noMatch: 'NINGÚN ELEMENTO COINCIDE CON LOS FILTROS',
|
|
912
|
+
// Detalles
|
|
913
|
+
description: 'DESCRIPCIÓN',
|
|
914
|
+
impact: 'IMPACTO',
|
|
915
|
+
solution: 'SOLUCIÓN PROPUESTA',
|
|
916
|
+
affectedFiles: 'ARCHIVOS AFECTADOS',
|
|
917
|
+
feature: 'CARACTERÍSTICA',
|
|
918
|
+
task: 'TAREA',
|
|
919
|
+
priority: 'PRIORIDAD',
|
|
920
|
+
notEstimated: 'Sin estimar',
|
|
921
|
+
// Fechas
|
|
922
|
+
created: 'Creado',
|
|
923
|
+
resolvedAt: 'Resuelto',
|
|
924
|
+
assigned: 'Asignado',
|
|
925
|
+
due: 'Vence',
|
|
926
|
+
// Schema
|
|
927
|
+
jsonSchema: 'ESQUEMA JSON PARA DEUDA TÉCNICA'
|
|
782
928
|
},
|
|
783
929
|
// Info
|
|
784
930
|
info: {
|
|
@@ -1207,6 +1353,9 @@ function App() {
|
|
|
1207
1353
|
const [loginError, setLoginError] = useState(null);
|
|
1208
1354
|
const [teamMembers, setTeamMembers] = useState([]); // Users for task assignment
|
|
1209
1355
|
|
|
1356
|
+
// Theme
|
|
1357
|
+
const { setTheme } = useTheme();
|
|
1358
|
+
|
|
1210
1359
|
// Confirmation modal state
|
|
1211
1360
|
const [confirmModal, setConfirmModal] = useState({
|
|
1212
1361
|
show: false,
|
|
@@ -1280,6 +1429,14 @@ function App() {
|
|
|
1280
1429
|
authEnabled: data.authEnabled,
|
|
1281
1430
|
user: data.user || null
|
|
1282
1431
|
});
|
|
1432
|
+
// Sync theme and color mode from user
|
|
1433
|
+
if (data.user?.theme) {
|
|
1434
|
+
document.documentElement.setAttribute('data-theme', data.user.theme);
|
|
1435
|
+
}
|
|
1436
|
+
if (data.user?.colorMode) {
|
|
1437
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
1438
|
+
document.documentElement.classList.add(data.user.colorMode);
|
|
1439
|
+
}
|
|
1283
1440
|
if (data.authenticated || !data.authEnabled) {
|
|
1284
1441
|
loadRoadmap();
|
|
1285
1442
|
loadTeamMembers();
|
|
@@ -1307,6 +1464,14 @@ function App() {
|
|
|
1307
1464
|
authEnabled: true,
|
|
1308
1465
|
user: data.user
|
|
1309
1466
|
});
|
|
1467
|
+
// Sync theme and color mode from user
|
|
1468
|
+
if (data.user?.theme) {
|
|
1469
|
+
document.documentElement.setAttribute('data-theme', data.user.theme);
|
|
1470
|
+
}
|
|
1471
|
+
if (data.user?.colorMode) {
|
|
1472
|
+
document.documentElement.classList.remove('light', 'dark');
|
|
1473
|
+
document.documentElement.classList.add(data.user.colorMode);
|
|
1474
|
+
}
|
|
1310
1475
|
loadRoadmap();
|
|
1311
1476
|
} else {
|
|
1312
1477
|
setLoginError(data.error || 'Error de autenticacion');
|
|
@@ -1339,8 +1504,31 @@ function App() {
|
|
|
1339
1504
|
return;
|
|
1340
1505
|
}
|
|
1341
1506
|
}
|
|
1342
|
-
if (!response.ok) throw new Error('No se pudo cargar roadmap.json');
|
|
1343
1507
|
const data = await response.json();
|
|
1508
|
+
// Handle case when roadmap.json doesn't exist - redirect to setup
|
|
1509
|
+
if (data.exists === false || data.redirectToSetup) {
|
|
1510
|
+
setRoadmap({ project_info: { name: '', description: '', stack: [] }, features: [] });
|
|
1511
|
+
setActiveTab('setup');
|
|
1512
|
+
setLoading(false);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (!response.ok) throw new Error('No se pudo cargar roadmap.json');
|
|
1516
|
+
// Recalculate progress on load to ensure consistency
|
|
1517
|
+
if (data.features) {
|
|
1518
|
+
data.features.forEach(feature => {
|
|
1519
|
+
if (feature.tasks?.length > 0) {
|
|
1520
|
+
const completed = feature.tasks.filter(t => t.status === 'completed').length;
|
|
1521
|
+
feature.progress = Math.round((completed / feature.tasks.length) * 100);
|
|
1522
|
+
} else {
|
|
1523
|
+
feature.progress = 0;
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
const allTasks = data.features.flatMap(f => f.tasks || []);
|
|
1527
|
+
if (allTasks.length > 0) {
|
|
1528
|
+
const totalCompleted = allTasks.filter(t => t.status === 'completed').length;
|
|
1529
|
+
data.project_info.total_progress = Math.round((totalCompleted / allTasks.length) * 100);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1344
1532
|
setRoadmap(data);
|
|
1345
1533
|
setHasChanges(false);
|
|
1346
1534
|
} catch (err) {
|
|
@@ -1597,13 +1785,13 @@ function App() {
|
|
|
1597
1785
|
if (error) {
|
|
1598
1786
|
return (
|
|
1599
1787
|
<div className="min-h-screen flex items-center justify-center bg-black p-4">
|
|
1600
|
-
<div className="
|
|
1788
|
+
<div className="theme-card p-8 max-w-md w-full">
|
|
1601
1789
|
<div className="flex items-center gap-3 mb-6">
|
|
1602
|
-
<div className="led led-
|
|
1790
|
+
<div className="led theme-led theme-led-danger" />
|
|
1603
1791
|
<span className="font-mono text-alert text-sm tracking-wider">ERROR</span>
|
|
1604
1792
|
</div>
|
|
1605
1793
|
<p className="font-mono text-gray-400 text-sm mb-6">{error}</p>
|
|
1606
|
-
<button onClick={loadRoadmap} className="btn-
|
|
1794
|
+
<button onClick={loadRoadmap} className="theme-btn-primary w-full">
|
|
1607
1795
|
RETRY
|
|
1608
1796
|
</button>
|
|
1609
1797
|
</div>
|
|
@@ -1621,7 +1809,7 @@ function App() {
|
|
|
1621
1809
|
{/* <div className="scanlines" /> */}
|
|
1622
1810
|
|
|
1623
1811
|
{/* Sidebar */}
|
|
1624
|
-
<aside className={`fixed left-0 top-0 h-full sidebar
|
|
1812
|
+
<aside className={`fixed left-0 top-0 h-full theme-sidebar z-50 transition-all duration-300 ${sidebarCollapsed ? 'w-16' : 'w-64'}`}>
|
|
1625
1813
|
<div className="flex flex-col h-full">
|
|
1626
1814
|
{/* Logo */}
|
|
1627
1815
|
<div className="p-4 border-b border-matrix/10">
|
|
@@ -1646,8 +1834,8 @@ function App() {
|
|
|
1646
1834
|
<span className="font-mono text-[10px] text-gray-500 tracking-widest">{language === 'es' ? 'PROGRESO' : 'PROGRESS'}</span>
|
|
1647
1835
|
<span className="font-display text-2xl text-matrix">{projectInfo.total_progress || 0}%</span>
|
|
1648
1836
|
</div>
|
|
1649
|
-
<div className="progress
|
|
1650
|
-
<div className="progress-
|
|
1837
|
+
<div className="theme-progress">
|
|
1838
|
+
<div className="theme-progress-fill" style={{ width: `${projectInfo.total_progress || 0}%` }} />
|
|
1651
1839
|
</div>
|
|
1652
1840
|
</div>
|
|
1653
1841
|
<div className="grid grid-cols-3 gap-2 text-center">
|
|
@@ -1676,7 +1864,7 @@ function App() {
|
|
|
1676
1864
|
<button
|
|
1677
1865
|
key={item.id}
|
|
1678
1866
|
onClick={() => setActiveTab(item.id)}
|
|
1679
|
-
className={`nav-
|
|
1867
|
+
className={`theme-nav-item w-full flex items-center gap-3 px-4 py-3 transition-all ${
|
|
1680
1868
|
isActive ? 'active' : 'text-gray-500 hover:text-gray-300 hover:bg-white/[0.02]'
|
|
1681
1869
|
} ${sidebarCollapsed ? 'justify-center px-2' : 'pl-8'}`}
|
|
1682
1870
|
>
|
|
@@ -1699,7 +1887,7 @@ function App() {
|
|
|
1699
1887
|
</div>
|
|
1700
1888
|
)}
|
|
1701
1889
|
{hasChanges && !sidebarCollapsed && (
|
|
1702
|
-
<button onClick={saveRoadmap} disabled={saving} className="btn-
|
|
1890
|
+
<button onClick={saveRoadmap} disabled={saving} className="theme-btn-primary w-full flex items-center justify-center gap-2">
|
|
1703
1891
|
{saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
|
1704
1892
|
{t('common.save')}
|
|
1705
1893
|
</button>
|
|
@@ -1772,7 +1960,7 @@ function App() {
|
|
|
1772
1960
|
</header>
|
|
1773
1961
|
|
|
1774
1962
|
{/* Content Area */}
|
|
1775
|
-
<div className="p-6 pb-24 grid-bg min-h-[calc(100vh-120px)]">
|
|
1963
|
+
<div className="p-6 pb-24 theme-grid-bg min-h-[calc(100vh-120px)]">
|
|
1776
1964
|
{activeTab === 'features' && (
|
|
1777
1965
|
<FeaturesTab
|
|
1778
1966
|
features={filteredFeatures}
|
|
@@ -1814,7 +2002,7 @@ function App() {
|
|
|
1814
2002
|
)}
|
|
1815
2003
|
{activeTab === 'metrics' && <MetricsTab roadmap={roadmap} language={language} />}
|
|
1816
2004
|
{activeTab === 'resources' && <ResourcesTab resources={projectInfo.shared_resources || {}} />}
|
|
1817
|
-
{activeTab === 'debt' && <DebtTab roadmap={roadmap} setRoadmap={setRoadmap} setHasChanges={setHasChanges} />}
|
|
2005
|
+
{activeTab === 'debt' && <DebtTab roadmap={roadmap} setRoadmap={setRoadmap} setHasChanges={setHasChanges} t={t} />}
|
|
1818
2006
|
{activeTab === 'info' && <InfoTab projectInfo={projectInfo} />}
|
|
1819
2007
|
{activeTab === 'settings' && <SettingsTab roadmap={roadmap} setRoadmap={setRoadmap} setHasChanges={setHasChanges} authState={authState} showConfirm={showConfirm} showAlert={showAlert} t={t} language={language} changeLanguage={changeLanguage} />}
|
|
1820
2008
|
{activeTab === 'help' && <HelpTab t={t} language={language} />}
|
|
@@ -2256,7 +2444,7 @@ function AIGeneratorModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
2256
2444
|
- Notificaciones por email
|
|
2257
2445
|
- Integración con Stripe para pagos
|
|
2258
2446
|
- API REST documentada con Swagger`}
|
|
2259
|
-
className="w-full h-64 input
|
|
2447
|
+
className="w-full h-64 theme-input p-4 text-sm resize-none"
|
|
2260
2448
|
spellCheck={false}
|
|
2261
2449
|
/>
|
|
2262
2450
|
<p className="mt-2 font-mono text-[10px] text-gray-600">
|
|
@@ -2268,7 +2456,7 @@ function AIGeneratorModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
2268
2456
|
<button
|
|
2269
2457
|
onClick={analyzeWithAI}
|
|
2270
2458
|
disabled={analyzing || !requirements.trim() || (!claudeStatus.available && !claudeStatus.checking)}
|
|
2271
|
-
className="btn-
|
|
2459
|
+
className="theme-btn-primary flex-1 py-3 disabled:opacity-50 flex items-center justify-center gap-2"
|
|
2272
2460
|
>
|
|
2273
2461
|
<Sparkles className="w-4 h-4" />
|
|
2274
2462
|
GENERAR CON IA
|
|
@@ -2350,7 +2538,7 @@ function AIGeneratorModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
2350
2538
|
<div className="flex gap-3">
|
|
2351
2539
|
<button
|
|
2352
2540
|
onClick={mergeResults}
|
|
2353
|
-
className="btn-
|
|
2541
|
+
className="theme-btn-primary flex-1 py-3 flex items-center justify-center gap-2"
|
|
2354
2542
|
>
|
|
2355
2543
|
<Plus className="w-4 h-4" />
|
|
2356
2544
|
AÑADIR AL ROADMAP
|
|
@@ -2533,13 +2721,13 @@ function FeaturesTab({
|
|
|
2533
2721
|
placeholder={t ? t('common.search') : "search tasks..."}
|
|
2534
2722
|
value={searchTerm}
|
|
2535
2723
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
2536
|
-
className="input
|
|
2724
|
+
className="theme-input w-full pl-10 pr-4 py-2.5 text-sm"
|
|
2537
2725
|
/>
|
|
2538
2726
|
</div>
|
|
2539
2727
|
<select
|
|
2540
2728
|
value={statusFilter}
|
|
2541
2729
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
2542
|
-
className="input
|
|
2730
|
+
className="theme-input px-4 py-2.5 text-sm cursor-pointer"
|
|
2543
2731
|
>
|
|
2544
2732
|
<option value="all">{language === 'es' ? 'TODOS' : 'ALL STATUS'}</option>
|
|
2545
2733
|
<option value="pending">{language === 'es' ? 'PENDIENTE' : 'PENDING'}</option>
|
|
@@ -2568,17 +2756,17 @@ function FeaturesTab({
|
|
|
2568
2756
|
{/* Advanced Filters Toggle */}
|
|
2569
2757
|
<button
|
|
2570
2758
|
onClick={() => setShowFilters(!showFilters)}
|
|
2571
|
-
className={`btn-
|
|
2759
|
+
className={`theme-btn-primary flex items-center gap-2 ${showFilters ? 'border-cyber/50 text-cyber' : ''}`}
|
|
2572
2760
|
>
|
|
2573
2761
|
<Filter className="w-4 h-4" />
|
|
2574
2762
|
{language === 'es' ? 'FILTROS' : 'FILTERS'}
|
|
2575
2763
|
</button>
|
|
2576
2764
|
|
|
2577
|
-
<button onClick={() => setShowAddFeature(true)} className="btn-
|
|
2765
|
+
<button onClick={() => setShowAddFeature(true)} className="theme-btn-primary flex items-center gap-2">
|
|
2578
2766
|
<Plus className="w-4 h-4" />
|
|
2579
2767
|
{language === 'es' ? 'NUEVA FEATURE' : 'NEW FEATURE'}
|
|
2580
2768
|
</button>
|
|
2581
|
-
<button onClick={() => setShowAIGenerator(true)} className="btn-
|
|
2769
|
+
<button onClick={() => setShowAIGenerator(true)} className="theme-btn-primary flex items-center gap-2 bg-gradient-to-r from-cyber/20 to-matrix/20 border-cyber/50 hover:border-cyber">
|
|
2582
2770
|
<Sparkles className="w-4 h-4 text-cyber" />
|
|
2583
2771
|
AI
|
|
2584
2772
|
</button>
|
|
@@ -2592,7 +2780,7 @@ function FeaturesTab({
|
|
|
2592
2780
|
<select
|
|
2593
2781
|
value={priorityFilter}
|
|
2594
2782
|
onChange={(e) => setPriorityFilter(e.target.value)}
|
|
2595
|
-
className="input
|
|
2783
|
+
className="theme-input px-3 py-1.5 text-xs cursor-pointer"
|
|
2596
2784
|
>
|
|
2597
2785
|
<option value="all">{language === 'es' ? 'TODAS' : 'ALL'}</option>
|
|
2598
2786
|
<option value="high">{language === 'es' ? 'ALTA' : 'HIGH'}</option>
|
|
@@ -2605,7 +2793,7 @@ function FeaturesTab({
|
|
|
2605
2793
|
<select
|
|
2606
2794
|
value={assigneeFilter}
|
|
2607
2795
|
onChange={(e) => setAssigneeFilter(e.target.value)}
|
|
2608
|
-
className="input
|
|
2796
|
+
className="theme-input px-3 py-1.5 text-xs cursor-pointer"
|
|
2609
2797
|
>
|
|
2610
2798
|
<option value="all">{language === 'es' ? 'TODOS' : 'ALL'}</option>
|
|
2611
2799
|
<option value="">{language === 'es' ? 'SIN ASIGNAR' : 'UNASSIGNED'}</option>
|
|
@@ -2654,7 +2842,7 @@ function FeaturesTab({
|
|
|
2654
2842
|
)}
|
|
2655
2843
|
|
|
2656
2844
|
{features.length === 0 && viewMode === 'list' && (
|
|
2657
|
-
<div className="
|
|
2845
|
+
<div className="theme-card p-12 text-center">
|
|
2658
2846
|
<Search className="w-10 h-10 mx-auto mb-4 text-gray-700" />
|
|
2659
2847
|
<p className="font-mono text-gray-600 text-sm">{language === 'es' ? 'NO HAY TAREAS' : 'NO TASKS FOUND'}</p>
|
|
2660
2848
|
</div>
|
|
@@ -2684,7 +2872,7 @@ function FeatureCard({ feature, onUpdateTaskStatus, onAddTask, showAddTask, setS
|
|
|
2684
2872
|
{/* Progress Ring */}
|
|
2685
2873
|
<div className="relative w-14 h-14 flex-shrink-0">
|
|
2686
2874
|
<svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
|
|
2687
|
-
<circle cx="18" cy="18" r="15" fill="none" stroke="
|
|
2875
|
+
<circle cx="18" cy="18" r="15" fill="none" stroke="var(--bg-secondary)" strokeWidth="3" />
|
|
2688
2876
|
<circle
|
|
2689
2877
|
cx="18" cy="18" r="15" fill="none"
|
|
2690
2878
|
stroke="url(#progressGradient)"
|
|
@@ -2695,8 +2883,8 @@ function FeatureCard({ feature, onUpdateTaskStatus, onAddTask, showAddTask, setS
|
|
|
2695
2883
|
/>
|
|
2696
2884
|
<defs>
|
|
2697
2885
|
<linearGradient id="progressGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
2698
|
-
<stop offset="0%" stopColor="
|
|
2699
|
-
<stop offset="100%" stopColor="
|
|
2886
|
+
<stop offset="0%" stopColor="var(--accent-primary)" />
|
|
2887
|
+
<stop offset="100%" stopColor="var(--accent-tertiary)" />
|
|
2700
2888
|
</linearGradient>
|
|
2701
2889
|
</defs>
|
|
2702
2890
|
</svg>
|
|
@@ -2726,8 +2914,8 @@ function FeatureCard({ feature, onUpdateTaskStatus, onAddTask, showAddTask, setS
|
|
|
2726
2914
|
<span>{feature.tasks.filter(t => t.status === 'completed').length} / {feature.tasks.length} TASKS</span>
|
|
2727
2915
|
<span>{feature.progress}%</span>
|
|
2728
2916
|
</div>
|
|
2729
|
-
<div className="progress
|
|
2730
|
-
<div className="progress-
|
|
2917
|
+
<div className="theme-progress">
|
|
2918
|
+
<div className="theme-progress-fill" style={{ width: `${feature.progress}%` }} />
|
|
2731
2919
|
</div>
|
|
2732
2920
|
</div>
|
|
2733
2921
|
|
|
@@ -2764,8 +2952,8 @@ function TaskList({ tasks = [], featureId = '', onUpdateStatus, team = [], onAss
|
|
|
2764
2952
|
};
|
|
2765
2953
|
|
|
2766
2954
|
const statusConfig = {
|
|
2767
|
-
completed: { led: 'led-
|
|
2768
|
-
in_progress: { led: 'led-
|
|
2955
|
+
completed: { led: 'theme-led theme-led-success', label: 'DONE', color: 'text-matrix' },
|
|
2956
|
+
in_progress: { led: 'theme-led theme-led-warning', label: 'ACTIVE', color: 'text-signal' },
|
|
2769
2957
|
pending: { led: 'led-gray', label: 'QUEUE', color: 'text-gray-500' }
|
|
2770
2958
|
};
|
|
2771
2959
|
|
|
@@ -2970,7 +3158,7 @@ function TaskList({ tasks = [], featureId = '', onUpdateStatus, team = [], onAss
|
|
|
2970
3158
|
const member = team.find(m => m.id === e.target.value);
|
|
2971
3159
|
onAssignTask?.(featureId, task.id, e.target.value, member?.name || '');
|
|
2972
3160
|
}}
|
|
2973
|
-
className="input
|
|
3161
|
+
className="theme-input px-3 py-1.5 text-[11px]"
|
|
2974
3162
|
>
|
|
2975
3163
|
<option value="">Sin asignar</option>
|
|
2976
3164
|
{team.map((member) => (
|
|
@@ -3063,27 +3251,27 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3063
3251
|
<div className="space-y-6 animate-fade-in">
|
|
3064
3252
|
{/* Summary Cards */}
|
|
3065
3253
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
3066
|
-
<div className="
|
|
3254
|
+
<div className="theme-card p-5">
|
|
3067
3255
|
<div className="font-mono text-[10px] text-gray-500 mb-2">{language === 'es' ? 'TOTAL TAREAS' : 'TOTAL TASKS'}</div>
|
|
3068
3256
|
<div className="font-display text-3xl text-white">{stats.total}</div>
|
|
3069
3257
|
</div>
|
|
3070
|
-
<div className="
|
|
3258
|
+
<div className="theme-card p-5 border-matrix/20">
|
|
3071
3259
|
<div className="font-mono text-[10px] text-gray-500 mb-2">{language === 'es' ? 'COMPLETADAS' : 'COMPLETED'}</div>
|
|
3072
3260
|
<div className="font-display text-3xl text-matrix">{stats.completed}</div>
|
|
3073
3261
|
<div className="font-mono text-[10px] text-matrix/60 mt-1">{completionRate}%</div>
|
|
3074
3262
|
</div>
|
|
3075
|
-
<div className="
|
|
3263
|
+
<div className="theme-card p-5 border-signal/20">
|
|
3076
3264
|
<div className="font-mono text-[10px] text-gray-500 mb-2">{language === 'es' ? 'EN PROGRESO' : 'IN PROGRESS'}</div>
|
|
3077
3265
|
<div className="font-display text-3xl text-signal">{stats.inProgress}</div>
|
|
3078
3266
|
</div>
|
|
3079
|
-
<div className="
|
|
3267
|
+
<div className="theme-card p-5">
|
|
3080
3268
|
<div className="font-mono text-[10px] text-gray-500 mb-2">{language === 'es' ? 'PENDIENTES' : 'PENDING'}</div>
|
|
3081
3269
|
<div className="font-display text-3xl text-gray-500">{stats.pending}</div>
|
|
3082
3270
|
</div>
|
|
3083
3271
|
</div>
|
|
3084
3272
|
|
|
3085
3273
|
{/* Progress Overview */}
|
|
3086
|
-
<div className="
|
|
3274
|
+
<div className="theme-card p-5">
|
|
3087
3275
|
<h3 className="font-mono text-sm text-white mb-4">{language === 'es' ? 'PROGRESO GENERAL' : 'OVERALL PROGRESS'}</h3>
|
|
3088
3276
|
<div className="relative h-8 bg-void-100 border border-white/5 overflow-hidden">
|
|
3089
3277
|
<div
|
|
@@ -3104,7 +3292,7 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3104
3292
|
</div>
|
|
3105
3293
|
|
|
3106
3294
|
{/* By Priority */}
|
|
3107
|
-
<div className="
|
|
3295
|
+
<div className="theme-card p-5">
|
|
3108
3296
|
<h3 className="font-mono text-sm text-white mb-4">{language === 'es' ? 'POR PRIORIDAD' : 'BY PRIORITY'}</h3>
|
|
3109
3297
|
<div className="grid grid-cols-3 gap-4">
|
|
3110
3298
|
<div className="p-4 bg-alert/5 border border-alert/20">
|
|
@@ -3132,7 +3320,7 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3132
3320
|
</div>
|
|
3133
3321
|
|
|
3134
3322
|
{/* By Feature */}
|
|
3135
|
-
<div className="
|
|
3323
|
+
<div className="theme-card p-5">
|
|
3136
3324
|
<h3 className="font-mono text-sm text-white mb-4">{language === 'es' ? 'POR FEATURE' : 'BY FEATURE'}</h3>
|
|
3137
3325
|
<div className="space-y-3">
|
|
3138
3326
|
{stats.byFeature.map((feature, idx) => (
|
|
@@ -3156,7 +3344,7 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3156
3344
|
</div>
|
|
3157
3345
|
|
|
3158
3346
|
{/* Export Options */}
|
|
3159
|
-
<div className="
|
|
3347
|
+
<div className="theme-card p-5">
|
|
3160
3348
|
<h3 className="font-mono text-sm text-white mb-4">{language === 'es' ? 'EXPORTAR DATOS' : 'EXPORT DATA'}</h3>
|
|
3161
3349
|
<div className="flex gap-3">
|
|
3162
3350
|
<button
|
|
@@ -3176,7 +3364,7 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3176
3364
|
a.download = 'roadmap-export.csv';
|
|
3177
3365
|
a.click();
|
|
3178
3366
|
}}
|
|
3179
|
-
className="btn-
|
|
3367
|
+
className="theme-btn-primary flex items-center gap-2"
|
|
3180
3368
|
>
|
|
3181
3369
|
<FileDown className="w-4 h-4" />
|
|
3182
3370
|
CSV
|
|
@@ -3190,7 +3378,7 @@ function MetricsTab({ roadmap, language }) {
|
|
|
3190
3378
|
a.download = 'roadmap-backup.json';
|
|
3191
3379
|
a.click();
|
|
3192
3380
|
}}
|
|
3193
|
-
className="btn-
|
|
3381
|
+
className="theme-btn-primary flex items-center gap-2"
|
|
3194
3382
|
>
|
|
3195
3383
|
<FileDown className="w-4 h-4" />
|
|
3196
3384
|
JSON
|
|
@@ -3216,23 +3404,23 @@ function ResourcesTab({ resources }) {
|
|
|
3216
3404
|
<div className="space-y-6 animate-fade-in">
|
|
3217
3405
|
{/* Summary */}
|
|
3218
3406
|
<div className="grid grid-cols-3 gap-4">
|
|
3219
|
-
<div className="
|
|
3407
|
+
<div className="theme-card p-5">
|
|
3220
3408
|
<div className="flex items-center gap-3 mb-3">
|
|
3221
|
-
<div className="led led-
|
|
3409
|
+
<div className="led theme-led theme-led-success" />
|
|
3222
3410
|
<span className="font-mono text-[10px] text-gray-500 tracking-wider">UI COMPONENTS</span>
|
|
3223
3411
|
</div>
|
|
3224
3412
|
<div className="metric-display text-3xl text-matrix">{ui_components.length}</div>
|
|
3225
3413
|
</div>
|
|
3226
|
-
<div className="
|
|
3414
|
+
<div className="theme-card p-5">
|
|
3227
3415
|
<div className="flex items-center gap-3 mb-3">
|
|
3228
|
-
<div className="led led-
|
|
3416
|
+
<div className="led theme-led theme-led-warning" />
|
|
3229
3417
|
<span className="font-mono text-[10px] text-gray-500 tracking-wider">UTILITIES</span>
|
|
3230
3418
|
</div>
|
|
3231
3419
|
<div className="metric-display text-3xl text-signal">{utilities.length}</div>
|
|
3232
3420
|
</div>
|
|
3233
|
-
<div className="
|
|
3421
|
+
<div className="theme-card p-5">
|
|
3234
3422
|
<div className="flex items-center gap-3 mb-3">
|
|
3235
|
-
<div className="led led-
|
|
3423
|
+
<div className="led theme-led theme-led-success" />
|
|
3236
3424
|
<span className="font-mono text-[10px] text-gray-500 tracking-wider">DB TABLES</span>
|
|
3237
3425
|
</div>
|
|
3238
3426
|
<div className="metric-display text-3xl text-cyber">{database_tables.length}</div>
|
|
@@ -3241,7 +3429,7 @@ function ResourcesTab({ resources }) {
|
|
|
3241
3429
|
|
|
3242
3430
|
{/* UI Components */}
|
|
3243
3431
|
{ui_components.length > 0 && (
|
|
3244
|
-
<div className="
|
|
3432
|
+
<div className="theme-card p-5">
|
|
3245
3433
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4"># UI_COMPONENTS</h3>
|
|
3246
3434
|
<div className="space-y-3">
|
|
3247
3435
|
{ui_components.map((comp, idx) => (
|
|
@@ -3265,7 +3453,7 @@ function ResourcesTab({ resources }) {
|
|
|
3265
3453
|
|
|
3266
3454
|
{/* Utilities */}
|
|
3267
3455
|
{utilities.length > 0 && (
|
|
3268
|
-
<div className="
|
|
3456
|
+
<div className="theme-card p-5">
|
|
3269
3457
|
<h3 className="font-mono text-sm text-signal tracking-wider mb-4"># UTILITIES</h3>
|
|
3270
3458
|
<div className="space-y-3">
|
|
3271
3459
|
{utilities.map((util, idx) => (
|
|
@@ -3302,7 +3490,7 @@ function ResourcesTab({ resources }) {
|
|
|
3302
3490
|
|
|
3303
3491
|
{/* Database Tables */}
|
|
3304
3492
|
{database_tables.length > 0 && (
|
|
3305
|
-
<div className="
|
|
3493
|
+
<div className="theme-card p-5">
|
|
3306
3494
|
<h3 className="font-mono text-sm text-cyber tracking-wider mb-4"># DATABASE_TABLES</h3>
|
|
3307
3495
|
<div className="space-y-3">
|
|
3308
3496
|
{database_tables.map((table, idx) => (
|
|
@@ -3324,7 +3512,7 @@ function ResourcesTab({ resources }) {
|
|
|
3324
3512
|
}
|
|
3325
3513
|
|
|
3326
3514
|
// ============ DEBT TAB ============
|
|
3327
|
-
function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
3515
|
+
function DebtTab({ roadmap, setRoadmap, setHasChanges, t }) {
|
|
3328
3516
|
const [expandedDebt, setExpandedDebt] = useState(null);
|
|
3329
3517
|
const [filterSeverity, setFilterSeverity] = useState('all');
|
|
3330
3518
|
const [filterStatus, setFilterStatus] = useState('all');
|
|
@@ -3368,9 +3556,9 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3368
3556
|
});
|
|
3369
3557
|
|
|
3370
3558
|
const statusConfig = {
|
|
3371
|
-
pending: { led: 'led-gray', label: '
|
|
3372
|
-
in_progress: { led: 'led-
|
|
3373
|
-
resolved: { led: 'led-
|
|
3559
|
+
pending: { led: 'led-gray', label: t('debt.pending'), color: 'text-gray-500' },
|
|
3560
|
+
in_progress: { led: 'theme-led theme-led-warning', label: t('debt.inProgress'), color: 'text-signal' },
|
|
3561
|
+
resolved: { led: 'theme-led theme-led-success', label: t('debt.resolved'), color: 'text-matrix' }
|
|
3374
3562
|
};
|
|
3375
3563
|
|
|
3376
3564
|
// Update debt status
|
|
@@ -3454,70 +3642,70 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3454
3642
|
|
|
3455
3643
|
return (
|
|
3456
3644
|
<div className="space-y-6 animate-fade-in">
|
|
3457
|
-
{/*
|
|
3645
|
+
{/* Resumen */}
|
|
3458
3646
|
<div className="grid grid-cols-3 gap-4">
|
|
3459
3647
|
<button
|
|
3460
3648
|
onClick={() => setFilterSeverity(filterSeverity === 'high' ? 'all' : 'high')}
|
|
3461
|
-
className={`
|
|
3649
|
+
className={`theme-card p-5 transition-all ${filterSeverity === 'high' ? 'border-alert glow-alert' : 'border-alert/30 hover:border-alert/50'}`}
|
|
3462
3650
|
>
|
|
3463
3651
|
<div className="flex items-center gap-3 mb-3">
|
|
3464
|
-
<div className="led led-
|
|
3465
|
-
<span className="font-mono text-[10px] text-gray-500 tracking-wider">
|
|
3652
|
+
<div className="led theme-led theme-led-danger" />
|
|
3653
|
+
<span className="font-mono text-[10px] text-gray-500 tracking-wider">{t('debt.high')}</span>
|
|
3466
3654
|
</div>
|
|
3467
3655
|
<div className="metric-display text-3xl text-alert">{bySeverity.high.length}</div>
|
|
3468
3656
|
</button>
|
|
3469
3657
|
<button
|
|
3470
3658
|
onClick={() => setFilterSeverity(filterSeverity === 'medium' ? 'all' : 'medium')}
|
|
3471
|
-
className={`
|
|
3659
|
+
className={`theme-card p-5 transition-all ${filterSeverity === 'medium' ? 'border-signal glow-signal' : 'border-signal/30 hover:border-signal/50'}`}
|
|
3472
3660
|
>
|
|
3473
3661
|
<div className="flex items-center gap-3 mb-3">
|
|
3474
|
-
<div className="led led-
|
|
3475
|
-
<span className="font-mono text-[10px] text-gray-500 tracking-wider">
|
|
3662
|
+
<div className="led theme-led theme-led-warning" />
|
|
3663
|
+
<span className="font-mono text-[10px] text-gray-500 tracking-wider">{t('debt.medium')}</span>
|
|
3476
3664
|
</div>
|
|
3477
3665
|
<div className="metric-display text-3xl text-signal">{bySeverity.medium.length}</div>
|
|
3478
3666
|
</button>
|
|
3479
3667
|
<button
|
|
3480
3668
|
onClick={() => setFilterSeverity(filterSeverity === 'low' ? 'all' : 'low')}
|
|
3481
|
-
className={`
|
|
3669
|
+
className={`theme-card p-5 transition-all ${filterSeverity === 'low' ? 'border-cyber glow-cyber' : 'border-cyber/30 hover:border-cyber/50'}`}
|
|
3482
3670
|
>
|
|
3483
3671
|
<div className="flex items-center gap-3 mb-3">
|
|
3484
|
-
<div className="led led-
|
|
3485
|
-
<span className="font-mono text-[10px] text-gray-500 tracking-wider">
|
|
3672
|
+
<div className="led theme-led theme-led-success" />
|
|
3673
|
+
<span className="font-mono text-[10px] text-gray-500 tracking-wider">{t('debt.low')}</span>
|
|
3486
3674
|
</div>
|
|
3487
3675
|
<div className="metric-display text-3xl text-cyber">{bySeverity.low.length}</div>
|
|
3488
3676
|
</button>
|
|
3489
3677
|
</div>
|
|
3490
3678
|
|
|
3491
|
-
{/*
|
|
3679
|
+
{/* Filtros y Botón Añadir */}
|
|
3492
3680
|
<div className="flex items-center gap-3 flex-wrap">
|
|
3493
|
-
<span className="font-mono text-[10px] text-gray-600">
|
|
3681
|
+
<span className="font-mono text-[10px] text-gray-600">{t('debt.filter')}:</span>
|
|
3494
3682
|
<select
|
|
3495
3683
|
value={filterStatus}
|
|
3496
3684
|
onChange={(e) => setFilterStatus(e.target.value)}
|
|
3497
|
-
className="input
|
|
3685
|
+
className="theme-input px-3 py-2 text-xs"
|
|
3498
3686
|
>
|
|
3499
|
-
<option value="all">
|
|
3500
|
-
<option value="pending">
|
|
3501
|
-
<option value="in_progress">
|
|
3502
|
-
<option value="resolved">
|
|
3687
|
+
<option value="all">{t('debt.all')}</option>
|
|
3688
|
+
<option value="pending">{t('debt.pending')}</option>
|
|
3689
|
+
<option value="in_progress">{t('debt.inProgress')}</option>
|
|
3690
|
+
<option value="resolved">{t('debt.resolved')}</option>
|
|
3503
3691
|
</select>
|
|
3504
3692
|
{(filterSeverity !== 'all' || filterStatus !== 'all') && (
|
|
3505
3693
|
<button
|
|
3506
3694
|
onClick={() => { setFilterSeverity('all'); setFilterStatus('all'); }}
|
|
3507
3695
|
className="font-mono text-[10px] text-gray-500 hover:text-white transition-all"
|
|
3508
3696
|
>
|
|
3509
|
-
|
|
3697
|
+
{t('debt.clear')}
|
|
3510
3698
|
</button>
|
|
3511
3699
|
)}
|
|
3512
3700
|
<span className="font-mono text-[10px] text-gray-600">
|
|
3513
|
-
{filteredDebts.length} / {allDebts.length} items
|
|
3701
|
+
{filteredDebts.length} / {allDebts.length} {t('debt.items')}
|
|
3514
3702
|
</span>
|
|
3515
3703
|
<button
|
|
3516
3704
|
onClick={() => setShowAddDebt(true)}
|
|
3517
|
-
className="ml-auto btn-
|
|
3705
|
+
className="ml-auto theme-btn-primary flex items-center gap-2"
|
|
3518
3706
|
>
|
|
3519
3707
|
<Plus className="w-4 h-4" />
|
|
3520
|
-
|
|
3708
|
+
{t('debt.addDebt')}
|
|
3521
3709
|
</button>
|
|
3522
3710
|
</div>
|
|
3523
3711
|
|
|
@@ -3533,8 +3721,8 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3533
3721
|
|
|
3534
3722
|
{/* Debt Items */}
|
|
3535
3723
|
{filteredDebts.length > 0 ? (
|
|
3536
|
-
<div className="
|
|
3537
|
-
<h3 className="font-mono text-sm text-signal tracking-wider mb-4">#
|
|
3724
|
+
<div className="theme-card p-5">
|
|
3725
|
+
<h3 className="font-mono text-sm text-signal tracking-wider mb-4"># {t('debt.title')}</h3>
|
|
3538
3726
|
<div className="space-y-2">
|
|
3539
3727
|
{filteredDebts.map((debt) => {
|
|
3540
3728
|
const isExpanded = expandedDebt === debt.id;
|
|
@@ -3583,9 +3771,9 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3583
3771
|
{/* Expanded Content */}
|
|
3584
3772
|
{isExpanded && !isEditing && (
|
|
3585
3773
|
<div className="px-4 pb-4 pt-2 border-t border-white/5 space-y-4 animate-fade-in">
|
|
3586
|
-
{/*
|
|
3774
|
+
{/* Controles de Estado */}
|
|
3587
3775
|
<div className="flex items-center gap-2 p-3 bg-black/30 border border-white/5">
|
|
3588
|
-
<span className="font-mono text-[10px] text-gray-600">
|
|
3776
|
+
<span className="font-mono text-[10px] text-gray-600">{t('debt.status')}:</span>
|
|
3589
3777
|
<div className="flex gap-1">
|
|
3590
3778
|
{['pending', 'in_progress', 'resolved'].map((s) => {
|
|
3591
3779
|
const cfg = statusConfig[s];
|
|
@@ -3622,36 +3810,36 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3622
3810
|
</div>
|
|
3623
3811
|
</div>
|
|
3624
3812
|
|
|
3625
|
-
{/*
|
|
3813
|
+
{/* Descripción Completa */}
|
|
3626
3814
|
<div>
|
|
3627
|
-
<h5 className="font-mono text-[10px] text-gray-600 tracking-wider mb-2">#
|
|
3815
|
+
<h5 className="font-mono text-[10px] text-gray-600 tracking-wider mb-2"># {t('debt.description')}</h5>
|
|
3628
3816
|
<p className="font-mono text-xs text-gray-400">{debt.description}</p>
|
|
3629
3817
|
</div>
|
|
3630
3818
|
|
|
3631
|
-
{/*
|
|
3819
|
+
{/* Impacto */}
|
|
3632
3820
|
{debt.impact && (
|
|
3633
3821
|
<div className="p-3 border border-alert/20 bg-alert/5">
|
|
3634
3822
|
<h5 className="font-mono text-[10px] text-alert tracking-wider mb-2 flex items-center gap-2">
|
|
3635
|
-
<AlertTriangle className="w-3 h-3" />
|
|
3823
|
+
<AlertTriangle className="w-3 h-3" /> {t('debt.impact')}
|
|
3636
3824
|
</h5>
|
|
3637
3825
|
<p className="font-mono text-xs text-gray-400">{debt.impact}</p>
|
|
3638
3826
|
</div>
|
|
3639
3827
|
)}
|
|
3640
3828
|
|
|
3641
|
-
{/*
|
|
3829
|
+
{/* Solución */}
|
|
3642
3830
|
{debt.solution && (
|
|
3643
3831
|
<div className="p-3 border border-matrix/20 bg-matrix/5">
|
|
3644
3832
|
<h5 className="font-mono text-[10px] text-matrix tracking-wider mb-2 flex items-center gap-2">
|
|
3645
|
-
<Wrench className="w-3 h-3" />
|
|
3833
|
+
<Wrench className="w-3 h-3" /> {t('debt.solution')}
|
|
3646
3834
|
</h5>
|
|
3647
3835
|
<p className="font-mono text-xs text-gray-400">{debt.solution}</p>
|
|
3648
3836
|
</div>
|
|
3649
3837
|
)}
|
|
3650
3838
|
|
|
3651
|
-
{/*
|
|
3839
|
+
{/* Archivos Afectados */}
|
|
3652
3840
|
{debt.affected_files && debt.affected_files.length > 0 && (
|
|
3653
3841
|
<div>
|
|
3654
|
-
<h5 className="font-mono text-[10px] text-gray-600 tracking-wider mb-2">#
|
|
3842
|
+
<h5 className="font-mono text-[10px] text-gray-600 tracking-wider mb-2"># {t('debt.affectedFiles')}</h5>
|
|
3655
3843
|
<div className="flex flex-wrap gap-2">
|
|
3656
3844
|
{debt.affected_files.map((file, idx) => (
|
|
3657
3845
|
<code key={idx} className="font-mono text-[11px] px-2 py-1 bg-black/50 border border-white/10 text-cyber">
|
|
@@ -3665,19 +3853,19 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3665
3853
|
{/* Metadata Grid */}
|
|
3666
3854
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
3667
3855
|
<div className="bg-black/30 p-3 border border-white/5">
|
|
3668
|
-
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">
|
|
3856
|
+
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">{t('debt.feature')}</div>
|
|
3669
3857
|
<div className="font-mono text-xs text-white truncate">{debt.featureName}</div>
|
|
3670
3858
|
</div>
|
|
3671
3859
|
<div className="bg-black/30 p-3 border border-white/5">
|
|
3672
|
-
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">
|
|
3860
|
+
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">{t('debt.task')}</div>
|
|
3673
3861
|
<div className="font-mono text-xs text-white truncate">{debt.taskName}</div>
|
|
3674
3862
|
</div>
|
|
3675
3863
|
<div className="bg-black/30 p-3 border border-white/5">
|
|
3676
|
-
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">
|
|
3677
|
-
<div className="font-mono text-xs text-signal">{debt.estimated_effort || '
|
|
3864
|
+
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">{t('debt.effort')}</div>
|
|
3865
|
+
<div className="font-mono text-xs text-signal">{debt.estimated_effort || t('debt.notEstimated')}</div>
|
|
3678
3866
|
</div>
|
|
3679
3867
|
<div className="bg-black/30 p-3 border border-white/5">
|
|
3680
|
-
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">
|
|
3868
|
+
<div className="font-mono text-[9px] text-gray-600 tracking-wider mb-1">{t('debt.priority')}</div>
|
|
3681
3869
|
<div className={`font-mono text-xs ${
|
|
3682
3870
|
debt.priority === 'critical' ? 'text-alert' :
|
|
3683
3871
|
debt.priority === 'high' ? 'text-signal' :
|
|
@@ -3689,16 +3877,16 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3689
3877
|
{/* Additional Info */}
|
|
3690
3878
|
<div className="flex items-center gap-4 pt-3 border-t border-white/5 font-mono text-[10px] text-gray-600 flex-wrap">
|
|
3691
3879
|
{debt.created_at && (
|
|
3692
|
-
<span>
|
|
3880
|
+
<span>{t('debt.created')}: {new Date(debt.created_at).toLocaleDateString()}</span>
|
|
3693
3881
|
)}
|
|
3694
3882
|
{debt.resolved_at && (
|
|
3695
|
-
<span className="text-matrix">
|
|
3883
|
+
<span className="text-matrix">{t('debt.resolvedAt')}: {new Date(debt.resolved_at).toLocaleDateString()}</span>
|
|
3696
3884
|
)}
|
|
3697
3885
|
{debt.assigned_to && (
|
|
3698
|
-
<span className="text-signal">
|
|
3886
|
+
<span className="text-signal">{t('debt.assigned')}: @{debt.assigned_to}</span>
|
|
3699
3887
|
)}
|
|
3700
3888
|
{debt.due_date && (
|
|
3701
|
-
<span className="text-alert">
|
|
3889
|
+
<span className="text-alert">{t('debt.due')}: {new Date(debt.due_date).toLocaleDateString()}</span>
|
|
3702
3890
|
)}
|
|
3703
3891
|
</div>
|
|
3704
3892
|
|
|
@@ -3734,28 +3922,28 @@ function DebtTab({ roadmap, setRoadmap, setHasChanges }) {
|
|
|
3734
3922
|
</div>
|
|
3735
3923
|
</div>
|
|
3736
3924
|
) : (
|
|
3737
|
-
<div className="
|
|
3925
|
+
<div className="theme-card p-12 text-center">
|
|
3738
3926
|
<CheckCircle2 className="w-10 h-10 mx-auto mb-4 text-matrix" />
|
|
3739
3927
|
<p className="font-mono text-sm text-gray-600">
|
|
3740
|
-
{allDebts.length === 0 ? '
|
|
3928
|
+
{allDebts.length === 0 ? t('debt.noDebt') : t('debt.noMatch')}
|
|
3741
3929
|
</p>
|
|
3742
3930
|
{allDebts.length === 0 && (
|
|
3743
3931
|
<button
|
|
3744
3932
|
onClick={() => setShowAddDebt(true)}
|
|
3745
|
-
className="btn-
|
|
3933
|
+
className="theme-btn-primary mt-4 inline-flex items-center gap-2"
|
|
3746
3934
|
>
|
|
3747
3935
|
<Plus className="w-4 h-4" />
|
|
3748
|
-
|
|
3936
|
+
{t('debt.addFirstDebt')}
|
|
3749
3937
|
</button>
|
|
3750
3938
|
)}
|
|
3751
3939
|
</div>
|
|
3752
3940
|
)}
|
|
3753
3941
|
|
|
3754
3942
|
{/* Debt Schema Info */}
|
|
3755
|
-
<div className="
|
|
3943
|
+
<div className="theme-card p-4">
|
|
3756
3944
|
<details>
|
|
3757
3945
|
<summary className="font-mono text-[10px] text-gray-600 cursor-pointer hover:text-white">
|
|
3758
|
-
|
|
3946
|
+
{t('debt.jsonSchema')}
|
|
3759
3947
|
</summary>
|
|
3760
3948
|
<pre className="mt-4 p-4 bg-void-100 border border-white/5 font-mono text-[10px] text-gray-500 overflow-x-auto">
|
|
3761
3949
|
{`{
|
|
@@ -3826,7 +4014,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3826
4014
|
};
|
|
3827
4015
|
|
|
3828
4016
|
return (
|
|
3829
|
-
<form onSubmit={handleSubmit} className="
|
|
4017
|
+
<form onSubmit={handleSubmit} className="theme-card p-5 space-y-4">
|
|
3830
4018
|
<h3 className="font-mono text-sm text-signal tracking-wider">
|
|
3831
4019
|
{isEdit ? '# EDIT DEBT' : '# NEW TECHNICAL DEBT'}
|
|
3832
4020
|
</h3>
|
|
@@ -3838,7 +4026,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3838
4026
|
<select
|
|
3839
4027
|
value={featureId}
|
|
3840
4028
|
onChange={(e) => { setFeatureId(e.target.value); setTaskId(''); }}
|
|
3841
|
-
className="input
|
|
4029
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
3842
4030
|
required
|
|
3843
4031
|
>
|
|
3844
4032
|
<option value="">Select feature...</option>
|
|
@@ -3852,7 +4040,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3852
4040
|
<select
|
|
3853
4041
|
value={taskId}
|
|
3854
4042
|
onChange={(e) => setTaskId(e.target.value)}
|
|
3855
|
-
className="input
|
|
4043
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
3856
4044
|
required
|
|
3857
4045
|
disabled={!featureId}
|
|
3858
4046
|
>
|
|
@@ -3872,7 +4060,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3872
4060
|
onChange={(e) => setDescription(e.target.value)}
|
|
3873
4061
|
placeholder="Describe the technical debt..."
|
|
3874
4062
|
rows={2}
|
|
3875
|
-
className="input
|
|
4063
|
+
className="theme-input w-full px-3 py-2 text-sm resize-none"
|
|
3876
4064
|
required
|
|
3877
4065
|
/>
|
|
3878
4066
|
</div>
|
|
@@ -3880,7 +4068,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3880
4068
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
3881
4069
|
<div>
|
|
3882
4070
|
<label className="font-mono text-[10px] text-gray-500 tracking-wider mb-2 block">SEVERITY</label>
|
|
3883
|
-
<select value={severity} onChange={(e) => setSeverity(e.target.value)} className="input
|
|
4071
|
+
<select value={severity} onChange={(e) => setSeverity(e.target.value)} className="theme-input w-full px-3 py-2 text-sm">
|
|
3884
4072
|
<option value="high">HIGH</option>
|
|
3885
4073
|
<option value="medium">MEDIUM</option>
|
|
3886
4074
|
<option value="low">LOW</option>
|
|
@@ -3888,7 +4076,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3888
4076
|
</div>
|
|
3889
4077
|
<div>
|
|
3890
4078
|
<label className="font-mono text-[10px] text-gray-500 tracking-wider mb-2 block">PRIORITY</label>
|
|
3891
|
-
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="input
|
|
4079
|
+
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="theme-input w-full px-3 py-2 text-sm">
|
|
3892
4080
|
<option value="critical">CRITICAL</option>
|
|
3893
4081
|
<option value="high">HIGH</option>
|
|
3894
4082
|
<option value="medium">MEDIUM</option>
|
|
@@ -3902,12 +4090,12 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3902
4090
|
value={estimatedEffort}
|
|
3903
4091
|
onChange={(e) => setEstimatedEffort(e.target.value)}
|
|
3904
4092
|
placeholder="e.g. 2h, 1d"
|
|
3905
|
-
className="input
|
|
4093
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
3906
4094
|
/>
|
|
3907
4095
|
</div>
|
|
3908
4096
|
<div>
|
|
3909
4097
|
<label className="font-mono text-[10px] text-gray-500 tracking-wider mb-2 block">ASSIGNED</label>
|
|
3910
|
-
<select value={assignedTo} onChange={(e) => setAssignedTo(e.target.value)} className="input
|
|
4098
|
+
<select value={assignedTo} onChange={(e) => setAssignedTo(e.target.value)} className="theme-input w-full px-3 py-2 text-sm">
|
|
3911
4099
|
<option value="">Unassigned</option>
|
|
3912
4100
|
{team.map(m => (
|
|
3913
4101
|
<option key={m.id} value={m.name}>{m.name}</option>
|
|
@@ -3923,7 +4111,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3923
4111
|
onChange={(e) => setImpact(e.target.value)}
|
|
3924
4112
|
placeholder="What happens if not fixed?"
|
|
3925
4113
|
rows={2}
|
|
3926
|
-
className="input
|
|
4114
|
+
className="theme-input w-full px-3 py-2 text-sm resize-none"
|
|
3927
4115
|
/>
|
|
3928
4116
|
</div>
|
|
3929
4117
|
|
|
@@ -3934,7 +4122,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3934
4122
|
onChange={(e) => setSolution(e.target.value)}
|
|
3935
4123
|
placeholder="How to fix this?"
|
|
3936
4124
|
rows={2}
|
|
3937
|
-
className="input
|
|
4125
|
+
className="theme-input w-full px-3 py-2 text-sm resize-none"
|
|
3938
4126
|
/>
|
|
3939
4127
|
</div>
|
|
3940
4128
|
|
|
@@ -3946,7 +4134,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3946
4134
|
value={affectedFiles}
|
|
3947
4135
|
onChange={(e) => setAffectedFiles(e.target.value)}
|
|
3948
4136
|
placeholder="src/file1.js, src/file2.js"
|
|
3949
|
-
className="input
|
|
4137
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
3950
4138
|
/>
|
|
3951
4139
|
</div>
|
|
3952
4140
|
<div>
|
|
@@ -3956,7 +4144,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3956
4144
|
value={tags}
|
|
3957
4145
|
onChange={(e) => setTags(e.target.value)}
|
|
3958
4146
|
placeholder="security, performance"
|
|
3959
|
-
className="input
|
|
4147
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
3960
4148
|
/>
|
|
3961
4149
|
</div>
|
|
3962
4150
|
</div>
|
|
@@ -3965,7 +4153,7 @@ function DebtForm({ features, team, initialData, onSave, onCancel, isEdit }) {
|
|
|
3965
4153
|
<button type="button" onClick={onCancel} className="px-4 py-2 border border-white/10 text-gray-500 font-mono text-xs hover:text-white transition-all">
|
|
3966
4154
|
CANCEL
|
|
3967
4155
|
</button>
|
|
3968
|
-
<button type="submit" className="btn-
|
|
4156
|
+
<button type="submit" className="theme-btn-primary flex-1 py-2">
|
|
3969
4157
|
{isEdit ? 'UPDATE' : 'ADD DEBT'}
|
|
3970
4158
|
</button>
|
|
3971
4159
|
</div>
|
|
@@ -3980,7 +4168,7 @@ function InfoTab({ projectInfo }) {
|
|
|
3980
4168
|
return (
|
|
3981
4169
|
<div className="space-y-6 animate-fade-in">
|
|
3982
4170
|
{/* Stack */}
|
|
3983
|
-
<div className="
|
|
4171
|
+
<div className="theme-card p-5">
|
|
3984
4172
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4"># STACK</h3>
|
|
3985
4173
|
<div className="flex flex-wrap gap-2">
|
|
3986
4174
|
{(projectInfo.stack || []).map((tech, idx) => (
|
|
@@ -3996,7 +4184,7 @@ function InfoTab({ projectInfo }) {
|
|
|
3996
4184
|
|
|
3997
4185
|
{/* Architecture */}
|
|
3998
4186
|
{projectInfo.architecture && (
|
|
3999
|
-
<div className="
|
|
4187
|
+
<div className="theme-card p-5">
|
|
4000
4188
|
<h3 className="font-mono text-sm text-cyber tracking-wider mb-4"># ARCHITECTURE</h3>
|
|
4001
4189
|
<p className="font-mono text-xs text-gray-400 leading-relaxed">{projectInfo.architecture}</p>
|
|
4002
4190
|
</div>
|
|
@@ -4004,7 +4192,7 @@ function InfoTab({ projectInfo }) {
|
|
|
4004
4192
|
|
|
4005
4193
|
{/* Conventions */}
|
|
4006
4194
|
{Object.keys(conventions).length > 0 && (
|
|
4007
|
-
<div className="
|
|
4195
|
+
<div className="theme-card p-5">
|
|
4008
4196
|
<h3 className="font-mono text-sm text-signal tracking-wider mb-4"># CONVENTIONS</h3>
|
|
4009
4197
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4010
4198
|
{conventions.naming && (
|
|
@@ -4054,7 +4242,7 @@ function InfoTab({ projectInfo }) {
|
|
|
4054
4242
|
|
|
4055
4243
|
{/* Purpose */}
|
|
4056
4244
|
{projectInfo.purpose && (
|
|
4057
|
-
<div className="
|
|
4245
|
+
<div className="theme-card p-5">
|
|
4058
4246
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4"># PURPOSE</h3>
|
|
4059
4247
|
<p className="font-mono text-xs text-gray-400 leading-relaxed">{projectInfo.purpose}</p>
|
|
4060
4248
|
</div>
|
|
@@ -4071,6 +4259,267 @@ function InfoTab({ projectInfo }) {
|
|
|
4071
4259
|
);
|
|
4072
4260
|
}
|
|
4073
4261
|
|
|
4262
|
+
// ============ APPEARANCE SECTION ============
|
|
4263
|
+
function AppearanceSection({ language }) {
|
|
4264
|
+
const { theme: currentTheme, setTheme, colorMode, setColorMode, isDark } = useTheme();
|
|
4265
|
+
const [saving, setSaving] = useState(false);
|
|
4266
|
+
const [savingMode, setSavingMode] = useState(false);
|
|
4267
|
+
const toast = useToast();
|
|
4268
|
+
|
|
4269
|
+
const handleThemeChange = async (themeId) => {
|
|
4270
|
+
if (themeId === currentTheme) return;
|
|
4271
|
+
setSaving(true);
|
|
4272
|
+
try {
|
|
4273
|
+
await setTheme(themeId);
|
|
4274
|
+
toast.success(language === 'es' ? 'Tema actualizado' : 'Theme updated');
|
|
4275
|
+
} catch (err) {
|
|
4276
|
+
toast.error(language === 'es' ? 'Error al guardar el tema' : 'Error saving theme');
|
|
4277
|
+
}
|
|
4278
|
+
setSaving(false);
|
|
4279
|
+
};
|
|
4280
|
+
|
|
4281
|
+
const handleColorModeChange = async (mode) => {
|
|
4282
|
+
if (mode === colorMode) return;
|
|
4283
|
+
setSavingMode(true);
|
|
4284
|
+
try {
|
|
4285
|
+
await setColorMode(mode);
|
|
4286
|
+
toast.success(language === 'es' ? 'Modo de color actualizado' : 'Color mode updated');
|
|
4287
|
+
} catch (err) {
|
|
4288
|
+
toast.error(language === 'es' ? 'Error al guardar el modo' : 'Error saving mode');
|
|
4289
|
+
}
|
|
4290
|
+
setSavingMode(false);
|
|
4291
|
+
};
|
|
4292
|
+
|
|
4293
|
+
// Theme preview SVG components
|
|
4294
|
+
const GlassPreview = () => (
|
|
4295
|
+
<svg viewBox="0 0 120 80" className="w-full h-full">
|
|
4296
|
+
{/* Background */}
|
|
4297
|
+
<rect width="120" height="80" fill="#f1f5f9" />
|
|
4298
|
+
{/* Sidebar */}
|
|
4299
|
+
<rect x="0" y="0" width="30" height="80" fill="#ffffff" fillOpacity="0.7" />
|
|
4300
|
+
<rect x="4" y="8" width="22" height="4" rx="2" fill="#10b981" />
|
|
4301
|
+
<rect x="4" y="16" width="22" height="3" rx="1.5" fill="#e2e8f0" />
|
|
4302
|
+
<rect x="4" y="22" width="22" height="3" rx="1.5" fill="#e2e8f0" />
|
|
4303
|
+
<rect x="4" y="28" width="22" height="3" rx="1.5" fill="#e2e8f0" />
|
|
4304
|
+
{/* Cards */}
|
|
4305
|
+
<rect x="36" y="8" width="38" height="28" rx="4" fill="#ffffff" stroke="#e2e8f0" strokeWidth="1" />
|
|
4306
|
+
<rect x="40" y="12" width="20" height="3" rx="1.5" fill="#1e293b" />
|
|
4307
|
+
<rect x="40" y="18" width="30" height="2" rx="1" fill="#94a3b8" />
|
|
4308
|
+
<rect x="40" y="24" width="30" height="4" rx="2" fill="#e2e8f0" />
|
|
4309
|
+
<rect x="40" y="24" width="18" height="4" rx="2" fill="#10b981" />
|
|
4310
|
+
<rect x="80" y="8" width="34" height="28" rx="4" fill="#ffffff" stroke="#e2e8f0" strokeWidth="1" />
|
|
4311
|
+
<rect x="84" y="12" width="16" height="3" rx="1.5" fill="#1e293b" />
|
|
4312
|
+
<rect x="84" y="18" width="26" height="2" rx="1" fill="#94a3b8" />
|
|
4313
|
+
{/* Bottom cards */}
|
|
4314
|
+
<rect x="36" y="44" width="78" height="28" rx="4" fill="#ffffff" stroke="#e2e8f0" strokeWidth="1" />
|
|
4315
|
+
<rect x="40" y="48" width="24" height="3" rx="1.5" fill="#1e293b" />
|
|
4316
|
+
<rect x="40" y="54" width="70" height="2" rx="1" fill="#94a3b8" />
|
|
4317
|
+
<rect x="40" y="60" width="70" height="2" rx="1" fill="#94a3b8" />
|
|
4318
|
+
</svg>
|
|
4319
|
+
);
|
|
4320
|
+
|
|
4321
|
+
const MatrixPreview = () => (
|
|
4322
|
+
<svg viewBox="0 0 120 80" className="w-full h-full">
|
|
4323
|
+
{/* Background with grid */}
|
|
4324
|
+
<rect width="120" height="80" fill="#0a0a0a" />
|
|
4325
|
+
<defs>
|
|
4326
|
+
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
4327
|
+
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="#00ff88" strokeWidth="0.2" strokeOpacity="0.1" />
|
|
4328
|
+
</pattern>
|
|
4329
|
+
</defs>
|
|
4330
|
+
<rect width="120" height="80" fill="url(#grid)" />
|
|
4331
|
+
{/* Sidebar */}
|
|
4332
|
+
<rect x="0" y="0" width="30" height="80" fill="#050505" />
|
|
4333
|
+
<line x1="30" y1="0" x2="30" y2="80" stroke="#00ff88" strokeWidth="0.5" strokeOpacity="0.3" />
|
|
4334
|
+
<rect x="0" y="8" width="2" height="6" fill="#00ff88" />
|
|
4335
|
+
<rect x="6" y="9" width="20" height="4" fill="#00ff88" fillOpacity="0.2" />
|
|
4336
|
+
<text x="8" y="12.5" fill="#00ff88" fontSize="3" fontFamily="monospace">> MENU</text>
|
|
4337
|
+
<rect x="6" y="16" width="20" height="3" fill="#333" />
|
|
4338
|
+
<rect x="6" y="22" width="20" height="3" fill="#333" />
|
|
4339
|
+
<rect x="6" y="28" width="20" height="3" fill="#333" />
|
|
4340
|
+
{/* Cards */}
|
|
4341
|
+
<rect x="36" y="8" width="38" height="28" fill="#0d0d0d" stroke="#00ff88" strokeWidth="0.5" strokeOpacity="0.3" />
|
|
4342
|
+
<line x1="36" y1="8" x2="74" y2="8" stroke="#00ff88" strokeWidth="1" strokeOpacity="0.5" />
|
|
4343
|
+
<rect x="40" y="14" width="20" height="3" fill="#e0e0e0" />
|
|
4344
|
+
<rect x="40" y="20" width="30" height="2" fill="#555" />
|
|
4345
|
+
<rect x="40" y="26" width="30" height="4" fill="#1a1a1a" stroke="#333" strokeWidth="0.5" />
|
|
4346
|
+
<rect x="40" y="26" width="18" height="4" fill="#00ff88" />
|
|
4347
|
+
<rect x="80" y="8" width="34" height="28" fill="#0d0d0d" stroke="#00ff88" strokeWidth="0.5" strokeOpacity="0.3" />
|
|
4348
|
+
<line x1="80" y1="8" x2="114" y2="8" stroke="#00ff88" strokeWidth="1" strokeOpacity="0.5" />
|
|
4349
|
+
<rect x="84" y="14" width="16" height="3" fill="#e0e0e0" />
|
|
4350
|
+
<rect x="84" y="20" width="26" height="2" fill="#555" />
|
|
4351
|
+
{/* Bottom card */}
|
|
4352
|
+
<rect x="36" y="44" width="78" height="28" fill="#0d0d0d" stroke="#00ff88" strokeWidth="0.5" strokeOpacity="0.3" />
|
|
4353
|
+
<line x1="36" y1="44" x2="114" y2="44" stroke="#00ff88" strokeWidth="1" strokeOpacity="0.5" />
|
|
4354
|
+
<rect x="40" y="50" width="24" height="3" fill="#e0e0e0" />
|
|
4355
|
+
<rect x="40" y="56" width="70" height="2" fill="#555" />
|
|
4356
|
+
<rect x="40" y="62" width="70" height="2" fill="#555" />
|
|
4357
|
+
</svg>
|
|
4358
|
+
);
|
|
4359
|
+
|
|
4360
|
+
const themeOptions = [
|
|
4361
|
+
{
|
|
4362
|
+
id: 'glass',
|
|
4363
|
+
name: 'Glass',
|
|
4364
|
+
description: language === 'es' ? 'Interfaz moderna y limpia' : 'Modern and clean interface',
|
|
4365
|
+
preview: GlassPreview
|
|
4366
|
+
},
|
|
4367
|
+
{
|
|
4368
|
+
id: 'matrix',
|
|
4369
|
+
name: 'Matrix',
|
|
4370
|
+
description: language === 'es' ? 'Estética cyberpunk terminal' : 'Cyberpunk terminal aesthetic',
|
|
4371
|
+
preview: MatrixPreview
|
|
4372
|
+
}
|
|
4373
|
+
];
|
|
4374
|
+
|
|
4375
|
+
return (
|
|
4376
|
+
<div className="theme-card p-6">
|
|
4377
|
+
<div className="flex items-center gap-3 mb-6">
|
|
4378
|
+
<div className="w-10 h-10 rounded-theme-md flex items-center justify-center" style={{ background: 'var(--accent-primary)', opacity: 0.15 }}>
|
|
4379
|
+
<Palette className="w-5 h-5" style={{ color: 'var(--accent-primary)' }} />
|
|
4380
|
+
</div>
|
|
4381
|
+
<div>
|
|
4382
|
+
<h3 className="font-heading text-lg" style={{ color: 'var(--text-primary)' }}>
|
|
4383
|
+
{language === 'es' ? 'Apariencia' : 'Appearance'}
|
|
4384
|
+
</h3>
|
|
4385
|
+
<p className="text-sm" style={{ color: 'var(--text-muted)' }}>
|
|
4386
|
+
{language === 'es' ? 'Personaliza el aspecto del dashboard' : 'Customize the dashboard appearance'}
|
|
4387
|
+
</p>
|
|
4388
|
+
</div>
|
|
4389
|
+
</div>
|
|
4390
|
+
|
|
4391
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
4392
|
+
{themeOptions.map((themeOpt) => {
|
|
4393
|
+
const isSelected = currentTheme === themeOpt.id;
|
|
4394
|
+
const Preview = themeOpt.preview;
|
|
4395
|
+
|
|
4396
|
+
return (
|
|
4397
|
+
<button
|
|
4398
|
+
key={themeOpt.id}
|
|
4399
|
+
onClick={() => handleThemeChange(themeOpt.id)}
|
|
4400
|
+
disabled={saving}
|
|
4401
|
+
className={`group relative p-4 text-left transition-all rounded-theme-lg ${
|
|
4402
|
+
isSelected
|
|
4403
|
+
? 'ring-2'
|
|
4404
|
+
: 'hover:ring-1'
|
|
4405
|
+
}`}
|
|
4406
|
+
style={{
|
|
4407
|
+
background: 'var(--bg-card)',
|
|
4408
|
+
border: '1px solid var(--border-color)',
|
|
4409
|
+
ringColor: isSelected ? 'var(--accent-primary)' : 'var(--border-color-strong)'
|
|
4410
|
+
}}
|
|
4411
|
+
>
|
|
4412
|
+
{/* Preview */}
|
|
4413
|
+
<div
|
|
4414
|
+
className="w-full aspect-[3/2] mb-4 overflow-hidden rounded-theme-md"
|
|
4415
|
+
style={{ border: '1px solid var(--border-color)' }}
|
|
4416
|
+
>
|
|
4417
|
+
<Preview />
|
|
4418
|
+
</div>
|
|
4419
|
+
|
|
4420
|
+
{/* Info */}
|
|
4421
|
+
<div className="flex items-center justify-between">
|
|
4422
|
+
<div>
|
|
4423
|
+
<h4 className="font-heading font-medium" style={{ color: 'var(--text-primary)' }}>
|
|
4424
|
+
{themeOpt.name}
|
|
4425
|
+
</h4>
|
|
4426
|
+
<p className="text-xs mt-0.5" style={{ color: 'var(--text-muted)' }}>
|
|
4427
|
+
{themeOpt.description}
|
|
4428
|
+
</p>
|
|
4429
|
+
</div>
|
|
4430
|
+
|
|
4431
|
+
{/* Radio indicator */}
|
|
4432
|
+
<div
|
|
4433
|
+
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all ${
|
|
4434
|
+
isSelected ? 'border-transparent' : ''
|
|
4435
|
+
}`}
|
|
4436
|
+
style={{
|
|
4437
|
+
borderColor: isSelected ? 'var(--accent-primary)' : 'var(--border-color-strong)',
|
|
4438
|
+
background: isSelected ? 'var(--accent-primary)' : 'transparent'
|
|
4439
|
+
}}
|
|
4440
|
+
>
|
|
4441
|
+
{isSelected && (
|
|
4442
|
+
<Check className="w-3 h-3" style={{ color: 'var(--text-inverse)' }} />
|
|
4443
|
+
)}
|
|
4444
|
+
</div>
|
|
4445
|
+
</div>
|
|
4446
|
+
|
|
4447
|
+
{/* Loading overlay */}
|
|
4448
|
+
{saving && isSelected && (
|
|
4449
|
+
<div className="absolute inset-0 flex items-center justify-center rounded-theme-lg" style={{ background: 'rgba(0,0,0,0.5)' }}>
|
|
4450
|
+
<Loader2 className="w-6 h-6 animate-spin" style={{ color: 'var(--accent-primary)' }} />
|
|
4451
|
+
</div>
|
|
4452
|
+
)}
|
|
4453
|
+
</button>
|
|
4454
|
+
);
|
|
4455
|
+
})}
|
|
4456
|
+
</div>
|
|
4457
|
+
|
|
4458
|
+
{/* Color Mode Toggle */}
|
|
4459
|
+
<div className="mt-6 pt-6" style={{ borderTop: '1px solid var(--border-color)' }}>
|
|
4460
|
+
<div className="flex items-center justify-between">
|
|
4461
|
+
<div className="flex items-center gap-3">
|
|
4462
|
+
{isDark ? (
|
|
4463
|
+
<Moon className="w-5 h-5" style={{ color: 'var(--accent-primary)' }} />
|
|
4464
|
+
) : (
|
|
4465
|
+
<Sun className="w-5 h-5" style={{ color: 'var(--accent-secondary)' }} />
|
|
4466
|
+
)}
|
|
4467
|
+
<div>
|
|
4468
|
+
<h4 className="font-heading font-medium" style={{ color: 'var(--text-primary)' }}>
|
|
4469
|
+
{language === 'es' ? 'Modo de color' : 'Color Mode'}
|
|
4470
|
+
</h4>
|
|
4471
|
+
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>
|
|
4472
|
+
{language === 'es'
|
|
4473
|
+
? isDark ? 'Modo oscuro activado' : 'Modo claro activado'
|
|
4474
|
+
: isDark ? 'Dark mode enabled' : 'Light mode enabled'}
|
|
4475
|
+
</p>
|
|
4476
|
+
</div>
|
|
4477
|
+
</div>
|
|
4478
|
+
|
|
4479
|
+
<div className="flex gap-2">
|
|
4480
|
+
<button
|
|
4481
|
+
onClick={() => handleColorModeChange('light')}
|
|
4482
|
+
disabled={savingMode}
|
|
4483
|
+
className={`px-4 py-2 rounded-theme-md transition-all flex items-center gap-2 ${
|
|
4484
|
+
!isDark ? 'ring-2' : ''
|
|
4485
|
+
}`}
|
|
4486
|
+
style={{
|
|
4487
|
+
background: !isDark ? 'var(--accent-primary)' : 'var(--bg-secondary)',
|
|
4488
|
+
color: !isDark ? 'var(--text-inverse)' : 'var(--text-secondary)',
|
|
4489
|
+
ringColor: 'var(--accent-primary)'
|
|
4490
|
+
}}
|
|
4491
|
+
>
|
|
4492
|
+
<Sun className="w-4 h-4" />
|
|
4493
|
+
<span className="text-sm font-medium">{language === 'es' ? 'Claro' : 'Light'}</span>
|
|
4494
|
+
</button>
|
|
4495
|
+
<button
|
|
4496
|
+
onClick={() => handleColorModeChange('dark')}
|
|
4497
|
+
disabled={savingMode}
|
|
4498
|
+
className={`px-4 py-2 rounded-theme-md transition-all flex items-center gap-2 ${
|
|
4499
|
+
isDark ? 'ring-2' : ''
|
|
4500
|
+
}`}
|
|
4501
|
+
style={{
|
|
4502
|
+
background: isDark ? 'var(--accent-primary)' : 'var(--bg-secondary)',
|
|
4503
|
+
color: isDark ? 'var(--text-inverse)' : 'var(--text-secondary)',
|
|
4504
|
+
ringColor: 'var(--accent-primary)'
|
|
4505
|
+
}}
|
|
4506
|
+
>
|
|
4507
|
+
<Moon className="w-4 h-4" />
|
|
4508
|
+
<span className="text-sm font-medium">{language === 'es' ? 'Oscuro' : 'Dark'}</span>
|
|
4509
|
+
</button>
|
|
4510
|
+
</div>
|
|
4511
|
+
</div>
|
|
4512
|
+
</div>
|
|
4513
|
+
|
|
4514
|
+
<p className="text-xs mt-4" style={{ color: 'var(--text-muted)' }}>
|
|
4515
|
+
{language === 'es'
|
|
4516
|
+
? 'El tema y modo se guardan automáticamente en tu perfil'
|
|
4517
|
+
: 'Theme and mode are automatically saved to your profile'}
|
|
4518
|
+
</p>
|
|
4519
|
+
</div>
|
|
4520
|
+
);
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4074
4523
|
// ============ SETTINGS TAB ============
|
|
4075
4524
|
function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfirm, showAlert, t, language, changeLanguage }) {
|
|
4076
4525
|
const [copiedItem, setCopiedItem] = useState(null);
|
|
@@ -4408,6 +4857,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4408
4857
|
// Settings sub-tabs configuration
|
|
4409
4858
|
const settingsTabs = [
|
|
4410
4859
|
{ id: 'profile', label: t('settings.profile'), icon: User, show: !!authState?.user },
|
|
4860
|
+
{ id: 'appearance', label: language === 'es' ? 'APARIENCIA' : 'APPEARANCE', icon: Palette, show: true },
|
|
4411
4861
|
{ id: 'users', label: t('settings.users'), icon: Lock, show: isAdmin },
|
|
4412
4862
|
{ id: 'history', label: t('settings.history'), icon: Clock, show: true },
|
|
4413
4863
|
{ id: 'files', label: t('settings.files'), icon: FileCode, show: true },
|
|
@@ -4446,7 +4896,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4446
4896
|
|
|
4447
4897
|
{/* PROFILE TAB */}
|
|
4448
4898
|
{settingsSubTab === 'profile' && authState?.user && (
|
|
4449
|
-
<div className="
|
|
4899
|
+
<div className="theme-card p-5 space-y-5">
|
|
4450
4900
|
<div className="flex items-center gap-3 mb-2">
|
|
4451
4901
|
<div className="w-12 h-12 border border-cyber/30 bg-cyber/10 flex items-center justify-center font-display text-cyber text-xl">
|
|
4452
4902
|
{authState.user.name?.charAt(0).toUpperCase() || 'U'}
|
|
@@ -4483,7 +4933,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4483
4933
|
type="text"
|
|
4484
4934
|
value={profileData.name}
|
|
4485
4935
|
onChange={(e) => setProfileData(prev => ({ ...prev, name: e.target.value }))}
|
|
4486
|
-
className="w-full input
|
|
4936
|
+
className="w-full theme-input px-3 py-2 text-sm"
|
|
4487
4937
|
placeholder="Your name"
|
|
4488
4938
|
/>
|
|
4489
4939
|
</div>
|
|
@@ -4493,7 +4943,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4493
4943
|
type="email"
|
|
4494
4944
|
value={profileData.email}
|
|
4495
4945
|
onChange={(e) => setProfileData(prev => ({ ...prev, email: e.target.value }))}
|
|
4496
|
-
className="w-full input
|
|
4946
|
+
className="w-full theme-input px-3 py-2 text-sm"
|
|
4497
4947
|
placeholder="your@email.com"
|
|
4498
4948
|
/>
|
|
4499
4949
|
</div>
|
|
@@ -4508,7 +4958,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4508
4958
|
type="password"
|
|
4509
4959
|
value={profileData.currentPassword}
|
|
4510
4960
|
onChange={(e) => setProfileData(prev => ({ ...prev, currentPassword: e.target.value }))}
|
|
4511
|
-
className="w-full input
|
|
4961
|
+
className="w-full theme-input px-3 py-2 text-sm"
|
|
4512
4962
|
placeholder="Required"
|
|
4513
4963
|
/>
|
|
4514
4964
|
</div>
|
|
@@ -4518,7 +4968,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4518
4968
|
type="password"
|
|
4519
4969
|
value={profileData.newPassword}
|
|
4520
4970
|
onChange={(e) => setProfileData(prev => ({ ...prev, newPassword: e.target.value }))}
|
|
4521
|
-
className="w-full input
|
|
4971
|
+
className="w-full theme-input px-3 py-2 text-sm"
|
|
4522
4972
|
placeholder="Min 6 chars"
|
|
4523
4973
|
/>
|
|
4524
4974
|
</div>
|
|
@@ -4528,7 +4978,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4528
4978
|
type="password"
|
|
4529
4979
|
value={profileData.confirmPassword}
|
|
4530
4980
|
onChange={(e) => setProfileData(prev => ({ ...prev, confirmPassword: e.target.value }))}
|
|
4531
|
-
className="w-full input
|
|
4981
|
+
className="w-full theme-input px-3 py-2 text-sm"
|
|
4532
4982
|
placeholder="Repeat"
|
|
4533
4983
|
/>
|
|
4534
4984
|
</div>
|
|
@@ -4538,7 +4988,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4538
4988
|
<button
|
|
4539
4989
|
onClick={updateProfile}
|
|
4540
4990
|
disabled={savingProfile}
|
|
4541
|
-
className="btn-
|
|
4991
|
+
className="theme-btn-primary py-2 px-6 disabled:opacity-50 flex items-center gap-2"
|
|
4542
4992
|
>
|
|
4543
4993
|
{savingProfile ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
|
|
4544
4994
|
SAVE CHANGES
|
|
@@ -4546,9 +4996,14 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4546
4996
|
</div>
|
|
4547
4997
|
)}
|
|
4548
4998
|
|
|
4999
|
+
{/* APPEARANCE TAB */}
|
|
5000
|
+
{settingsSubTab === 'appearance' && (
|
|
5001
|
+
<AppearanceSection language={language} />
|
|
5002
|
+
)}
|
|
5003
|
+
|
|
4549
5004
|
{/* HISTORY TAB */}
|
|
4550
5005
|
{settingsSubTab === 'history' && (
|
|
4551
|
-
<div className="
|
|
5006
|
+
<div className="theme-card p-5">
|
|
4552
5007
|
<div className="flex items-center justify-between mb-4">
|
|
4553
5008
|
<div>
|
|
4554
5009
|
<h3 className="font-mono text-sm text-white">VERSION HISTORY</h3>
|
|
@@ -4559,7 +5014,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4559
5014
|
<button
|
|
4560
5015
|
onClick={loadVersions}
|
|
4561
5016
|
disabled={loadingVersions}
|
|
4562
|
-
className="btn-
|
|
5017
|
+
className="theme-btn-primary py-2 px-4 text-xs"
|
|
4563
5018
|
>
|
|
4564
5019
|
{loadingVersions ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
|
|
4565
5020
|
</button>
|
|
@@ -4571,7 +5026,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4571
5026
|
<Clock className="w-10 h-10 mx-auto mb-3 text-gray-800" />
|
|
4572
5027
|
<p className="font-mono text-xs text-gray-600">NO VERSIONS YET</p>
|
|
4573
5028
|
<p className="font-mono text-[10px] text-gray-700 mt-1">Versions are saved automatically when you save changes</p>
|
|
4574
|
-
<button onClick={loadVersions} className="mt-4 btn-
|
|
5029
|
+
<button onClick={loadVersions} className="mt-4 theme-btn-primary py-2 px-4 text-xs">
|
|
4575
5030
|
LOAD HISTORY
|
|
4576
5031
|
</button>
|
|
4577
5032
|
</div>
|
|
@@ -4636,7 +5091,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4636
5091
|
<button
|
|
4637
5092
|
onClick={() => restoreVersion(selectedVersion.id)}
|
|
4638
5093
|
disabled={restoringVersion}
|
|
4639
|
-
className="btn-
|
|
5094
|
+
className="theme-btn-primary py-2 px-4 text-xs flex items-center gap-2"
|
|
4640
5095
|
>
|
|
4641
5096
|
{restoringVersion ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
|
|
4642
5097
|
RESTORE THIS VERSION
|
|
@@ -4778,7 +5233,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4778
5233
|
{settingsSubTab === 'users' && isAdmin && (
|
|
4779
5234
|
<div className="space-y-5">
|
|
4780
5235
|
{/* Auth Settings */}
|
|
4781
|
-
<div className="
|
|
5236
|
+
<div className="theme-card p-5">
|
|
4782
5237
|
<div className="flex items-center justify-between">
|
|
4783
5238
|
<div>
|
|
4784
5239
|
<h3 className="font-mono text-sm text-white">Authentication Required</h3>
|
|
@@ -4794,7 +5249,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4794
5249
|
</div>
|
|
4795
5250
|
|
|
4796
5251
|
{/* Add User */}
|
|
4797
|
-
<div className="
|
|
5252
|
+
<div className="theme-card p-5">
|
|
4798
5253
|
<h3 className="font-mono text-sm text-white mb-4">ADD NEW USER</h3>
|
|
4799
5254
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 mb-3">
|
|
4800
5255
|
<input
|
|
@@ -4802,26 +5257,26 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4802
5257
|
placeholder="Name *"
|
|
4803
5258
|
value={newUser.name}
|
|
4804
5259
|
onChange={(e) => setNewUser(prev => ({ ...prev, name: e.target.value }))}
|
|
4805
|
-
className="input
|
|
5260
|
+
className="theme-input px-3 py-2 text-sm"
|
|
4806
5261
|
/>
|
|
4807
5262
|
<input
|
|
4808
5263
|
type="email"
|
|
4809
5264
|
placeholder="Email *"
|
|
4810
5265
|
value={newUser.email}
|
|
4811
5266
|
onChange={(e) => setNewUser(prev => ({ ...prev, email: e.target.value }))}
|
|
4812
|
-
className="input
|
|
5267
|
+
className="theme-input px-3 py-2 text-sm"
|
|
4813
5268
|
/>
|
|
4814
5269
|
<input
|
|
4815
5270
|
type="password"
|
|
4816
5271
|
placeholder="Password *"
|
|
4817
5272
|
value={newUser.password}
|
|
4818
5273
|
onChange={(e) => setNewUser(prev => ({ ...prev, password: e.target.value }))}
|
|
4819
|
-
className="input
|
|
5274
|
+
className="theme-input px-3 py-2 text-sm"
|
|
4820
5275
|
/>
|
|
4821
5276
|
<select
|
|
4822
5277
|
value={newUser.role}
|
|
4823
5278
|
onChange={(e) => setNewUser(prev => ({ ...prev, role: e.target.value }))}
|
|
4824
|
-
className="input
|
|
5279
|
+
className="theme-input px-3 py-2 text-sm"
|
|
4825
5280
|
>
|
|
4826
5281
|
<option value="member">Member</option>
|
|
4827
5282
|
<option value="admin">Admin</option>
|
|
@@ -4835,14 +5290,14 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4835
5290
|
<button
|
|
4836
5291
|
onClick={createUser}
|
|
4837
5292
|
disabled={!newUser.name.trim() || !newUser.email.trim() || !newUser.password.trim()}
|
|
4838
|
-
className="btn-
|
|
5293
|
+
className="theme-btn-primary py-2 px-4 disabled:opacity-50"
|
|
4839
5294
|
>
|
|
4840
5295
|
<Plus className="w-4 h-4 inline mr-1" /> CREATE USER
|
|
4841
5296
|
</button>
|
|
4842
5297
|
</div>
|
|
4843
5298
|
|
|
4844
5299
|
{/* Users List */}
|
|
4845
|
-
<div className="
|
|
5300
|
+
<div className="theme-card p-5">
|
|
4846
5301
|
<h3 className="font-mono text-sm text-white mb-4">REGISTERED USERS ({users.length})</h3>
|
|
4847
5302
|
{loadingUsers ? (
|
|
4848
5303
|
<div className="py-8 text-center">
|
|
@@ -4895,9 +5350,9 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4895
5350
|
{settingsSubTab === 'files' && (
|
|
4896
5351
|
<div className="space-y-5">
|
|
4897
5352
|
{/* Deploy */}
|
|
4898
|
-
<div className="
|
|
5353
|
+
<div className="theme-card p-5">
|
|
4899
5354
|
<div className="flex items-center gap-3 mb-4">
|
|
4900
|
-
<div className="led led-
|
|
5355
|
+
<div className="led theme-led theme-led-success" />
|
|
4901
5356
|
<div>
|
|
4902
5357
|
<h3 className="font-mono text-sm text-matrix tracking-wider">DEPLOY CONFIG</h3>
|
|
4903
5358
|
<p className="font-mono text-[10px] text-gray-600">Copy files to project root</p>
|
|
@@ -4920,7 +5375,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4920
5375
|
Also create .cursorrules (for Cursor IDE)
|
|
4921
5376
|
</label>
|
|
4922
5377
|
|
|
4923
|
-
<button onClick={deployToProject} disabled={deploying} className="btn-
|
|
5378
|
+
<button onClick={deployToProject} disabled={deploying} className="theme-btn-primary flex items-center gap-2">
|
|
4924
5379
|
{deploying ? <Loader2 className="w-4 h-4 animate-spin" /> : <Rocket className="w-4 h-4" />}
|
|
4925
5380
|
DEPLOY FILES
|
|
4926
5381
|
</button>
|
|
@@ -4942,7 +5397,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4942
5397
|
</div>
|
|
4943
5398
|
|
|
4944
5399
|
{/* .clinerules */}
|
|
4945
|
-
<div className="
|
|
5400
|
+
<div className="theme-card">
|
|
4946
5401
|
<button
|
|
4947
5402
|
onClick={() => setClineruleExpanded(!clineruleExpanded)}
|
|
4948
5403
|
className="w-full p-4 flex items-center justify-between hover:bg-white/[0.01] transition-colors"
|
|
@@ -4978,7 +5433,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4978
5433
|
<textarea
|
|
4979
5434
|
value={clinerules}
|
|
4980
5435
|
onChange={(e) => setClinerules(e.target.value)}
|
|
4981
|
-
className="w-full h-[400px] input
|
|
5436
|
+
className="w-full h-[400px] theme-input p-4 text-xs resize-none"
|
|
4982
5437
|
spellCheck={false}
|
|
4983
5438
|
/>
|
|
4984
5439
|
) : (
|
|
@@ -4991,7 +5446,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
4991
5446
|
</div>
|
|
4992
5447
|
|
|
4993
5448
|
{/* roadmap.json */}
|
|
4994
|
-
<div className="
|
|
5449
|
+
<div className="theme-card">
|
|
4995
5450
|
<button
|
|
4996
5451
|
onClick={() => setJsonExpanded(!jsonExpanded)}
|
|
4997
5452
|
className="w-full p-4 flex items-center justify-between hover:bg-white/[0.01] transition-colors"
|
|
@@ -5051,7 +5506,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
5051
5506
|
<textarea
|
|
5052
5507
|
value={jsonContent}
|
|
5053
5508
|
onChange={(e) => { setJsonContent(e.target.value); setJsonError(null); }}
|
|
5054
|
-
className="w-full h-[400px] input
|
|
5509
|
+
className="w-full h-[400px] theme-input p-4 text-xs resize-none"
|
|
5055
5510
|
spellCheck={false}
|
|
5056
5511
|
/>
|
|
5057
5512
|
<p className="mt-2 font-mono text-[10px] text-gray-600">
|
|
@@ -5071,7 +5526,7 @@ function SettingsTab({ roadmap, setRoadmap, setHasChanges, authState, showConfir
|
|
|
5071
5526
|
|
|
5072
5527
|
{/* LANGUAGE TAB */}
|
|
5073
5528
|
{settingsSubTab === 'language' && (
|
|
5074
|
-
<div className="
|
|
5529
|
+
<div className="theme-card p-5">
|
|
5075
5530
|
<h3 className="font-mono text-sm text-white mb-2">{t('settings.language')}</h3>
|
|
5076
5531
|
<p className="font-mono text-[10px] text-gray-600 mb-6">
|
|
5077
5532
|
{language === 'es' ? 'Selecciona el idioma de la interfaz' : 'Select interface language'}
|
|
@@ -5188,7 +5643,7 @@ function HelpTab({ t, language }) {
|
|
|
5188
5643
|
{/* Templates */}
|
|
5189
5644
|
{activeSection === 'templates' && (
|
|
5190
5645
|
<div className="space-y-6">
|
|
5191
|
-
<div className="
|
|
5646
|
+
<div className="theme-card p-5">
|
|
5192
5647
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4">{t('help.referenceFiles')}</h3>
|
|
5193
5648
|
<p className="font-mono text-[11px] text-gray-500 mb-4">{t('help.sharePaths')}</p>
|
|
5194
5649
|
<div className="space-y-2">
|
|
@@ -5212,7 +5667,7 @@ function HelpTab({ t, language }) {
|
|
|
5212
5667
|
</div>
|
|
5213
5668
|
</div>
|
|
5214
5669
|
|
|
5215
|
-
<div className="
|
|
5670
|
+
<div className="theme-card p-5">
|
|
5216
5671
|
<h3 className="font-mono text-sm text-cyber tracking-wider mb-4">{t('help.aiInstructions')}</h3>
|
|
5217
5672
|
<div className="bg-void-100 border border-white/5 p-4 font-mono text-[11px] text-gray-400 overflow-x-auto">
|
|
5218
5673
|
<pre className="whitespace-pre-wrap">{`Read the example files in:
|
|
@@ -5247,7 +5702,7 @@ Then generate a roadmap.json for my project following this structure:
|
|
|
5247
5702
|
</div>
|
|
5248
5703
|
</div>
|
|
5249
5704
|
|
|
5250
|
-
<div className="
|
|
5705
|
+
<div className="theme-card p-5 border-matrix/20">
|
|
5251
5706
|
<div className="flex items-center justify-between mb-3">
|
|
5252
5707
|
<h4 className="font-mono text-sm text-matrix">{t('help.quickPrompt')}</h4>
|
|
5253
5708
|
<button
|
|
@@ -5276,7 +5731,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5276
5731
|
{/* Commits */}
|
|
5277
5732
|
{activeSection === 'commits' && (
|
|
5278
5733
|
<div className="space-y-6">
|
|
5279
|
-
<div className="
|
|
5734
|
+
<div className="theme-card p-5">
|
|
5280
5735
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4">{t('help.commitFormat')}</h3>
|
|
5281
5736
|
<p className="font-mono text-[11px] text-gray-500 mb-4">{t('help.commitFormatDesc')}</p>
|
|
5282
5737
|
<div className="bg-void-100 border border-white/5 p-4 mb-6">
|
|
@@ -5314,7 +5769,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5314
5769
|
</div>
|
|
5315
5770
|
</div>
|
|
5316
5771
|
|
|
5317
|
-
<div className="
|
|
5772
|
+
<div className="theme-card p-5">
|
|
5318
5773
|
<h4 className="font-mono text-xs text-gray-400 mb-4">{t('help.examples')}</h4>
|
|
5319
5774
|
<div className="space-y-3">
|
|
5320
5775
|
{[
|
|
@@ -5330,7 +5785,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5330
5785
|
</div>
|
|
5331
5786
|
</div>
|
|
5332
5787
|
|
|
5333
|
-
<div className="
|
|
5788
|
+
<div className="theme-card p-5 border-cyber/20">
|
|
5334
5789
|
<h4 className="font-mono text-sm text-cyber tracking-wider mb-3">{t('help.syncWithGit')}</h4>
|
|
5335
5790
|
<p className="font-mono text-[11px] text-gray-500 mb-3">{t('help.afterCommits')}</p>
|
|
5336
5791
|
<div className="bg-void-100 border border-white/5 p-3">
|
|
@@ -5346,7 +5801,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5346
5801
|
{/* AI Workflow */}
|
|
5347
5802
|
{activeSection === 'ai' && (
|
|
5348
5803
|
<div className="space-y-6">
|
|
5349
|
-
<div className="
|
|
5804
|
+
<div className="theme-card p-5">
|
|
5350
5805
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4">{t('help.recommendedWorkflow')}</h3>
|
|
5351
5806
|
<div className="space-y-2">
|
|
5352
5807
|
{[
|
|
@@ -5363,7 +5818,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5363
5818
|
</div>
|
|
5364
5819
|
|
|
5365
5820
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
5366
|
-
<div className="
|
|
5821
|
+
<div className="theme-card p-5 border-matrix/20">
|
|
5367
5822
|
<h4 className="font-mono text-xs text-matrix tracking-wider mb-4 flex items-center gap-2">
|
|
5368
5823
|
<CheckCircle2 className="w-4 h-4" /> {t('help.aiMustDo')}
|
|
5369
5824
|
</h4>
|
|
@@ -5376,7 +5831,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5376
5831
|
</ul>
|
|
5377
5832
|
</div>
|
|
5378
5833
|
|
|
5379
|
-
<div className="
|
|
5834
|
+
<div className="theme-card p-5 border-alert/20">
|
|
5380
5835
|
<h4 className="font-mono text-xs text-alert tracking-wider mb-4 flex items-center gap-2">
|
|
5381
5836
|
<X className="w-4 h-4" /> {t('help.aiMustNot')}
|
|
5382
5837
|
</h4>
|
|
@@ -5395,7 +5850,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5395
5850
|
{/* Troubleshooting */}
|
|
5396
5851
|
{activeSection === 'troubleshooting' && (
|
|
5397
5852
|
<div className="space-y-4">
|
|
5398
|
-
<div className="
|
|
5853
|
+
<div className="theme-card p-5">
|
|
5399
5854
|
<h3 className="font-mono text-sm text-signal tracking-wider mb-4">{t('help.troubleshooting')}</h3>
|
|
5400
5855
|
<div className="space-y-4">
|
|
5401
5856
|
{[
|
|
@@ -5415,7 +5870,7 @@ Then help me create/improve the roadmap.json for my project following that forma
|
|
|
5415
5870
|
</div>
|
|
5416
5871
|
</div>
|
|
5417
5872
|
|
|
5418
|
-
<div className="
|
|
5873
|
+
<div className="theme-card p-5">
|
|
5419
5874
|
<h4 className="font-mono text-xs text-gray-400 mb-4">{t('help.fileSummary')}</h4>
|
|
5420
5875
|
<div className="overflow-x-auto">
|
|
5421
5876
|
<table className="w-full font-mono text-[11px]">
|
|
@@ -5716,7 +6171,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5716
6171
|
return (
|
|
5717
6172
|
<div className="space-y-6 animate-fade-in">
|
|
5718
6173
|
{/* Setup Complete Header */}
|
|
5719
|
-
<div className="
|
|
6174
|
+
<div className="theme-card p-8 border-matrix/50 text-center">
|
|
5720
6175
|
<div className="w-20 h-20 mx-auto border-2 border-matrix/50 flex items-center justify-center mb-6 glow-matrix">
|
|
5721
6176
|
<CheckCircle2 className="w-10 h-10 text-matrix" />
|
|
5722
6177
|
</div>
|
|
@@ -5729,7 +6184,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5729
6184
|
</div>
|
|
5730
6185
|
|
|
5731
6186
|
{/* Roadmap Status Card */}
|
|
5732
|
-
<div className="
|
|
6187
|
+
<div className="theme-card p-5">
|
|
5733
6188
|
<h3 className="font-mono text-sm text-white tracking-wider mb-4">
|
|
5734
6189
|
{t ? t('setup.roadmapStatus') : 'ROADMAP STATUS'}
|
|
5735
6190
|
</h3>
|
|
@@ -5772,7 +6227,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5772
6227
|
<div className="flex gap-3">
|
|
5773
6228
|
<button
|
|
5774
6229
|
onClick={() => window.location.hash = '#features'}
|
|
5775
|
-
className="btn-
|
|
6230
|
+
className="theme-btn-primary flex-1 py-3 flex items-center justify-center gap-2"
|
|
5776
6231
|
>
|
|
5777
6232
|
<Zap className="w-4 h-4" />
|
|
5778
6233
|
{t ? t('setup.goToDashboard') : 'GO TO DASHBOARD'}
|
|
@@ -5788,7 +6243,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5788
6243
|
|
|
5789
6244
|
{/* Project Info Summary */}
|
|
5790
6245
|
{roadmap?.project_info && (
|
|
5791
|
-
<div className="
|
|
6246
|
+
<div className="theme-card p-5">
|
|
5792
6247
|
<h3 className="font-mono text-sm text-cyber tracking-wider mb-4"># {roadmap.project_info.name || 'PROJECT'}</h3>
|
|
5793
6248
|
{roadmap.project_info.description && (
|
|
5794
6249
|
<p className="font-mono text-xs text-gray-400 mb-4">{roadmap.project_info.description}</p>
|
|
@@ -5811,7 +6266,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5811
6266
|
return (
|
|
5812
6267
|
<div className="space-y-6 animate-fade-in">
|
|
5813
6268
|
{/* Header */}
|
|
5814
|
-
<div className="
|
|
6269
|
+
<div className="theme-card p-5 border-matrix/30">
|
|
5815
6270
|
<div className="flex items-center gap-4">
|
|
5816
6271
|
<div className="w-14 h-14 border border-matrix/50 flex items-center justify-center glow-matrix">
|
|
5817
6272
|
<Sparkles className="w-7 h-7 text-matrix" />
|
|
@@ -5864,7 +6319,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5864
6319
|
{/* Step 1: Choose Connection Mode */}
|
|
5865
6320
|
{activeStep === 1 && (
|
|
5866
6321
|
<div className="space-y-4">
|
|
5867
|
-
<div className="
|
|
6322
|
+
<div className="theme-card p-6">
|
|
5868
6323
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4"># {t ? t('setup.chooseConnection') : 'CHOOSE CONNECTION MODE'}</h3>
|
|
5869
6324
|
<p className="font-mono text-xs text-gray-500 mb-6">
|
|
5870
6325
|
{t ? t('setup.chooseConnectionDesc') : 'Choose how to connect with Claude to generate your project configuration.'}
|
|
@@ -5881,7 +6336,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5881
6336
|
}`}
|
|
5882
6337
|
>
|
|
5883
6338
|
<div className="flex items-center gap-3 mb-3">
|
|
5884
|
-
<div className={`led ${claudeCodeStatus.available ? 'led-
|
|
6339
|
+
<div className={`led ${claudeCodeStatus.available ? 'theme-led theme-led-success' : claudeCodeStatus.checking ? 'theme-led theme-led-warning' : 'theme-led theme-led-danger'}`} />
|
|
5885
6340
|
<span className="font-mono text-sm text-white">CLAUDE CODE</span>
|
|
5886
6341
|
<span className="font-mono text-[9px] px-2 py-0.5 bg-matrix/20 text-matrix border border-matrix/30">
|
|
5887
6342
|
{t ? t('setup.recommended') : 'RECOMMENDED'}
|
|
@@ -5918,7 +6373,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5918
6373
|
}`}
|
|
5919
6374
|
>
|
|
5920
6375
|
<div className="flex items-center gap-3 mb-3">
|
|
5921
|
-
<div className={`led ${apiKeyStatus.hasKey ? 'led-
|
|
6376
|
+
<div className={`led ${apiKeyStatus.hasKey ? 'theme-led theme-led-success' : 'led-gray'}`} />
|
|
5922
6377
|
<span className="font-mono text-sm text-white">API KEY</span>
|
|
5923
6378
|
</div>
|
|
5924
6379
|
<p className="font-mono text-[11px] text-gray-500 mb-3">
|
|
@@ -5941,7 +6396,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5941
6396
|
|
|
5942
6397
|
{/* Claude Code selected but not available */}
|
|
5943
6398
|
{generationMode === 'claude-code' && !claudeCodeStatus.checking && !claudeCodeStatus.available && (
|
|
5944
|
-
<div className="
|
|
6399
|
+
<div className="theme-card p-5 border-signal/30">
|
|
5945
6400
|
<h4 className="font-mono text-xs text-signal tracking-wider mb-3">INSTALAR CLAUDE CODE</h4>
|
|
5946
6401
|
<p className="font-mono text-[11px] text-gray-500 mb-4">
|
|
5947
6402
|
Para usar tu suscripción, necesitas tener Claude Code instalado y autenticado.
|
|
@@ -5965,7 +6420,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5965
6420
|
|
|
5966
6421
|
{/* API mode selected - show key input */}
|
|
5967
6422
|
{generationMode === 'api' && (
|
|
5968
|
-
<div className="
|
|
6423
|
+
<div className="theme-card p-5">
|
|
5969
6424
|
<h4 className="font-mono text-xs text-cyber tracking-wider mb-3">CONFIGURAR API KEY</h4>
|
|
5970
6425
|
<p className="font-mono text-[11px] text-gray-500 mb-4">
|
|
5971
6426
|
Obtén una API key en{' '}
|
|
@@ -5981,7 +6436,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5981
6436
|
<span className="text-matrix">{apiKeyStatus.maskedKey}</span>
|
|
5982
6437
|
</div>
|
|
5983
6438
|
<div className="flex gap-3">
|
|
5984
|
-
<button onClick={() => setActiveStep(2)} className="btn-
|
|
6439
|
+
<button onClick={() => setActiveStep(2)} className="theme-btn-primary flex-1">
|
|
5985
6440
|
CONTINUE →
|
|
5986
6441
|
</button>
|
|
5987
6442
|
<button
|
|
@@ -5999,12 +6454,12 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
5999
6454
|
placeholder="sk-ant-api03-..."
|
|
6000
6455
|
value={apiKey}
|
|
6001
6456
|
onChange={(e) => setApiKey(e.target.value)}
|
|
6002
|
-
className="input
|
|
6457
|
+
className="theme-input w-full px-4 py-3 text-sm"
|
|
6003
6458
|
/>
|
|
6004
6459
|
<button
|
|
6005
6460
|
onClick={saveApiKey}
|
|
6006
6461
|
disabled={!apiKey.trim()}
|
|
6007
|
-
className="btn-
|
|
6462
|
+
className="theme-btn-primary w-full py-3 disabled:opacity-50"
|
|
6008
6463
|
>
|
|
6009
6464
|
SAVE API KEY
|
|
6010
6465
|
</button>
|
|
@@ -6015,7 +6470,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6015
6470
|
|
|
6016
6471
|
{/* Continue button for Claude Code mode */}
|
|
6017
6472
|
{generationMode === 'claude-code' && claudeCodeStatus.available && (
|
|
6018
|
-
<button onClick={() => setActiveStep(2)} className="btn-
|
|
6473
|
+
<button onClick={() => setActiveStep(2)} className="theme-btn-primary w-full py-4">
|
|
6019
6474
|
CONTINUE WITH CLAUDE CODE →
|
|
6020
6475
|
</button>
|
|
6021
6476
|
)}
|
|
@@ -6027,7 +6482,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6027
6482
|
<div className="space-y-4">
|
|
6028
6483
|
{/* Roadmap Validation Status */}
|
|
6029
6484
|
{hasExistingRoadmap && (
|
|
6030
|
-
<div className={`
|
|
6485
|
+
<div className={`theme-card p-5 ${validateRoadmap.valid ? 'border-matrix/50' : validateRoadmap.issues.length > 0 ? 'border-alert/30' : 'border-signal/30'}`}>
|
|
6031
6486
|
<div className="flex items-start gap-4">
|
|
6032
6487
|
<div className={`w-12 h-12 border flex items-center justify-center ${
|
|
6033
6488
|
validateRoadmap.valid ? 'border-matrix/50 bg-matrix/10' :
|
|
@@ -6105,7 +6560,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6105
6560
|
<div className="flex gap-3">
|
|
6106
6561
|
<button
|
|
6107
6562
|
onClick={() => setActiveStep(4)}
|
|
6108
|
-
className="btn-
|
|
6563
|
+
className="theme-btn-primary px-6 py-2 flex items-center gap-2"
|
|
6109
6564
|
>
|
|
6110
6565
|
<CheckCircle2 className="w-4 h-4" />
|
|
6111
6566
|
CONTINUE TO DASHBOARD
|
|
@@ -6149,10 +6604,13 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6149
6604
|
)}
|
|
6150
6605
|
|
|
6151
6606
|
{/* Project Scan Results */}
|
|
6152
|
-
<div className="
|
|
6153
|
-
<
|
|
6607
|
+
<div className="theme-card">
|
|
6608
|
+
<div
|
|
6154
6609
|
onClick={() => setScanExpanded(!scanExpanded)}
|
|
6155
|
-
className="w-full p-4 flex items-center justify-between hover:bg-white/[0.01] transition-colors"
|
|
6610
|
+
className="w-full p-4 flex items-center justify-between hover:bg-white/[0.01] transition-colors cursor-pointer"
|
|
6611
|
+
role="button"
|
|
6612
|
+
tabIndex={0}
|
|
6613
|
+
onKeyDown={(e) => e.key === 'Enter' && setScanExpanded(!scanExpanded)}
|
|
6156
6614
|
>
|
|
6157
6615
|
<div className="flex items-center gap-3">
|
|
6158
6616
|
<FolderOpen className="w-4 h-4 text-cyber" />
|
|
@@ -6173,7 +6631,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6173
6631
|
</button>
|
|
6174
6632
|
<ChevronDown className={`w-4 h-4 text-gray-600 transition-transform ${scanExpanded ? 'rotate-180' : ''}`} />
|
|
6175
6633
|
</div>
|
|
6176
|
-
</
|
|
6634
|
+
</div>
|
|
6177
6635
|
|
|
6178
6636
|
{scanExpanded && projectScan && (
|
|
6179
6637
|
<div className="px-4 pb-4 border-t border-white/5">
|
|
@@ -6229,7 +6687,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6229
6687
|
</div>
|
|
6230
6688
|
|
|
6231
6689
|
{/* Requirements Input */}
|
|
6232
|
-
<div className="
|
|
6690
|
+
<div className="theme-card p-6">
|
|
6233
6691
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-2"># REQUIREMENTS</h3>
|
|
6234
6692
|
<p className="font-mono text-[10px] text-gray-500 mb-4">
|
|
6235
6693
|
Describe las características, funcionalidades o mejoras que quieres implementar. Claude analizará tu proyecto y generará un roadmap estructurado.
|
|
@@ -6247,13 +6705,13 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6247
6705
|
value={requirements}
|
|
6248
6706
|
onChange={(e) => setRequirements(e.target.value)}
|
|
6249
6707
|
rows={10}
|
|
6250
|
-
className="input
|
|
6708
|
+
className="theme-input w-full px-4 py-3 text-sm resize-none mb-4"
|
|
6251
6709
|
/>
|
|
6252
6710
|
|
|
6253
6711
|
<button
|
|
6254
6712
|
onClick={analyzeWithAI}
|
|
6255
6713
|
disabled={generating || !requirements.trim()}
|
|
6256
|
-
className="btn-
|
|
6714
|
+
className="theme-btn-primary w-full py-3 flex items-center justify-center gap-2 disabled:opacity-50"
|
|
6257
6715
|
>
|
|
6258
6716
|
{generating ? (
|
|
6259
6717
|
<>
|
|
@@ -6280,7 +6738,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6280
6738
|
<div className="space-y-4">
|
|
6281
6739
|
{/* Analysis Summary */}
|
|
6282
6740
|
{generatedResult.analysis && (
|
|
6283
|
-
<div className="
|
|
6741
|
+
<div className="theme-card p-4 border-cyber/30">
|
|
6284
6742
|
<h4 className="font-mono text-xs text-cyber tracking-wider mb-3 flex items-center gap-2">
|
|
6285
6743
|
<Info className="w-4 h-4" /> ANALYSIS SUMMARY
|
|
6286
6744
|
</h4>
|
|
@@ -6312,7 +6770,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6312
6770
|
</div>
|
|
6313
6771
|
)}
|
|
6314
6772
|
|
|
6315
|
-
<div className="
|
|
6773
|
+
<div className="theme-card p-6">
|
|
6316
6774
|
<h3 className="font-mono text-sm text-matrix tracking-wider mb-4"># GENERATED ROADMAP</h3>
|
|
6317
6775
|
|
|
6318
6776
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
|
@@ -6373,14 +6831,14 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6373
6831
|
<button onClick={() => setActiveStep(2)} className="px-4 py-2 border border-white/10 text-gray-500 font-mono text-xs hover:text-white transition-all">
|
|
6374
6832
|
← BACK
|
|
6375
6833
|
</button>
|
|
6376
|
-
<button onClick={applyGenerated} className="btn-
|
|
6834
|
+
<button onClick={applyGenerated} className="theme-btn-primary flex-1 py-3">
|
|
6377
6835
|
APPLY & DEPLOY
|
|
6378
6836
|
</button>
|
|
6379
6837
|
</div>
|
|
6380
6838
|
</div>
|
|
6381
6839
|
|
|
6382
6840
|
{/* JSON Preview */}
|
|
6383
|
-
<div className="
|
|
6841
|
+
<div className="theme-card p-4">
|
|
6384
6842
|
<details>
|
|
6385
6843
|
<summary className="font-mono text-xs text-gray-500 cursor-pointer hover:text-white">
|
|
6386
6844
|
VIEW GENERATED JSON
|
|
@@ -6395,7 +6853,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6395
6853
|
|
|
6396
6854
|
{/* Step 4: Complete */}
|
|
6397
6855
|
{activeStep === 4 && (
|
|
6398
|
-
<div className="
|
|
6856
|
+
<div className="theme-card p-8 text-center">
|
|
6399
6857
|
<div className="w-20 h-20 mx-auto mb-6 border-2 border-matrix flex items-center justify-center glow-matrix">
|
|
6400
6858
|
<CheckCircle2 className="w-10 h-10 text-matrix" />
|
|
6401
6859
|
</div>
|
|
@@ -6407,7 +6865,7 @@ function SetupTab({ roadmap, setRoadmap, setHasChanges, loadRoadmap, t, language
|
|
|
6407
6865
|
<button onClick={() => { setActiveStep(1); setGeneratedResult(null); }} className="px-4 py-2 border border-white/10 text-gray-500 font-mono text-xs hover:text-white transition-all">
|
|
6408
6866
|
START OVER
|
|
6409
6867
|
</button>
|
|
6410
|
-
<button onClick={() => window.location.reload()} className="btn-
|
|
6868
|
+
<button onClick={() => window.location.reload()} className="theme-btn-primary px-6 py-2">
|
|
6411
6869
|
VIEW DASHBOARD
|
|
6412
6870
|
</button>
|
|
6413
6871
|
</div>
|
|
@@ -6445,7 +6903,7 @@ function AddTaskForm({ onAdd, onCancel, team = [], t, language }) {
|
|
|
6445
6903
|
placeholder={t ? t('features.taskName') : "Task name"}
|
|
6446
6904
|
value={name}
|
|
6447
6905
|
onChange={(e) => setName(e.target.value)}
|
|
6448
|
-
className="input
|
|
6906
|
+
className="theme-input w-full px-3 py-2 text-sm"
|
|
6449
6907
|
autoFocus
|
|
6450
6908
|
/>
|
|
6451
6909
|
<textarea
|
|
@@ -6453,10 +6911,10 @@ function AddTaskForm({ onAdd, onCancel, team = [], t, language }) {
|
|
|
6453
6911
|
value={description}
|
|
6454
6912
|
onChange={(e) => setDescription(e.target.value)}
|
|
6455
6913
|
rows={2}
|
|
6456
|
-
className="input
|
|
6914
|
+
className="theme-input w-full px-3 py-2 text-sm resize-none"
|
|
6457
6915
|
/>
|
|
6458
6916
|
<div className="flex items-center gap-3 flex-wrap">
|
|
6459
|
-
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="input
|
|
6917
|
+
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="theme-input px-3 py-2 text-sm">
|
|
6460
6918
|
<option value="high">{t ? t('common.high').toUpperCase() : 'HIGH'}</option>
|
|
6461
6919
|
<option value="medium">{t ? t('common.medium').toUpperCase() : 'MEDIUM'}</option>
|
|
6462
6920
|
<option value="low">{t ? t('common.low').toUpperCase() : 'LOW'}</option>
|
|
@@ -6465,7 +6923,7 @@ function AddTaskForm({ onAdd, onCancel, team = [], t, language }) {
|
|
|
6465
6923
|
<select
|
|
6466
6924
|
value={assignedTo}
|
|
6467
6925
|
onChange={(e) => setAssignedTo(e.target.value)}
|
|
6468
|
-
className="input
|
|
6926
|
+
className="theme-input px-3 py-2 text-sm"
|
|
6469
6927
|
>
|
|
6470
6928
|
<option value="">{t ? t('features.unassigned') : 'Unassigned'}</option>
|
|
6471
6929
|
{team.map(member => (
|
|
@@ -6477,7 +6935,7 @@ function AddTaskForm({ onAdd, onCancel, team = [], t, language }) {
|
|
|
6477
6935
|
<button type="button" onClick={onCancel} className="px-4 py-2 font-mono text-xs text-gray-500 hover:text-white transition-all">
|
|
6478
6936
|
{t ? t('common.cancel').toUpperCase() : 'CANCEL'}
|
|
6479
6937
|
</button>
|
|
6480
|
-
<button type="submit" disabled={!name.trim()} className="btn-
|
|
6938
|
+
<button type="submit" disabled={!name.trim()} className="theme-btn-primary px-4 py-2 disabled:opacity-50">
|
|
6481
6939
|
{t ? t('common.add').toUpperCase() : 'ADD'}
|
|
6482
6940
|
</button>
|
|
6483
6941
|
</div>
|
|
@@ -6518,7 +6976,7 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6518
6976
|
placeholder="e.g. user-auth"
|
|
6519
6977
|
value={id}
|
|
6520
6978
|
onChange={(e) => setId(e.target.value)}
|
|
6521
|
-
className="input
|
|
6979
|
+
className="theme-input w-full px-4 py-2.5 mt-2 text-sm"
|
|
6522
6980
|
/>
|
|
6523
6981
|
</div>
|
|
6524
6982
|
<div>
|
|
@@ -6528,7 +6986,7 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6528
6986
|
placeholder={t ? t('features.featureName') : "Feature name"}
|
|
6529
6987
|
value={name}
|
|
6530
6988
|
onChange={(e) => setName(e.target.value)}
|
|
6531
|
-
className="input
|
|
6989
|
+
className="theme-input w-full px-4 py-2.5 mt-2 text-sm"
|
|
6532
6990
|
autoFocus
|
|
6533
6991
|
/>
|
|
6534
6992
|
</div>
|
|
@@ -6539,12 +6997,12 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6539
6997
|
value={description}
|
|
6540
6998
|
onChange={(e) => setDescription(e.target.value)}
|
|
6541
6999
|
rows={3}
|
|
6542
|
-
className="input
|
|
7000
|
+
className="theme-input w-full px-4 py-2.5 mt-2 text-sm resize-none"
|
|
6543
7001
|
/>
|
|
6544
7002
|
</div>
|
|
6545
7003
|
<div>
|
|
6546
7004
|
<label className="font-mono text-[10px] text-gray-500 tracking-wider">{t ? t('features.priority').toUpperCase() : 'PRIORITY'}</label>
|
|
6547
|
-
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="input
|
|
7005
|
+
<select value={priority} onChange={(e) => setPriority(e.target.value)} className="theme-input w-full px-4 py-2.5 mt-2 text-sm">
|
|
6548
7006
|
<option value="high">{t ? t('common.high').toUpperCase() : 'HIGH'}</option>
|
|
6549
7007
|
<option value="medium">{t ? t('common.medium').toUpperCase() : 'MEDIUM'}</option>
|
|
6550
7008
|
<option value="low">{t ? t('common.low').toUpperCase() : 'LOW'}</option>
|
|
@@ -6556,7 +7014,7 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6556
7014
|
<select
|
|
6557
7015
|
value={assignedTo}
|
|
6558
7016
|
onChange={(e) => setAssignedTo(e.target.value)}
|
|
6559
|
-
className="input
|
|
7017
|
+
className="theme-input w-full px-4 py-2.5 mt-2 text-sm"
|
|
6560
7018
|
>
|
|
6561
7019
|
<option value="">{t ? t('features.unassigned') : 'Unassigned'}</option>
|
|
6562
7020
|
{team.map(member => (
|
|
@@ -6569,7 +7027,7 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6569
7027
|
<button type="button" onClick={onClose} className="flex-1 py-2.5 border border-white/20 text-gray-500 font-mono text-xs hover:bg-white/5 transition-all">
|
|
6570
7028
|
{t ? t('common.cancel').toUpperCase() : 'CANCEL'}
|
|
6571
7029
|
</button>
|
|
6572
|
-
<button type="submit" disabled={!name.trim()} className="btn-
|
|
7030
|
+
<button type="submit" disabled={!name.trim()} className="theme-btn-primary flex-1 py-2.5 disabled:opacity-50">
|
|
6573
7031
|
{language === 'es' ? 'CREAR' : 'CREATE'}
|
|
6574
7032
|
</button>
|
|
6575
7033
|
</div>
|
|
@@ -6581,7 +7039,7 @@ function AddFeatureForm({ onAdd, onClose, team = [], t, language }) {
|
|
|
6581
7039
|
function Modal({ children, onClose }) {
|
|
6582
7040
|
return (
|
|
6583
7041
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 modal-overlay animate-fade-in" onClick={onClose}>
|
|
6584
|
-
<div className="
|
|
7042
|
+
<div className="theme-card p-6 max-w-md w-full" onClick={e => e.stopPropagation()}>
|
|
6585
7043
|
{children}
|
|
6586
7044
|
</div>
|
|
6587
7045
|
</div>
|
|
@@ -6618,7 +7076,7 @@ function ThemeModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
6618
7076
|
|
|
6619
7077
|
return (
|
|
6620
7078
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 modal-overlay animate-fade-in" onClick={onClose}>
|
|
6621
|
-
<div className="
|
|
7079
|
+
<div className="theme-card p-6 max-w-lg w-full" onClick={e => e.stopPropagation()}>
|
|
6622
7080
|
{/* Header */}
|
|
6623
7081
|
<div className="flex items-center justify-between mb-6">
|
|
6624
7082
|
<div className="flex items-center gap-3">
|
|
@@ -6683,14 +7141,14 @@ function ThemeModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
6683
7141
|
}
|
|
6684
7142
|
}}
|
|
6685
7143
|
placeholder="#00ff88"
|
|
6686
|
-
className="input
|
|
7144
|
+
className="theme-input px-3 py-2 text-sm w-32 font-mono"
|
|
6687
7145
|
/>
|
|
6688
7146
|
<input
|
|
6689
7147
|
type="text"
|
|
6690
7148
|
value={currentTheme.name}
|
|
6691
7149
|
onChange={(e) => updateTheme({ ...currentTheme, name: e.target.value })}
|
|
6692
7150
|
placeholder="Theme name"
|
|
6693
|
-
className="input
|
|
7151
|
+
className="theme-input px-3 py-2 text-sm flex-1 font-mono"
|
|
6694
7152
|
/>
|
|
6695
7153
|
</div>
|
|
6696
7154
|
</div>
|
|
@@ -6707,7 +7165,7 @@ function ThemeModal({ roadmap, setRoadmap, setHasChanges, onClose }) {
|
|
|
6707
7165
|
</div>
|
|
6708
7166
|
<div className="flex-1">
|
|
6709
7167
|
<div className="font-mono text-xs text-white">{roadmap?.project_info?.name || 'Project'}</div>
|
|
6710
|
-
<div className="progress
|
|
7168
|
+
<div className="theme-progress mt-2 h-2">
|
|
6711
7169
|
<div
|
|
6712
7170
|
className="h-full transition-all"
|
|
6713
7171
|
style={{
|
|
@@ -6761,9 +7219,9 @@ function LoginScreen({ onLogin, error }) {
|
|
|
6761
7219
|
</div>
|
|
6762
7220
|
|
|
6763
7221
|
{/* Login Form */}
|
|
6764
|
-
<div className="
|
|
7222
|
+
<div className="theme-card p-6">
|
|
6765
7223
|
<div className="flex items-center gap-3 mb-6">
|
|
6766
|
-
<div className="led led-
|
|
7224
|
+
<div className="led theme-led theme-led-warning" />
|
|
6767
7225
|
<span className="font-mono text-xs text-signal tracking-wider">AUTHENTICATION REQUIRED</span>
|
|
6768
7226
|
</div>
|
|
6769
7227
|
|
|
@@ -6777,7 +7235,7 @@ function LoginScreen({ onLogin, error }) {
|
|
|
6777
7235
|
value={email}
|
|
6778
7236
|
onChange={(e) => setEmail(e.target.value)}
|
|
6779
7237
|
placeholder="admin@localhost"
|
|
6780
|
-
className="input
|
|
7238
|
+
className="theme-input w-full pl-10 pr-4 py-3 text-sm"
|
|
6781
7239
|
autoComplete="email"
|
|
6782
7240
|
autoFocus
|
|
6783
7241
|
/>
|
|
@@ -6793,7 +7251,7 @@ function LoginScreen({ onLogin, error }) {
|
|
|
6793
7251
|
value={password}
|
|
6794
7252
|
onChange={(e) => setPassword(e.target.value)}
|
|
6795
7253
|
placeholder="••••••••"
|
|
6796
|
-
className="input
|
|
7254
|
+
className="theme-input w-full pl-10 pr-4 py-3 text-sm"
|
|
6797
7255
|
autoComplete="current-password"
|
|
6798
7256
|
/>
|
|
6799
7257
|
</div>
|
|
@@ -6806,7 +7264,7 @@ function LoginScreen({ onLogin, error }) {
|
|
|
6806
7264
|
</div>
|
|
6807
7265
|
)}
|
|
6808
7266
|
|
|
6809
|
-
<button type="submit" disabled={loading || !email.trim() || !password.trim()} className="btn-
|
|
7267
|
+
<button type="submit" disabled={loading || !email.trim() || !password.trim()} className="theme-btn-primary w-full py-3 flex items-center justify-center gap-2 disabled:opacity-50">
|
|
6810
7268
|
{loading ? (
|
|
6811
7269
|
<>
|
|
6812
7270
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
@@ -6968,11 +7426,13 @@ Update \`roadmap.json\` with:
|
|
|
6968
7426
|
// Wrap App with providers
|
|
6969
7427
|
function AppWithProviders() {
|
|
6970
7428
|
return (
|
|
6971
|
-
<
|
|
6972
|
-
<
|
|
6973
|
-
<
|
|
6974
|
-
|
|
6975
|
-
|
|
7429
|
+
<ThemeProvider>
|
|
7430
|
+
<ToastProvider>
|
|
7431
|
+
<ActivityProvider>
|
|
7432
|
+
<App />
|
|
7433
|
+
</ActivityProvider>
|
|
7434
|
+
</ToastProvider>
|
|
7435
|
+
</ThemeProvider>
|
|
6976
7436
|
);
|
|
6977
7437
|
}
|
|
6978
7438
|
|