diff --git a/nummi/plot/__init__.py b/nummi/api/__init__.py
similarity index 100%
rename from nummi/plot/__init__.py
rename to nummi/api/__init__.py
diff --git a/nummi/api/admin.py b/nummi/api/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/nummi/api/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/nummi/plot/apps.py b/nummi/api/apps.py
similarity index 66%
rename from nummi/plot/apps.py
rename to nummi/api/apps.py
index 1f7a601..878e7d5 100644
--- a/nummi/plot/apps.py
+++ b/nummi/api/apps.py
@@ -1,6 +1,6 @@
from django.apps import AppConfig
-class PlotConfig(AppConfig):
+class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
- name = "plot"
+ name = "api"
diff --git a/nummi/plot/migrations/__init__.py b/nummi/api/migrations/__init__.py
similarity index 100%
rename from nummi/plot/migrations/__init__.py
rename to nummi/api/migrations/__init__.py
diff --git a/nummi/api/models.py b/nummi/api/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/nummi/api/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/nummi/api/tests.py b/nummi/api/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/nummi/api/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/nummi/api/urls.py b/nummi/api/urls.py
new file mode 100644
index 0000000..cb74a80
--- /dev/null
+++ b/nummi/api/urls.py
@@ -0,0 +1,11 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+ path("transactions", views.TransactionListView.as_view(), name="transactions"),
+ path("categories", views.CategoryListView.as_view(), name="categories"),
+ path("accounts", views.AccountListView.as_view(), name="accounts"),
+ path("snapshots", views.SnapshotListView.as_view(), name="snapshots"),
+ path("history", views.HistoryView.as_view(), name="history"),
+]
diff --git a/nummi/api/views.py b/nummi/api/views.py
new file mode 100644
index 0000000..31024eb
--- /dev/null
+++ b/nummi/api/views.py
@@ -0,0 +1,61 @@
+from django.db.models import Sum, FloatField, Q
+from django.db.models.functions import TruncMonth
+from django.http import JsonResponse
+from django.views import View
+from django.views.generic.list import MultipleObjectMixin
+
+from main.models import Account, Category, Snapshot, Transaction
+from main.views import UserMixin
+
+
+class TransactionListView(UserMixin, MultipleObjectMixin, View):
+ model = Transaction
+
+ def get(self, request, *args, **kwargs):
+ return JsonResponse({"transactions": list(self.get_queryset().values())})
+
+
+class CategoryListView(UserMixin, MultipleObjectMixin, View):
+ model = Category
+
+ def get(self, request, *args, **kwargs):
+ return JsonResponse({"categories": list(self.get_queryset().values())})
+
+
+class AccountListView(UserMixin, MultipleObjectMixin, View):
+ model = Account
+
+ def get(self, request, *args, **kwargs):
+ return JsonResponse({"accounts": list(self.get_queryset().values())})
+
+
+class SnapshotListView(UserMixin, MultipleObjectMixin, View):
+ model = Snapshot
+
+ def get(self, request, *args, **kwargs):
+ return JsonResponse({"snapshots": list(self.get_queryset().values())})
+
+
+class HistoryView(UserMixin, MultipleObjectMixin, View):
+ model = Transaction
+
+ def get(self, request, *args, **kwargs):
+ return JsonResponse(
+ {
+ "data": list(
+ self.get_queryset()
+ .filter(category__budget=True)
+ .values(month=TruncMonth("date"))
+ .annotate(
+ sum_p=Sum(
+ "value", output_field=FloatField(), filter=Q(value__gt=0)
+ ),
+ sum_m=Sum(
+ "value", output_field=FloatField(), filter=Q(value__lt=0)
+ ),
+ sum=Sum("value", output_field=FloatField()),
+ )
+ .order_by("month")
+ )
+ }
+ )
diff --git a/nummi/main/forms.py b/nummi/main/forms.py
index 146432a..4ae8339 100644
--- a/nummi/main/forms.py
+++ b/nummi/main/forms.py
@@ -28,6 +28,7 @@ class CategoryForm(NummiForm):
fields = [
"name",
"icon",
+ "budget",
]
diff --git a/nummi/main/static/main/js/history_plot.js b/nummi/main/static/main/js/history_plot.js
new file mode 100644
index 0000000..ddec5bd
--- /dev/null
+++ b/nummi/main/static/main/js/history_plot.js
@@ -0,0 +1,31 @@
+import {json} from "https://cdn.skypack.dev/d3-fetch@3";
+import {timeParse} from "https://cdn.skypack.dev/d3-time-format@4";
+import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm";
+
+const history = await json("/api/history");
+const parseDate = timeParse("%Y-%m-%d");
+console.log(history);
+history["data"].forEach((d) => {
+ d.month = parseDate(d.month);
+});
+const data = history["data"];
+
+let history_plot = Plot.plot({
+ x: {
+ round: true,
+ label: "Month",
+ grid: true,
+ },
+ y: {
+ grid: true,
+ },
+ marks: [
+ Plot.areaY(data, {x: "month", y: "sum_p", interval: "month", curve: "bump-x", fill: "#aaccff"}),
+ Plot.areaY(data, {x: "month", y: "sum_m", interval: "month", curve: "bump-x", fill: "#ffccaa"}),
+ Plot.ruleY([0]),
+ Plot.lineY(data, {x: "month", y: "sum", interval: "month", curve: "bump-x", marker: "circle", strokeWidth: 2}),
+ ],
+ insetTop: 20,
+ insetBottom: 20,
+});
+document.querySelector("#history_plot").append(history_plot);
diff --git a/nummi/main/templates/main/category_form.html b/nummi/main/templates/main/category_form.html
index b2c04cf..a3b327a 100644
--- a/nummi/main/templates/main/category_form.html
+++ b/nummi/main/templates/main/category_form.html
@@ -20,8 +20,6 @@
{{ form }}
{% if form.instance.transactions %}
-