diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/static/css/styles.css | 70 | ||||
| -rw-r--r-- | web/static/js/app.js | 77 | ||||
| -rw-r--r-- | web/templates/index.html | 185 |
3 files changed, 332 insertions, 0 deletions
diff --git a/web/static/css/styles.css b/web/static/css/styles.css new file mode 100644 index 0000000..aee6ee3 --- /dev/null +++ b/web/static/css/styles.css @@ -0,0 +1,70 @@ +/* Custom styles for Personal Dashboard */ + +/* Line clamp utility for truncating text */ +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Loading spinner */ +.spinner { + border: 3px solid #f3f3f3; + border-top: 3px solid #3b82f6; + border-radius: 50%; + width: 20px; + height: 20px; + animation: spin 1s linear infinite; + display: inline-block; + margin-left: 8px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Smooth transitions */ +* { + transition-property: background-color, border-color, color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #888; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Print styles */ +@media print { + .no-print { + display: none; + } +} + +/* Dark mode support (optional) */ +@media (prefers-color-scheme: dark) { + /* Uncomment to enable dark mode */ + /* + body { + background-color: #1a202c; + color: #e2e8f0; + } + */ +} diff --git a/web/static/js/app.js b/web/static/js/app.js new file mode 100644 index 0000000..a96c05d --- /dev/null +++ b/web/static/js/app.js @@ -0,0 +1,77 @@ +// Personal Dashboard JavaScript + +// Auto-refresh every 5 minutes +const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds + +// Initialize auto-refresh on page load +document.addEventListener('DOMContentLoaded', function() { + // Set up auto-refresh + setInterval(autoRefresh, AUTO_REFRESH_INTERVAL); +}); + +// Auto-refresh function +async function autoRefresh() { + console.log('Auto-refreshing data...'); + try { + const response = await fetch('/api/refresh', { + method: 'POST' + }); + + if (response.ok) { + // Reload the page to show updated data + window.location.reload(); + } + } catch (error) { + console.error('Auto-refresh failed:', error); + } +} + +// Manual refresh function +async function refreshData() { + const button = event.target; + const originalText = button.textContent; + + // Show loading state + button.disabled = true; + button.innerHTML = 'Refreshing...<span class="spinner"></span>'; + + try { + const response = await fetch('/api/refresh', { + method: 'POST' + }); + + if (response.ok) { + // Update last updated time + const now = new Date(); + const timeString = now.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit' + }); + document.getElementById('last-updated').textContent = timeString; + + // Reload the page to show updated data + setTimeout(() => { + window.location.reload(); + }, 500); + } else { + throw new Error('Refresh failed'); + } + } catch (error) { + console.error('Refresh failed:', error); + alert('Failed to refresh data. Please try again.'); + button.disabled = false; + button.textContent = originalText; + } +} + +// Filter tasks by status +function filterTasks(status) { + // This will be implemented in Phase 2 + console.log('Filter tasks:', status); +} + +// Toggle task completion +function toggleTask(taskId) { + // This will be implemented in Phase 2 + console.log('Toggle task:', taskId); +} diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000..7668a94 --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,185 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Personal Dashboard</title> + <link rel="stylesheet" href="/static/css/styles.css"> + <script src="https://cdn.tailwindcss.com"></script> +</head> +<body class="bg-gray-100 min-h-screen"> + <div class="container mx-auto px-4 py-8 max-w-7xl"> + <!-- Header --> + <header class="mb-8 flex justify-between items-center"> + <h1 class="text-3xl font-bold text-gray-800">Personal Dashboard</h1> + <div class="flex items-center gap-4"> + <span class="text-sm text-gray-600"> + Last updated: <span id="last-updated">{{.LastUpdated.Format "3:04 PM"}}</span> + </span> + <button onclick="refreshData()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition"> + Refresh + </button> + </div> + </header> + + <!-- Error Messages --> + {{if .Errors}} + <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg mb-6"> + <p class="font-bold">Errors:</p> + <ul class="list-disc list-inside"> + {{range .Errors}} + <li>{{.}}</li> + {{end}} + </ul> + </div> + {{end}} + + <!-- Main Grid --> + <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> + <!-- Trello Boards Section (PRIORITY) --> + <div class="lg:col-span-3"> + {{if .Boards}} + <div class="bg-white rounded-lg shadow-md p-6 mb-6"> + <h2 class="text-xl font-semibold mb-4 text-gray-800">📋 Trello Boards</h2> + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + {{range .Boards}} + <div class="border border-gray-300 rounded-lg p-4 bg-gradient-to-br from-blue-50 to-white"> + <h3 class="font-bold text-lg text-gray-800 mb-3">{{.Name}}</h3> + {{if .Cards}} + <div class="space-y-2 max-h-96 overflow-y-auto"> + {{range .Cards}} + <div class="bg-white border border-gray-200 rounded p-3 hover:shadow-md transition"> + <p class="font-medium text-gray-800 text-sm">{{.Name}}</p> + {{if .ListName}} + <span class="inline-block mt-1 text-xs bg-gray-200 text-gray-700 px-2 py-1 rounded"> + {{.ListName}} + </span> + {{end}} + {{if .DueDate}} + <span class="inline-block mt-1 text-xs bg-red-100 text-red-800 px-2 py-1 rounded"> + Due: {{.DueDate.Format "Jan 2"}} + </span> + {{end}} + {{if .URL}} + <a href="{{.URL}}" target="_blank" class="text-blue-600 hover:text-blue-800 text-xs mt-1 inline-block"> + View → + </a> + {{end}} + </div> + {{end}} + </div> + {{else}} + <p class="text-gray-500 text-sm text-center py-4">No cards</p> + {{end}} + </div> + {{end}} + </div> + </div> + {{end}} + </div> + + <!-- Tasks Section --> + <div class="lg:col-span-2"> + <div class="bg-white rounded-lg shadow-md p-6"> + <h2 class="text-xl font-semibold mb-4 text-gray-800">✓ Todoist Tasks</h2> + + {{if .Tasks}} + <div class="space-y-3"> + {{range .Tasks}} + <div class="flex items-start gap-3 p-3 hover:bg-gray-50 rounded-lg transition"> + <input type="checkbox" {{if .Completed}}checked{{end}} + class="mt-1 h-5 w-5 text-blue-600 rounded" disabled> + <div class="flex-1"> + <p class="font-medium text-gray-800 {{if .Completed}}line-through text-gray-500{{end}}"> + {{.Content}} + </p> + {{if .Description}} + <p class="text-sm text-gray-600 mt-1">{{.Description}}</p> + {{end}} + <div class="flex gap-2 mt-2 text-xs text-gray-500"> + {{if .ProjectName}} + <span class="bg-gray-200 px-2 py-1 rounded">{{.ProjectName}}</span> + {{end}} + {{if .DueDate}} + <span class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded"> + Due: {{.DueDate.Format "Jan 2"}} + </span> + {{end}} + {{range .Labels}} + <span class="bg-blue-100 text-blue-800 px-2 py-1 rounded">{{.}}</span> + {{end}} + </div> + </div> + {{if .URL}} + <a href="{{.URL}}" target="_blank" class="text-blue-600 hover:text-blue-800"> + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path> + </svg> + </a> + {{end}} + </div> + {{end}} + </div> + {{else}} + <p class="text-gray-500 text-center py-8">No tasks found</p> + {{end}} + </div> + </div> + + <!-- Meals Section --> + <div> + <div class="bg-white rounded-lg shadow-md p-6"> + <h2 class="text-xl font-semibold mb-4 text-gray-800">Upcoming Meals</h2> + + {{if .Meals}} + <div class="space-y-3"> + {{range .Meals}} + <div class="border-l-4 border-green-500 pl-3 py-2"> + <p class="font-medium text-gray-800">{{.RecipeName}}</p> + <div class="flex justify-between items-center mt-1"> + <span class="text-sm text-gray-600">{{.Date.Format "Mon, Jan 2"}}</span> + <span class="text-xs bg-green-100 text-green-800 px-2 py-1 rounded capitalize"> + {{.MealType}} + </span> + </div> + </div> + {{end}} + </div> + {{else}} + <p class="text-gray-500 text-center py-8">No meals planned</p> + {{end}} + </div> + </div> + </div> + + <!-- Notes Section --> + {{if .Notes}} + <div class="mt-6"> + <div class="bg-white rounded-lg shadow-md p-6"> + <h2 class="text-xl font-semibold mb-4 text-gray-800">Recent Notes</h2> + <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> + {{range .Notes}} + <div class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition"> + <h3 class="font-semibold text-gray-800 mb-2">{{.Title}}</h3> + <p class="text-sm text-gray-600 mb-2 line-clamp-3">{{.Content}}</p> + <div class="flex justify-between items-center text-xs text-gray-500"> + <span>{{.Modified.Format "Jan 2, 3:04 PM"}}</span> + {{if .Tags}} + <div class="flex gap-1"> + {{range .Tags}} + <span class="bg-purple-100 text-purple-800 px-2 py-1 rounded">#{{.}}</span> + {{end}} + </div> + {{end}} + </div> + </div> + {{end}} + </div> + </div> + </div> + {{end}} + </div> + + <script src="/static/js/app.js"></script> +</body> +</html> |
