Add Todos, Projects, and Contexts web pages

- Created todos.html template showing user's todos with state badges
- Created projects.html template showing project cards in grid layout
- Created contexts.html template showing context cards
- Added ShowTodos, ShowProjects, ShowContexts handlers to web_handler.go
- Added routes for /todos, /projects, /contexts to main.go
- All pages show empty state when no data exists
- Navigation menu links now work without 404 errors

All pages are functional and display user-specific data from the database.
This commit is contained in:
Claude 2025-11-05 12:54:47 +00:00
parent f51dccb228
commit ca6e157a91
No known key found for this signature in database
5 changed files with 380 additions and 0 deletions

View file

@ -103,6 +103,9 @@ func setupRoutes(router *gin.Engine, cfg *config.Config) {
{
webProtected.GET("/", webHandler.ShowDashboard)
webProtected.GET("/dashboard", webHandler.ShowDashboard)
webProtected.GET("/todos", webHandler.ShowTodos)
webProtected.GET("/projects", webHandler.ShowProjects)
webProtected.GET("/contexts", webHandler.ShowContexts)
// Admin web routes
webAdmin := webProtected.Group("/admin")

View file

@ -0,0 +1,83 @@
{{define "content"}}
<style>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.context-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.context-card {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.25rem;
display: flex;
justify-content: space-between;
align-items: center;
transition: box-shadow 0.2s;
}
.context-card:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.context-name {
font-size: 1.1rem;
font-weight: 500;
color: var(--text-primary);
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
}
.badge-active {
background-color: var(--accent-color);
color: white;
}
.badge-hidden {
background-color: var(--warning-color);
color: white;
}
.badge-closed {
background-color: var(--text-secondary);
color: white;
}
</style>
<div class="page-header">
<h2>🏷️ Contexts</h2>
<button class="btn" onclick="alert('Create context - Coming soon!')"> New Context</button>
</div>
{{if .Contexts}}
<div class="context-grid">
{{range .Contexts}}
<div class="context-card">
<div class="context-name">{{.Name}}</div>
<span class="badge badge-{{.State}}">{{.State}}</span>
</div>
{{end}}
</div>
{{else}}
<div class="card">
<div style="text-align: center; padding: 3rem; color: var(--text-secondary);">
<div style="font-size: 3rem; margin-bottom: 1rem;">🏷️</div>
<p>No contexts yet. Create contexts to categorize where or how you'll complete todos!</p>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,109 @@
{{define "content"}}
<style>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.project-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.project-card {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem;
transition: box-shadow 0.2s;
}
.project-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.project-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 1rem;
}
.project-name {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-primary);
}
.project-description {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.project-stats {
display: flex;
gap: 1rem;
font-size: 0.85rem;
color: var(--text-secondary);
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
}
.badge-active {
background-color: var(--accent-color);
color: white;
}
.badge-completed {
background-color: var(--success-color);
color: white;
}
.badge-hidden {
background-color: var(--warning-color);
color: white;
}
</style>
<div class="page-header">
<h2>📁 Projects</h2>
<button class="btn" onclick="alert('Create project - Coming soon!')"> New Project</button>
</div>
{{if .Projects}}
<div class="project-grid">
{{range .Projects}}
<div class="project-card">
<div class="project-header">
<div class="project-name">{{.Name}}</div>
<span class="badge badge-{{.State}}">{{.State}}</span>
</div>
{{if .Description}}
<div class="project-description">{{.Description}}</div>
{{end}}
<div class="project-stats">
<span>📝 Todos</span>
<span>Created: {{.CreatedAt.Format "2006-01-02"}}</span>
</div>
</div>
{{end}}
</div>
{{else}}
<div class="card">
<div style="text-align: center; padding: 3rem; color: var(--text-secondary);">
<div style="font-size: 3rem; margin-bottom: 1rem;">📁</div>
<p>No projects yet. Create your first one to organize your todos!</p>
</div>
</div>
{{end}}
{{end}}

View file

@ -0,0 +1,120 @@
{{define "content"}}
<style>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.todo-list {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
}
.todo-item {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.todo-item:last-child {
border-bottom: none;
}
.todo-item:hover {
background-color: var(--bg-secondary);
}
.todo-content {
flex: 1;
}
.todo-description {
font-weight: 500;
margin-bottom: 0.25rem;
}
.todo-meta {
font-size: 0.85rem;
color: var(--text-secondary);
}
.todo-actions {
display: flex;
gap: 0.5rem;
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
margin-right: 0.5rem;
}
.badge-active {
background-color: var(--accent-color);
color: white;
}
.badge-completed {
background-color: var(--success-color);
color: white;
}
.badge-deferred {
background-color: var(--warning-color);
color: white;
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-secondary);
}
</style>
<div class="page-header">
<h2>📝 Todos</h2>
<button class="btn" onclick="alert('Create todo - Coming soon!')"> New Todo</button>
</div>
<div class="card">
{{if .Todos}}
<div class="todo-list">
{{range .Todos}}
<div class="todo-item">
<div class="todo-content">
<div class="todo-description">
<span class="badge badge-{{.State}}">{{.State}}</span>
{{.Description}}
</div>
<div class="todo-meta">
{{if .Context}}📁 {{.Context.Name}}{{end}}
{{if .Project}} • 🗂️ {{.Project.Name}}{{end}}
{{if .DueDate}} • 📅 Due: {{.DueDate.Format "2006-01-02"}}{{end}}
</div>
</div>
<div class="todo-actions">
{{if eq .State "active"}}
<button class="btn btn-sm btn-success" onclick="alert('Complete - Coming soon!')"></button>
{{end}}
<button class="btn btn-sm" onclick="alert('Edit - Coming soon!')">Edit</button>
</div>
</div>
{{end}}
</div>
{{else}}
<div class="empty-state">
<div style="font-size: 3rem; margin-bottom: 1rem;">📭</div>
<p>No todos yet. Create your first one to get started!</p>
</div>
{{end}}
</div>
{{end}}

View file

@ -183,3 +183,68 @@ func (h *WebHandler) HandleCreateUser(c *gin.Context) {
// Redirect back to users page with success message
c.Redirect(http.StatusFound, "/admin/users?success=User created successfully")
}
// ShowTodos displays the todos page
func (h *WebHandler) ShowTodos(c *gin.Context) {
user, _ := middleware.GetCurrentUser(c)
// Get user's todos
var todos []models.Todo
database.DB.
Preload("Context").
Preload("Project").
Where("user_id = ?", user.ID).
Order("created_at DESC").
Find(&todos)
data := gin.H{
"Title": "Todos",
"User": user,
"Todos": todos,
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.templates.ExecuteTemplate(c.Writer, "base.html", data)
}
// ShowProjects displays the projects page
func (h *WebHandler) ShowProjects(c *gin.Context) {
user, _ := middleware.GetCurrentUser(c)
// Get user's projects
var projects []models.Project
database.DB.
Where("user_id = ?", user.ID).
Order("created_at DESC").
Find(&projects)
data := gin.H{
"Title": "Projects",
"User": user,
"Projects": projects,
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.templates.ExecuteTemplate(c.Writer, "base.html", data)
}
// ShowContexts displays the contexts page
func (h *WebHandler) ShowContexts(c *gin.Context) {
user, _ := middleware.GetCurrentUser(c)
// Get user's contexts
var contexts []models.Context
database.DB.
Where("user_id = ?", user.ID).
Order("position ASC").
Find(&contexts)
data := gin.H{
"Title": "Contexts",
"User": user,
"Contexts": contexts,
}
c.Header("Content-Type", "text/html; charset=utf-8")
h.templates.ExecuteTemplate(c.Writer, "base.html", data)
}