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:
3 files changed
+213
No files matched your search
@@ -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",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ urlpatterns = [
|
||||
path("clients/", include("src.api.clients.urls")),
|
||||
path("projects/", include("src.api.projects.urls")),
|
||||
path("finance/", include("src.api.finance.urls")),
|
||||
path("productivity/", include("src.api.productivity.urls")),
|
||||
]
|
||||
Reference in new issue
Block a user