feat: Modularizes dashboard structure

Introduces DashboardSection class to encapsulate dashboard logic,
enabling easier extension and customization. Adds separate mood
and dreams dashboard templates to streamline content rendering.

Refactors main dashboard template to dynamically render content
from enabled modules using a new template tag system, improving
flexibility and maintainability.
This commit is contained in:
Kumi 2024-11-18 10:49:04 +01:00
parent 3c3cfc38ba
commit 5c7586bb9a
Signed by: kumi
GPG key ID: ECBCC9082395383F
7 changed files with 254 additions and 185 deletions

View file

@ -1,7 +1,9 @@
from frontend.classes import NavSection, NavItem from frontend.classes import NavSection, NavItem, DashboardSection
from django.urls import reverse_lazy from django.urls import reverse_lazy
# Sidebar navigation items
dreams_section = NavSection("Dreams") dreams_section = NavSection("Dreams")
dreams_items = { dreams_items = {
@ -14,3 +16,9 @@ for _, item in dreams_items.items():
dreams_section.add_item(item) dreams_section.add_item(item)
NAV_SECTIONS = [dreams_section] NAV_SECTIONS = [dreams_section]
# Dashboard sections
dreams_section = DashboardSection("Dreams", "dreams/dashboard_section.html")
DASHBOARD_SECTIONS = [dreams_section]

View file

@ -0,0 +1,78 @@
{% load dream_stats %}
<!-- Dream Stats -->
<div class="row">
<!-- Total Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Dream count (total)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% total_dreams %}</div>
</div>
<div class="col-auto">
<i class="fas fa-book fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Weekly Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Dreams (weekly)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% weekly_dreams %}</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Special Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Special Dreams (lucid / wet)</div>
<div class="row no-gutters align-items-center">
<div class="col-auto">
{% special_dreams_weekly as weekly %}
<div class="h5 mb-0 mr-3 font-weight-bold text-gray-800">
{{ weekly.0 }} / {{ weekly.1 }} <sub><i>(weekly)</i></sub>
</div>
</div>
</div>
</div>
<div class="col-auto">
<i class="{{ moodobj.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Most common theme -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Most Common Theme (weekly)</div>
{% most_common_theme_weekly as theme %}
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ theme.0 }} <sub><i>({{ theme.1 }})</i></sub>
</div>
</div>
<div class="col-auto">
<i class="fas fa-bed fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,3 +1,6 @@
from django.template.loader import render_to_string
class NavSection: class NavSection:
def __init__(self, name, order=100): def __init__(self, name, order=100):
self.name = name self.name = name
@ -16,18 +19,23 @@ class NavSection:
self.items.sort(key=lambda x: x.order) self.items.sort(key=lambda x: x.order)
for item in self.items: for item in self.items:
html += """ html += (
"""
<!-- Nav Item --> <!-- Nav Item -->
<li class="nav-item""" + (" active" if item.name == active else "") + f""""> <li class="nav-item"""
+ (" active" if item.name == active else "")
+ f"""">
<a class="nav-link" href="{item.url}"> <a class="nav-link" href="{item.url}">
<i class="{item.icon}"></i> <i class="{item.icon}"></i>
<span>{item.name}</span> <span>{item.name}</span>
</a> </a>
</li> </li>
""" """
)
return html return html
class NavItem: class NavItem:
def __init__(self, name, url, icon="fas fa-fw fa-smile", title=None, order=100): def __init__(self, name, url, icon="fas fa-fw fa-smile", title=None, order=100):
self.name = name self.name = name
@ -35,3 +43,13 @@ class NavItem:
self.icon = icon self.icon = icon
self.title = title or name self.title = title or name
self.order = order self.order = order
class DashboardSection:
def __init__(self, name, template, context=None):
self.name = name
self.template = template
self.context = context or {}
def get_html(self, request):
return render_to_string(self.template, self.context, request)

View file

@ -1,178 +1,6 @@
{% extends "frontend/base.html" %} {% extends "frontend/base.html" %}
{% load mood_stats %} {% load dashboard %}
{% load dream_stats %}
{% block "content" %} {% block "content" %}
<h2>Moods</h2> {% dashboard as dashboard %}
<!-- Mood calendar heatmap --> {{ dashboard | safe }}
<div class="row"> {% endblock "content" %}
<div class="col-xl-12 mb-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Mood Calendar</h6>
</div>
<div class="card-body">
<div id="mood-count-heatmap"></div>
</div>
</div>
</div>
</div>
<!-- Mood cards -->
<div class="row">
<!-- Status count -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Status count (total)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% total_moods %}</div>
</div>
<div class="col-auto">
<i class="fas fa-book fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Current Streak Length -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Current Streak Length</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{% current_streak %} <sub>days</sub>
</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Average mood -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Average mood (weekly)</div>
<div class="row no-gutters align-items-center">
<div class="col-auto">
{% average_mood_weekly as mood %} {% closest_mood mood as moodobj %}
<div class="h5 mb-0 mr-3 font-weight-bold text-gray-800">
{{ moodobj }} <sub><i>({{ mood|floatformat:2 }})</i></sub>
</div>
</div>
</div>
</div>
<div class="col-auto">
<i class="{{ moodobj.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Most common activity -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Most Common Activity (weekly)</div>
{% most_common_activity_weekly as activity %}
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ activity.0 }}<sub><i>({{ activity.1 }})</i></sub>
</div>
</div>
<div class="col-auto">
<i class="{{ activity.0.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section>
<h2>Dreams</h2>
<!-- Dream Stats -->
<div class="row">
<!-- Total Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Dream count (total)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% total_dreams %}</div>
</div>
<div class="col-auto">
<i class="fas fa-book fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Weekly Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Dreams (weekly)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% weekly_dreams %}</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Special Dreams -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Special Dreams (lucid / wet)</div>
<div class="row no-gutters align-items-center">
<div class="col-auto">
{% special_dreams_weekly as weekly %}
<div class="h5 mb-0 mr-3 font-weight-bold text-gray-800">
{{ weekly.0 }} / {{ weekly.1 }} <sub><i>(weekly)</i></sub>
</div>
</div>
</div>
</div>
<div class="col-auto">
<i class="{{ moodobj.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Most common theme -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Most Common Theme (weekly)</div>
{% most_common_theme_weekly as theme %}
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ theme.0 }} <sub><i>({{ theme.1 }})</i></sub>
</div>
</div>
<div class="col-auto">
<i class="fas fa-bed fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,33 @@
from django import template
from django.conf import settings
from importlib import import_module
register = template.Library()
@register.simple_tag(takes_context=True)
def dashboard(context):
sections = []
for module in settings.CORE_MODULES + settings.ENABLED_MODULES:
try:
features = import_module(f"{module}.features")
try:
sections += features.DASHBOARD_SECTIONS
except Exception:
pass
except Exception:
pass
dashboard_html = ""
for section in sections:
dashboard_html += f"<h2>{section.name}</h2>"
dashboard_html += section.get_html(context["request"])
if section != sections[-1]:
dashboard_html += '<hr class="dashboard-divider">'
return dashboard_html

View file

@ -1,18 +1,28 @@
from frontend.classes import NavSection, NavItem from frontend.classes import NavSection, NavItem, DashboardSection
from django.urls import reverse_lazy from django.urls import reverse_lazy
# Sidebar navigation items
mood_section = NavSection("Mood") mood_section = NavSection("Mood")
mood_items = { mood_items = {
"mood_status_list": NavItem("Status List", reverse_lazy("mood:status_list")), "mood_status_list": NavItem("Status List", reverse_lazy("mood:status_list")),
"mood_activity_list": NavItem("Activities", reverse_lazy("mood:activity_list")), "mood_activity_list": NavItem("Activities", reverse_lazy("mood:activity_list")),
"mood_mood_list": NavItem("Moods", reverse_lazy("mood:mood_list")), "mood_mood_list": NavItem("Moods", reverse_lazy("mood:mood_list")),
"mood_notification_list": NavItem("Notifications", reverse_lazy("mood:notification_list")), "mood_notification_list": NavItem(
"mood_statistics": NavItem("Statistics", reverse_lazy("mood:statistics")) "Notifications", reverse_lazy("mood:notification_list")
),
"mood_statistics": NavItem("Statistics", reverse_lazy("mood:statistics")),
} }
for _, item in mood_items.items(): for _, item in mood_items.items():
mood_section.add_item(item) mood_section.add_item(item)
NAV_SECTIONS = [mood_section] NAV_SECTIONS = [mood_section]
# Dashboard sections
mood_section = DashboardSection("Moods", "mood/dashboard_section.html")
DASHBOARD_SECTIONS = [mood_section]

View file

@ -0,0 +1,94 @@
{% load mood_stats %}
<!-- Mood calendar heatmap -->
<div class="row">
<div class="col-xl-12 mb-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Mood Calendar</h6>
</div>
<div class="card-body">
<div id="mood-count-heatmap"></div>
</div>
</div>
</div>
</div>
<!-- Mood cards -->
<div class="row">
<!-- Status count -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Status count (total)</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">{% total_moods %}</div>
</div>
<div class="col-auto">
<i class="fas fa-book fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Current Streak Length -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Current Streak Length</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{% current_streak %} <sub>days</sub>
</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Average mood -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Average mood (weekly)</div>
<div class="row no-gutters align-items-center">
<div class="col-auto">
{% average_mood_weekly as mood %} {% closest_mood mood as moodobj %}
<div class="h5 mb-0 mr-3 font-weight-bold text-gray-800">
{{ moodobj }} <sub><i>({{ mood|floatformat:2 }})</i></sub>
</div>
</div>
</div>
</div>
<div class="col-auto">
<i class="{{ moodobj.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Most common activity -->
<div class="col-xl-3 col-md-6 mb-4">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Most Common Activity (weekly)</div>
{% most_common_activity_weekly as activity %}
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ activity.0 }}<sub><i>({{ activity.1 }})</i></sub>
</div>
</div>
<div class="col-auto">
<i class="{{ activity.0.icon }} fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>