feat(src): add productivity API endpoint

- **New Features**:
  - Added new URL route for productivity API at `/productivity/`
  - Integrated `src.api.productivity.urls` module into the main URL configuration
```
This commit is contained in:
sankalp committed 2025-12-18 21:16:40 +05:45
1 parent 3643e34304
commit 32be95257e
3 files changed
+213

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.productivity_dashboard, name="productivity_dashboard"),
# Notes
path("notes/", views.note_list, name="note_list"),
path("notes/create/", views.note_create, name="note_create"),
path("notes/<int:pk>/edit/", views.note_edit, name="note_edit"),
path("notes/<int:pk>/delete/", views.note_delete, name="note_delete"),
# Time entries
path("time-entries/", views.timeentry_list, name="timeentry_list"),
path("time-entries/create/", views.timeentry_create, name="timeentry_create"),
path("time-entries/<int:pk>/edit/", views.timeentry_edit, name="timeentry_edit"),
path(
"time-entries/<int:pk>/delete/",
views.timeentry_delete,
name="timeentry_delete",
),
]
from django.urls import path
from . import views
urlpatterns = [
# Dashboard
path("", views.productivity_dashboard, name="productivity_dashboard"),
# Notes
path("notes/", views.note_list, name="note_list"),
path("notes/create/", views.note_create, name="note_create"),
path("notes/<int:pk>/edit/", views.note_edit, name="note_edit"),
path("notes/<int:pk>/delete/", views.note_delete, name="note_delete"),
# Time entries
path("time-entries/", views.timeentry_list, name="timeentry_list"),
path("time-entries/create/", views.timeentry_create, name="timeentry_create"),
path("time-entries/<int:pk>/edit/", views.timeentry_edit, name="timeentry_edit"),
path(
"time-entries/<int:pk>/delete/",
views.timeentry_delete,
name="timeentry_delete",
),
]
+166
View File
@@ -0,0 +1,166 @@
from datetime import datetime
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_http_methods
from src.models.productivity import Note, TimeEntry
from src.models.projects import Project, Task
def productivity_dashboard(request):
recent_notes = Note.objects.all()[:5]
recent_time_entries = (
TimeEntry.objects.select_related("project", "task").all()[:5]
)
total_tracked_hours = (
sum(
(entry.duration.total_seconds() for entry in recent_time_entries if entry.duration),
0,
)
/ 3600
if recent_time_entries
else 0
)
return render(
request,
"productivity/dashboard.html",
{
"recent_notes": recent_notes,
"recent_time_entries": recent_time_entries,
"total_tracked_hours": total_tracked_hours,
},
)
def note_list(request):
notes = Note.objects.all()
return render(request, "productivity/note_list.html", {"notes": notes})
def note_create(request):
if request.method == "POST":
title = request.POST.get("title", "").strip()
content = request.POST.get("content", "").strip()
if title and content:
Note.objects.create(title=title, content=content)
return redirect("note_list")
return render(request, "productivity/note_form.html")
def note_edit(request, pk):
note = get_object_or_404(Note, pk=pk)
if request.method == "POST":
title = request.POST.get("title", "").strip()
content = request.POST.get("content", "").strip()
if title and content:
note.title = title
note.content = content
note.save()
return redirect("note_list")
return render(request, "productivity/note_form.html", {"note": note})
@require_http_methods(["DELETE", "POST"])
def note_delete(request, pk):
note = get_object_or_404(Note, pk=pk)
note.delete()
return redirect("note_list")
def _parse_datetime(value: str):
if not value:
return None
# Expect HTML datetime-local format: "YYYY-MM-DDTHH:MM"
try:
return datetime.fromisoformat(value)
except ValueError:
return None
def timeentry_list(request):
time_entries = TimeEntry.objects.select_related("project", "task").all()
return render(
request,
"productivity/timeentry_list.html",
{"time_entries": time_entries},
)
def timeentry_create(request):
projects = Project.objects.all()
tasks = Task.objects.all()
if request.method == "POST":
project_id = request.POST.get("project")
task_id = request.POST.get("task")
description = request.POST.get("description", "").strip()
start_time_raw = request.POST.get("start_time", "").strip()
end_time_raw = request.POST.get("end_time", "").strip()
start_time = _parse_datetime(start_time_raw)
end_time = _parse_datetime(end_time_raw)
if description and start_time:
project = Project.objects.filter(id=project_id).first() if project_id else None
task = Task.objects.filter(id=task_id).first() if task_id else None
TimeEntry.objects.create(
project=project,
task=task,
description=description,
start_time=start_time,
end_time=end_time,
)
return redirect("timeentry_list")
return render(
request,
"productivity/timeentry_form.html",
{"projects": projects, "tasks": tasks},
)
def timeentry_edit(request, pk):
time_entry = get_object_or_404(TimeEntry, pk=pk)
projects = Project.objects.all()
tasks = Task.objects.all()
if request.method == "POST":
project_id = request.POST.get("project")
task_id = request.POST.get("task")
description = request.POST.get("description", "").strip()
start_time_raw = request.POST.get("start_time", "").strip()
end_time_raw = request.POST.get("end_time", "").strip()
start_time = _parse_datetime(start_time_raw)
end_time = _parse_datetime(end_time_raw)
if description and start_time:
time_entry.project = Project.objects.filter(id=project_id).first() if project_id else None
time_entry.task = Task.objects.filter(id=task_id).first() if task_id else None
time_entry.description = description
time_entry.start_time = start_time
time_entry.end_time = end_time
time_entry.save()
return redirect("timeentry_list")
context = {
"time_entry": time_entry,
"projects": projects,
"tasks": tasks,
}
return render(request, "productivity/timeentry_form.html", context)
@require_http_methods(["DELETE", "POST"])
def timeentry_delete(request, pk):
time_entry = get_object_or_404(TimeEntry, pk=pk)
time_entry.delete()
return redirect("timeentry_list")
+1
View File
@@ -11,4 +11,5 @@ urlpatterns = [
path("clients/", include("src.api.clients.urls")), path("clients/", include("src.api.clients.urls")),
path("projects/", include("src.api.projects.urls")), path("projects/", include("src.api.projects.urls")),
path("finance/", include("src.api.finance.urls")), path("finance/", include("src.api.finance.urls")),
path("productivity/", include("src.api.productivity.urls")),
] ]