mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 15:20:13 +01:00
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:
parent
f51dccb228
commit
ca6e157a91
5 changed files with 380 additions and 0 deletions
|
|
@ -103,6 +103,9 @@ func setupRoutes(router *gin.Engine, cfg *config.Config) {
|
||||||
{
|
{
|
||||||
webProtected.GET("/", webHandler.ShowDashboard)
|
webProtected.GET("/", webHandler.ShowDashboard)
|
||||||
webProtected.GET("/dashboard", 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
|
// Admin web routes
|
||||||
webAdmin := webProtected.Group("/admin")
|
webAdmin := webProtected.Group("/admin")
|
||||||
|
|
|
||||||
83
cmd/tracks/web/templates/contexts.html
Normal file
83
cmd/tracks/web/templates/contexts.html
Normal 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}}
|
||||||
109
cmd/tracks/web/templates/projects.html
Normal file
109
cmd/tracks/web/templates/projects.html
Normal 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}}
|
||||||
120
cmd/tracks/web/templates/todos.html
Normal file
120
cmd/tracks/web/templates/todos.html
Normal 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}}
|
||||||
|
|
@ -183,3 +183,68 @@ func (h *WebHandler) HandleCreateUser(c *gin.Context) {
|
||||||
// Redirect back to users page with success message
|
// Redirect back to users page with success message
|
||||||
c.Redirect(http.StatusFound, "/admin/users?success=User created successfully")
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue