lean-spec 0.2.5 → 0.2.6-dev.20251125015611
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/dist/{chunk-7WXYOHZU.js → chunk-RTEGSMVL.js} +1253 -678
- package/dist/chunk-RTEGSMVL.js.map +1 -0
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +2 -3
- package/templates/detailed/AGENTS.md +113 -0
- package/templates/detailed/README.md +28 -0
- package/templates/detailed/files/DESIGN.md +43 -0
- package/templates/detailed/files/PLAN.md +59 -0
- package/templates/detailed/files/README.md +30 -0
- package/templates/detailed/files/TEST.md +71 -0
- package/templates/examples/api-refactor/README.md +81 -0
- package/templates/examples/api-refactor/package.json +16 -0
- package/templates/examples/api-refactor/src/app.js +40 -0
- package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
- package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
- package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
- package/templates/examples/dark-theme/README.md +66 -0
- package/templates/examples/dark-theme/package.json +16 -0
- package/templates/examples/dark-theme/src/public/app.js +277 -0
- package/templates/examples/dark-theme/src/public/index.html +225 -0
- package/templates/examples/dark-theme/src/public/style.css +625 -0
- package/templates/examples/dark-theme/src/server.js +18 -0
- package/templates/examples/dashboard-widgets/README.md +70 -0
- package/templates/examples/dashboard-widgets/index.html +12 -0
- package/templates/examples/dashboard-widgets/package.json +22 -0
- package/templates/examples/dashboard-widgets/src/App.css +20 -0
- package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
- package/templates/examples/dashboard-widgets/src/index.css +13 -0
- package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
- package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
- package/templates/examples/dashboard-widgets/vite.config.js +6 -0
- package/templates/standard/AGENTS.md +113 -0
- package/templates/standard/README.md +4 -2
- package/dist/chunk-7WXYOHZU.js.map +0 -1
- package/templates/_shared/agents-components/core-rules-base-additions.md +0 -4
- package/templates/_shared/agents-components/core-rules-enterprise-additions.md +0 -4
- package/templates/_shared/agents-components/core-rules-shared.md +0 -1
- package/templates/_shared/agents-components/discovery-commands-enterprise-additions.md +0 -6
- package/templates/_shared/agents-components/discovery-commands-minimal-additions.md +0 -0
- package/templates/_shared/agents-components/discovery-commands-shared.md +0 -8
- package/templates/_shared/agents-components/discovery-commands-standard-additions.md +0 -3
- package/templates/_shared/agents-components/enterprise-approval.md +0 -10
- package/templates/_shared/agents-components/enterprise-compliance.md +0 -12
- package/templates/_shared/agents-components/enterprise-when-required.md +0 -13
- package/templates/_shared/agents-components/essential-commands-enterprise-additions.md +0 -29
- package/templates/_shared/agents-components/essential-commands-minimal-additions.md +0 -1
- package/templates/_shared/agents-components/essential-commands-shared.md +0 -15
- package/templates/_shared/agents-components/essential-commands-standard-additions.md +0 -18
- package/templates/_shared/agents-components/frontmatter-enterprise.md +0 -33
- package/templates/_shared/agents-components/frontmatter-minimal.md +0 -18
- package/templates/_shared/agents-components/frontmatter-standard.md +0 -23
- package/templates/_shared/agents-components/quality-standards-enterprise-additions.md +0 -4
- package/templates/_shared/agents-components/quality-standards-minimal-additions.md +0 -3
- package/templates/_shared/agents-components/quality-standards-shared.md +0 -6
- package/templates/_shared/agents-components/status-update-triggers.md +0 -14
- package/templates/_shared/agents-components/when-to-use-enterprise.md +0 -11
- package/templates/_shared/agents-components/when-to-use-minimal.md +0 -9
- package/templates/_shared/agents-components/when-to-use-standard.md +0 -9
- package/templates/_shared/agents-components/workflow-enterprise.md +0 -11
- package/templates/_shared/agents-components/workflow-standard-detailed.md +0 -10
- package/templates/_shared/agents-components/workflow-standard.md +0 -8
- package/templates/_shared/agents-template.hbs +0 -43
- package/templates/enterprise/README.md +0 -25
- package/templates/enterprise/agents-config.json +0 -16
- package/templates/enterprise/files/AGENTS.md +0 -194
- package/templates/enterprise/spec-template.md +0 -80
- package/templates/minimal/README.md +0 -18
- package/templates/minimal/agents-config.json +0 -13
- package/templates/minimal/config.json +0 -15
- package/templates/minimal/files/AGENTS.md +0 -116
- package/templates/minimal/spec-template.md +0 -25
- package/templates/standard/agents-config.json +0 -13
- package/templates/standard/files/AGENTS.md +0 -142
- /package/templates/{enterprise → detailed}/config.json +0 -0
- /package/templates/standard/{spec-template.md → files/README.md} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get weather for a city
|
|
5
|
+
*
|
|
6
|
+
* PROBLEM: This function has HTTP logic mixed with business logic
|
|
7
|
+
* - Manual fetch calls
|
|
8
|
+
* - Manual error handling
|
|
9
|
+
* - Manual JSON parsing
|
|
10
|
+
* - No retry logic
|
|
11
|
+
* - Hard to test (can't mock HTTP)
|
|
12
|
+
*/
|
|
13
|
+
export async function getWeather(city) {
|
|
14
|
+
// Mock API endpoint (replace with real API in production)
|
|
15
|
+
const url = `https://api.weatherapi.com/v1/current.json?key=mock&q=${city}`;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url);
|
|
19
|
+
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(`Weather API error: ${response.status}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
|
|
26
|
+
// Business logic: Transform API response to our format
|
|
27
|
+
return {
|
|
28
|
+
city: data.location?.name || city,
|
|
29
|
+
temp: data.current?.temp_c || Math.floor(Math.random() * 30),
|
|
30
|
+
condition: data.current?.condition?.text || 'Sunny',
|
|
31
|
+
humidity: data.current?.humidity || Math.floor(Math.random() * 100),
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// For demo, return mock data on error
|
|
35
|
+
return {
|
|
36
|
+
city,
|
|
37
|
+
temp: Math.floor(Math.random() * 30),
|
|
38
|
+
condition: 'Sunny (mock data)',
|
|
39
|
+
humidity: Math.floor(Math.random() * 100),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Dark Theme Demo
|
|
2
|
+
|
|
3
|
+
> **Tutorial**: [Adding Dark Theme Support](https://leanspec.dev/docs/tutorials/adding-dark-theme)
|
|
4
|
+
|
|
5
|
+
## Scenario
|
|
6
|
+
|
|
7
|
+
You're building a professional admin dashboard for a SaaS product. The dashboard looks modern and works great, but users keep requesting a dark mode option for late-night monitoring sessions and to reduce eye strain during extended use. Currently, the dashboard only has a bright light theme.
|
|
8
|
+
|
|
9
|
+
## What's Here
|
|
10
|
+
|
|
11
|
+
A professional admin dashboard with:
|
|
12
|
+
- Interactive data visualization with charts (Chart.js)
|
|
13
|
+
- Real-time statistics cards with animations
|
|
14
|
+
- Sidebar navigation and top bar
|
|
15
|
+
- Activity feed with recent events
|
|
16
|
+
- Responsive layout
|
|
17
|
+
- Clean, modern light theme
|
|
18
|
+
- No dark mode support (yet!)
|
|
19
|
+
|
|
20
|
+
**Files:**
|
|
21
|
+
- `src/server.js` - Express server for static files
|
|
22
|
+
- `src/public/index.html` - Dashboard interface with sidebar, charts, and stats
|
|
23
|
+
- `src/public/style.css` - Current light theme styles (CSS custom properties ready)
|
|
24
|
+
- `src/public/app.js` - Dashboard logic, chart initialization, and animations
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Install dependencies
|
|
30
|
+
npm install
|
|
31
|
+
|
|
32
|
+
# Start the server
|
|
33
|
+
npm start
|
|
34
|
+
|
|
35
|
+
# Open in your browser:
|
|
36
|
+
# http://localhost:3000
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Your Mission
|
|
40
|
+
|
|
41
|
+
Add dark theme support with automatic switching based on system preferences. Follow the tutorial and ask your AI assistant:
|
|
42
|
+
|
|
43
|
+
> "Help me add dark theme support to this admin dashboard using LeanSpec. It should automatically switch based on the user's system theme preference."
|
|
44
|
+
|
|
45
|
+
The AI will guide you through:
|
|
46
|
+
1. Creating a spec for dark theme support
|
|
47
|
+
2. Designing CSS custom properties for dark mode colors
|
|
48
|
+
3. Implementing the `@media (prefers-color-scheme: dark)` query
|
|
49
|
+
4. Ensuring charts adapt to the theme
|
|
50
|
+
5. Testing the theme switching
|
|
51
|
+
|
|
52
|
+
## Current Limitations
|
|
53
|
+
|
|
54
|
+
- Only light theme available
|
|
55
|
+
- No manual theme toggle
|
|
56
|
+
- Chart colors don't adapt to theme
|
|
57
|
+
- No theme persistence across sessions
|
|
58
|
+
|
|
59
|
+
These are perfect opportunities to practice spec-driven development!
|
|
60
|
+
|
|
61
|
+
## Tech Stack
|
|
62
|
+
|
|
63
|
+
- **Frontend**: Vanilla HTML, CSS, JavaScript
|
|
64
|
+
- **Charts**: Chart.js 4.4.0
|
|
65
|
+
- **Backend**: Express.js (serving static files)
|
|
66
|
+
- **Styling**: CSS Custom Properties (CSS Variables)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dark-theme-demo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Professional admin dashboard for LeanSpec Dark Theme Tutorial",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/server.js",
|
|
8
|
+
"dev": "node --watch src/server.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["leanspec", "tutorial", "demo", "dashboard", "admin"],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"express": "^4.18.2"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// Admin Dashboard - Interactive data visualization
|
|
2
|
+
// This is a starting point for adding dark theme support
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Initialize Charts
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
function initializeCharts() {
|
|
9
|
+
// Revenue Chart
|
|
10
|
+
const revenueCtx = document.getElementById('revenueChart');
|
|
11
|
+
if (revenueCtx) {
|
|
12
|
+
new Chart(revenueCtx, {
|
|
13
|
+
type: 'line',
|
|
14
|
+
data: {
|
|
15
|
+
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
|
16
|
+
datasets: [{
|
|
17
|
+
label: 'Revenue',
|
|
18
|
+
data: [4200, 5100, 4800, 6200, 5800, 7100, 6500],
|
|
19
|
+
borderColor: '#0066cc',
|
|
20
|
+
backgroundColor: 'rgba(0, 102, 204, 0.1)',
|
|
21
|
+
borderWidth: 2,
|
|
22
|
+
tension: 0.4,
|
|
23
|
+
fill: true,
|
|
24
|
+
pointBackgroundColor: '#0066cc',
|
|
25
|
+
pointBorderColor: '#fff',
|
|
26
|
+
pointBorderWidth: 2,
|
|
27
|
+
pointRadius: 4,
|
|
28
|
+
pointHoverRadius: 6
|
|
29
|
+
}]
|
|
30
|
+
},
|
|
31
|
+
options: {
|
|
32
|
+
responsive: true,
|
|
33
|
+
maintainAspectRatio: false,
|
|
34
|
+
plugins: {
|
|
35
|
+
legend: {
|
|
36
|
+
display: false
|
|
37
|
+
},
|
|
38
|
+
tooltip: {
|
|
39
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
40
|
+
padding: 12,
|
|
41
|
+
borderRadius: 8,
|
|
42
|
+
titleColor: '#fff',
|
|
43
|
+
bodyColor: '#fff',
|
|
44
|
+
callbacks: {
|
|
45
|
+
label: function(context) {
|
|
46
|
+
return '$' + context.parsed.y.toLocaleString();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
scales: {
|
|
52
|
+
y: {
|
|
53
|
+
beginAtZero: true,
|
|
54
|
+
grid: {
|
|
55
|
+
color: 'rgba(0, 0, 0, 0.05)'
|
|
56
|
+
},
|
|
57
|
+
ticks: {
|
|
58
|
+
callback: function(value) {
|
|
59
|
+
return '$' + value.toLocaleString();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
x: {
|
|
64
|
+
grid: {
|
|
65
|
+
display: false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Traffic Chart
|
|
74
|
+
const trafficCtx = document.getElementById('trafficChart');
|
|
75
|
+
if (trafficCtx) {
|
|
76
|
+
new Chart(trafficCtx, {
|
|
77
|
+
type: 'doughnut',
|
|
78
|
+
data: {
|
|
79
|
+
labels: ['Organic', 'Direct', 'Social', 'Referral'],
|
|
80
|
+
datasets: [{
|
|
81
|
+
data: [42, 28, 18, 12],
|
|
82
|
+
backgroundColor: [
|
|
83
|
+
'#0066cc',
|
|
84
|
+
'#00a854',
|
|
85
|
+
'#7c3aed',
|
|
86
|
+
'#ff8c00'
|
|
87
|
+
],
|
|
88
|
+
borderWidth: 0,
|
|
89
|
+
hoverOffset: 8
|
|
90
|
+
}]
|
|
91
|
+
},
|
|
92
|
+
options: {
|
|
93
|
+
responsive: true,
|
|
94
|
+
maintainAspectRatio: false,
|
|
95
|
+
plugins: {
|
|
96
|
+
legend: {
|
|
97
|
+
position: 'bottom',
|
|
98
|
+
labels: {
|
|
99
|
+
padding: 16,
|
|
100
|
+
usePointStyle: true,
|
|
101
|
+
pointStyle: 'circle',
|
|
102
|
+
font: {
|
|
103
|
+
size: 12
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
tooltip: {
|
|
108
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
109
|
+
padding: 12,
|
|
110
|
+
borderRadius: 8,
|
|
111
|
+
titleColor: '#fff',
|
|
112
|
+
bodyColor: '#fff',
|
|
113
|
+
callbacks: {
|
|
114
|
+
label: function(context) {
|
|
115
|
+
return context.label + ': ' + context.parsed + '%';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ============================================
|
|
126
|
+
// Animate Stats on Load
|
|
127
|
+
// ============================================
|
|
128
|
+
|
|
129
|
+
function animateValue(element, start, end, duration) {
|
|
130
|
+
const range = end - start;
|
|
131
|
+
const increment = range / (duration / 16);
|
|
132
|
+
let current = start;
|
|
133
|
+
|
|
134
|
+
const timer = setInterval(() => {
|
|
135
|
+
current += increment;
|
|
136
|
+
if ((increment > 0 && current >= end) || (increment < 0 && current <= end)) {
|
|
137
|
+
element.textContent = formatStatValue(end);
|
|
138
|
+
clearInterval(timer);
|
|
139
|
+
} else {
|
|
140
|
+
element.textContent = formatStatValue(Math.floor(current));
|
|
141
|
+
}
|
|
142
|
+
}, 16);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatStatValue(value) {
|
|
146
|
+
if (value >= 1000) {
|
|
147
|
+
return value.toLocaleString();
|
|
148
|
+
}
|
|
149
|
+
return value.toString();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function animateStats() {
|
|
153
|
+
const stats = [
|
|
154
|
+
{ id: 'totalUsers', value: 2847 },
|
|
155
|
+
{ id: 'revenue', value: 45231, prefix: '$' },
|
|
156
|
+
{ id: 'completedTasks', value: 892 },
|
|
157
|
+
{ id: 'activeProjects', value: 24 }
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
stats.forEach(stat => {
|
|
161
|
+
const element = document.getElementById(stat.id);
|
|
162
|
+
if (element) {
|
|
163
|
+
if (stat.prefix) {
|
|
164
|
+
const originalText = element.textContent;
|
|
165
|
+
element.textContent = stat.prefix + '0';
|
|
166
|
+
animateValue(element, 0, stat.value, 1000);
|
|
167
|
+
const timer = setInterval(() => {
|
|
168
|
+
if (element.textContent !== stat.prefix + '0') {
|
|
169
|
+
const currentVal = element.textContent.replace(/[^0-9]/g, '');
|
|
170
|
+
element.textContent = stat.prefix + Number(currentVal).toLocaleString();
|
|
171
|
+
}
|
|
172
|
+
}, 16);
|
|
173
|
+
setTimeout(() => clearInterval(timer), 1000);
|
|
174
|
+
} else {
|
|
175
|
+
element.textContent = '0';
|
|
176
|
+
animateValue(element, 0, stat.value, 1000);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================
|
|
183
|
+
// Recent Activity
|
|
184
|
+
// ============================================
|
|
185
|
+
|
|
186
|
+
function loadRecentActivity() {
|
|
187
|
+
const activities = [
|
|
188
|
+
{
|
|
189
|
+
type: 'user',
|
|
190
|
+
title: 'New user registration',
|
|
191
|
+
description: 'Sarah Johnson joined the platform',
|
|
192
|
+
time: '5 minutes ago',
|
|
193
|
+
icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
194
|
+
<path d="M10 11C12.2091 11 14 9.20914 14 7C14 4.79086 12.2091 3 10 3C7.79086 3 6 4.79086 6 7C6 9.20914 7.79086 11 10 11Z" stroke="currentColor" stroke-width="2"/>
|
|
195
|
+
<path d="M3 17C3 14.2386 5.23858 12 8 12H12C14.7614 12 17 14.2386 17 17" stroke="currentColor" stroke-width="2"/>
|
|
196
|
+
</svg>`
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'report',
|
|
200
|
+
title: 'Monthly report generated',
|
|
201
|
+
description: 'Q4 2024 performance summary is ready',
|
|
202
|
+
time: '1 hour ago',
|
|
203
|
+
icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
204
|
+
<rect x="3" y="3" width="14" height="14" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
205
|
+
<path d="M7 8H13M7 12H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
206
|
+
</svg>`
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: 'alert',
|
|
210
|
+
title: 'Server capacity warning',
|
|
211
|
+
description: 'CPU usage exceeded 80% threshold',
|
|
212
|
+
time: '3 hours ago',
|
|
213
|
+
icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
214
|
+
<path d="M10 6V10M10 14H10.01M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
215
|
+
</svg>`
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: 'user',
|
|
219
|
+
title: 'Team member added',
|
|
220
|
+
description: 'Michael Chen joined the Development team',
|
|
221
|
+
time: '5 hours ago',
|
|
222
|
+
icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
223
|
+
<path d="M10 11C12.2091 11 14 9.20914 14 7C14 4.79086 12.2091 3 10 3C7.79086 3 6 4.79086 6 7C6 9.20914 7.79086 11 10 11Z" stroke="currentColor" stroke-width="2"/>
|
|
224
|
+
<path d="M3 17C3 14.2386 5.23858 12 8 12H12C14.7614 12 17 14.2386 17 17" stroke="currentColor" stroke-width="2"/>
|
|
225
|
+
</svg>`
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'report',
|
|
229
|
+
title: 'Backup completed',
|
|
230
|
+
description: 'Database backup finished successfully',
|
|
231
|
+
time: '8 hours ago',
|
|
232
|
+
icon: `<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
233
|
+
<path d="M3 12L9 18L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
234
|
+
</svg>`
|
|
235
|
+
}
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
const activityList = document.getElementById('activityList');
|
|
239
|
+
if (activityList) {
|
|
240
|
+
activityList.innerHTML = activities.map(activity => `
|
|
241
|
+
<div class="activity-item">
|
|
242
|
+
<div class="activity-icon ${activity.type}">
|
|
243
|
+
${activity.icon}
|
|
244
|
+
</div>
|
|
245
|
+
<div class="activity-content">
|
|
246
|
+
<div class="activity-title">${activity.title}</div>
|
|
247
|
+
<div class="activity-description">${activity.description}</div>
|
|
248
|
+
<div class="activity-time">${activity.time}</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
`).join('');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============================================
|
|
256
|
+
// Initialize Dashboard
|
|
257
|
+
// ============================================
|
|
258
|
+
|
|
259
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
260
|
+
// Initialize all components
|
|
261
|
+
animateStats();
|
|
262
|
+
initializeCharts();
|
|
263
|
+
loadRecentActivity();
|
|
264
|
+
|
|
265
|
+
// Add subtle hover effects
|
|
266
|
+
document.querySelectorAll('.stat-card').forEach(card => {
|
|
267
|
+
card.addEventListener('mouseenter', (e) => {
|
|
268
|
+
e.currentTarget.style.transform = 'translateY(-4px)';
|
|
269
|
+
});
|
|
270
|
+
card.addEventListener('mouseleave', (e) => {
|
|
271
|
+
e.currentTarget.style.transform = 'translateY(0)';
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
console.log('✓ Dashboard initialized');
|
|
276
|
+
console.log('💡 Try adding dark theme support with CSS custom properties!');
|
|
277
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Admin Dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<!-- Sidebar Navigation -->
|
|
12
|
+
<aside class="sidebar">
|
|
13
|
+
<div class="sidebar-header">
|
|
14
|
+
<div class="logo">
|
|
15
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none">
|
|
16
|
+
<rect width="32" height="32" rx="8" fill="currentColor" opacity="0.2"/>
|
|
17
|
+
<path d="M16 8L24 12V20L16 24L8 20V12L16 8Z" fill="currentColor"/>
|
|
18
|
+
</svg>
|
|
19
|
+
<span class="logo-text">AdminPro</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<nav class="sidebar-nav">
|
|
24
|
+
<a href="#" class="nav-item active">
|
|
25
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
26
|
+
<rect x="3" y="3" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"/>
|
|
27
|
+
<rect x="11" y="3" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"/>
|
|
28
|
+
<rect x="3" y="11" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"/>
|
|
29
|
+
<rect x="11" y="11" width="6" height="6" rx="1" stroke="currentColor" stroke-width="2"/>
|
|
30
|
+
</svg>
|
|
31
|
+
<span>Dashboard</span>
|
|
32
|
+
</a>
|
|
33
|
+
<a href="#" class="nav-item">
|
|
34
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
35
|
+
<path d="M10 11C12.2091 11 14 9.20914 14 7C14 4.79086 12.2091 3 10 3C7.79086 3 6 4.79086 6 7C6 9.20914 7.79086 11 10 11Z" stroke="currentColor" stroke-width="2"/>
|
|
36
|
+
<path d="M3 17C3 14.2386 5.23858 12 8 12H12C14.7614 12 17 14.2386 17 17" stroke="currentColor" stroke-width="2"/>
|
|
37
|
+
</svg>
|
|
38
|
+
<span>Users</span>
|
|
39
|
+
</a>
|
|
40
|
+
<a href="#" class="nav-item">
|
|
41
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
42
|
+
<path d="M3 6H17M3 10H17M3 14H17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
43
|
+
</svg>
|
|
44
|
+
<span>Analytics</span>
|
|
45
|
+
</a>
|
|
46
|
+
<a href="#" class="nav-item">
|
|
47
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
48
|
+
<rect x="3" y="3" width="14" height="14" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
49
|
+
<path d="M7 8H13M7 12H10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
50
|
+
</svg>
|
|
51
|
+
<span>Reports</span>
|
|
52
|
+
</a>
|
|
53
|
+
<a href="#" class="nav-item">
|
|
54
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
55
|
+
<circle cx="10" cy="10" r="3" stroke="currentColor" stroke-width="2"/>
|
|
56
|
+
<path d="M10 3V5M10 15V17M17 10H15M5 10H3M15.364 4.636L13.95 6.05M6.05 13.95L4.636 15.364M15.364 15.364L13.95 13.95M6.05 6.05L4.636 4.636" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
57
|
+
</svg>
|
|
58
|
+
<span>Settings</span>
|
|
59
|
+
</a>
|
|
60
|
+
</nav>
|
|
61
|
+
|
|
62
|
+
<div class="sidebar-footer">
|
|
63
|
+
<div class="user-profile">
|
|
64
|
+
<div class="avatar">JD</div>
|
|
65
|
+
<div class="user-info">
|
|
66
|
+
<div class="user-name">John Doe</div>
|
|
67
|
+
<div class="user-role">Administrator</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</aside>
|
|
72
|
+
|
|
73
|
+
<!-- Main Content -->
|
|
74
|
+
<main class="main-content">
|
|
75
|
+
<!-- Top Bar -->
|
|
76
|
+
<header class="top-bar">
|
|
77
|
+
<div class="search-bar">
|
|
78
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
79
|
+
<circle cx="9" cy="9" r="6" stroke="currentColor" stroke-width="2"/>
|
|
80
|
+
<path d="M13.5 13.5L17 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
81
|
+
</svg>
|
|
82
|
+
<input type="text" placeholder="Search..." />
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div class="top-bar-actions">
|
|
86
|
+
<button class="icon-button" title="Notifications">
|
|
87
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
88
|
+
<path d="M10 3C7.79086 3 6 4.79086 6 7V10L4 12V14H16V12L14 10V7C14 4.79086 12.2091 3 10 3Z" stroke="currentColor" stroke-width="2"/>
|
|
89
|
+
<path d="M8 14V15C8 16.1046 8.89543 17 10 17C11.1046 17 12 16.1046 12 15V14" stroke="currentColor" stroke-width="2"/>
|
|
90
|
+
</svg>
|
|
91
|
+
<span class="badge">3</span>
|
|
92
|
+
</button>
|
|
93
|
+
<button class="icon-button" title="Messages">
|
|
94
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
95
|
+
<path d="M17 10C17 13.866 13.866 17 10 17C8.87809 17 7.82343 16.7223 6.89743 16.2334L3 17L3.76661 13.1026C3.27766 12.1766 3 11.1219 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2"/>
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
<!-- Dashboard Content -->
|
|
102
|
+
<div class="dashboard-content">
|
|
103
|
+
<!-- Page Header -->
|
|
104
|
+
<div class="page-header">
|
|
105
|
+
<div>
|
|
106
|
+
<h1>Dashboard Overview</h1>
|
|
107
|
+
<p class="page-subtitle">Welcome back! Here's what's happening today.</p>
|
|
108
|
+
</div>
|
|
109
|
+
<button class="btn-primary">
|
|
110
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
111
|
+
<path d="M8 3V13M3 8H13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
112
|
+
</svg>
|
|
113
|
+
New Report
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Stats Grid -->
|
|
118
|
+
<div class="stats-grid">
|
|
119
|
+
<div class="stat-card">
|
|
120
|
+
<div class="stat-header">
|
|
121
|
+
<div class="stat-icon blue">
|
|
122
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
123
|
+
<path d="M17 21V19C17 17.9391 16.5786 16.9217 15.8284 16.1716C15.0783 15.4214 14.0609 15 13 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21" stroke="currentColor" stroke-width="2"/>
|
|
124
|
+
<circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2"/>
|
|
125
|
+
<path d="M23 21V19C22.9993 18.1137 22.7044 17.2528 22.1614 16.5523C21.6184 15.8519 20.8581 15.3516 20 15.13" stroke="currentColor" stroke-width="2"/>
|
|
126
|
+
<path d="M16 3.13C16.8604 3.35031 17.623 3.85071 18.1676 4.55232C18.7122 5.25392 19.0078 6.11683 19.0078 7.005C19.0078 7.89318 18.7122 8.75608 18.1676 9.45769C17.623 10.1593 16.8604 10.6597 16 10.88" stroke="currentColor" stroke-width="2"/>
|
|
127
|
+
</svg>
|
|
128
|
+
</div>
|
|
129
|
+
<span class="stat-change positive">↑ 12%</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="stat-content">
|
|
132
|
+
<h3 class="stat-value" id="totalUsers">2,847</h3>
|
|
133
|
+
<p class="stat-label">Total Users</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="stat-card">
|
|
138
|
+
<div class="stat-header">
|
|
139
|
+
<div class="stat-icon green">
|
|
140
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
141
|
+
<path d="M12 2V6M12 18V22M4.93 4.93L7.76 7.76M16.24 16.24L19.07 19.07M2 12H6M18 12H22M4.93 19.07L7.76 16.24M16.24 7.76L19.07 4.93" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
142
|
+
<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2"/>
|
|
143
|
+
</svg>
|
|
144
|
+
</div>
|
|
145
|
+
<span class="stat-change positive">↑ 8%</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="stat-content">
|
|
148
|
+
<h3 class="stat-value" id="revenue">$45,231</h3>
|
|
149
|
+
<p class="stat-label">Revenue</p>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="stat-card">
|
|
154
|
+
<div class="stat-header">
|
|
155
|
+
<div class="stat-icon purple">
|
|
156
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
157
|
+
<path d="M3 12L9 18L21 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
158
|
+
</svg>
|
|
159
|
+
</div>
|
|
160
|
+
<span class="stat-change positive">↑ 23%</span>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="stat-content">
|
|
163
|
+
<h3 class="stat-value" id="completedTasks">892</h3>
|
|
164
|
+
<p class="stat-label">Completed Tasks</p>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="stat-card">
|
|
169
|
+
<div class="stat-header">
|
|
170
|
+
<div class="stat-icon orange">
|
|
171
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
172
|
+
<path d="M13 2L3 14H12L11 22L21 10H12L13 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
173
|
+
</svg>
|
|
174
|
+
</div>
|
|
175
|
+
<span class="stat-change negative">↓ 4%</span>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="stat-content">
|
|
178
|
+
<h3 class="stat-value" id="activeProjects">24</h3>
|
|
179
|
+
<p class="stat-label">Active Projects</p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- Charts Row -->
|
|
185
|
+
<div class="charts-row">
|
|
186
|
+
<div class="chart-card">
|
|
187
|
+
<div class="chart-header">
|
|
188
|
+
<h3>Revenue Overview</h3>
|
|
189
|
+
<select class="chart-period">
|
|
190
|
+
<option>Last 7 days</option>
|
|
191
|
+
<option>Last 30 days</option>
|
|
192
|
+
<option>Last 90 days</option>
|
|
193
|
+
</select>
|
|
194
|
+
</div>
|
|
195
|
+
<canvas id="revenueChart"></canvas>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<div class="chart-card">
|
|
199
|
+
<div class="chart-header">
|
|
200
|
+
<h3>Traffic Sources</h3>
|
|
201
|
+
<select class="chart-period">
|
|
202
|
+
<option>This month</option>
|
|
203
|
+
<option>Last month</option>
|
|
204
|
+
</select>
|
|
205
|
+
</div>
|
|
206
|
+
<canvas id="trafficChart"></canvas>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<!-- Recent Activity -->
|
|
211
|
+
<div class="activity-section">
|
|
212
|
+
<div class="section-header">
|
|
213
|
+
<h3>Recent Activity</h3>
|
|
214
|
+
<a href="#" class="view-all">View All →</a>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="activity-list" id="activityList">
|
|
217
|
+
<!-- Activity items will be populated by JavaScript -->
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
</main>
|
|
222
|
+
|
|
223
|
+
<script src="app.js"></script>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|