This commit is contained in:
Flatlogic Bot 2025-12-22 20:10:27 +00:00
parent 8a19b38bb7
commit 0443b0e2cb
23 changed files with 432 additions and 6 deletions

Binary file not shown.

Binary file not shown.

View File

@ -17,6 +17,7 @@ class Activity(models.Model):
return self.name
class Mission(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
description = models.TextField()
trigger_mood_score = models.IntegerField()

View File

@ -17,6 +17,7 @@
{% load static %}
<link href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
</head>
@ -44,6 +45,12 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'core:dashboard' %}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'core:note_list' %}">My Notes</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'core:mission_list' %}">Missions</a>
</li>
{% endif %}
</ul>
<ul class="navbar-nav">

View File

@ -21,6 +21,17 @@
</div>
</div>
{% if ai_insight %}
<div class="card shadow-sm mb-4 bg-primary-soft">
<div class="card-body">
<h5 class="card-title fw-bold mb-3"><i class="fas fa-robot me-2"></i>AI-Powered Insights</h5>
<blockquote class="blockquote mb-0">
<p>{{ ai_insight }}</p>
</blockquote>
</div>
</div>
{% endif %}
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title fw-bold mb-3">Filter by Date</h5>
@ -53,6 +64,18 @@
{% endif %}
</div>
</div>
<div class="card p-4 shadow-sm mt-4">
<div class="card-body">
<h5 class="card-title fw-bold mb-3">Activity-Mood Correlation</h5>
{% if activity_mood_data != '[]' %}
<canvas id="activityMoodChart"></canvas>
{% else %}
<div class="text-center p-5">
<p class="lead">No activity data found for the selected period.</p>
</div>
{% endif %}
</div>
</div>
</div>
</section>
@ -108,6 +131,35 @@
}
});
}
const activityMoodData = JSON.parse('{{ activity_mood_data|safe }}');
if (activityMoodData.length > 0) {
const ctxActivity = document.getElementById('activityMoodChart').getContext('2d');
const activityNames = activityMoodData.map(d => d.activity);
const avgMoods = activityMoodData.map(d => d.avg_mood);
const activityMoodChart = new Chart(ctxActivity, {
type: 'bar',
data: {
labels: activityNames,
datasets: [{
label: 'Average Mood Score',
data: avgMoods,
backgroundColor: '#8884d8'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 5
}
}
}
});
}
});
</script>
{% endblock %}

View File

@ -9,6 +9,22 @@
</div>
{% include 'core/mood_modal.html' %}
<div class="container mt-5">
<h2 class="text-center mb-4">Mood History</h2>
<div class="row">
{% for entry in mood_entries %}
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ entry.date_time|date:"F d, Y" }}</h5>
<p class="card-text">Mood Score: {{ entry.mood_score }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
{% block extra_js %}

View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% block title %}Confirm Delete - PixelMinds{% endblock %}
{% block content %}
<section class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card shadow-sm border-danger">
<div class="card-body text-center p-5">
<h1 class="card-title text-danger mb-4">Delete Mission</h1>
<p>Are you sure you want to delete the mission: <strong>"{{ object.title }}"</strong>?</p>
<p class="text-muted">This action cannot be undone.</p>
<form method="post">
{% csrf_token %}
<div class="d-grid gap-2 d-md-flex justify-content-center mt-4">
<button type="submit" class="btn btn-danger">Yes, Delete</button>
<a href="{% url 'core:mission_list' %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block title %}{{ mission.title }} - Missions{% endblock %}
{% block content %}
<div class="container mt-5">
<h1 class="mb-4">{{ mission.title }}</h1>
<hr>
<div class="mt-4">
{{ mission.description|linebreaksbr }}
</div>
<div class="mt-5">
<a href="{% url 'core:mission_list' %}" class="btn btn-secondary">Back to Missions</a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends 'base.html' %}
{% block title %}{% if object %}Edit Mission{% else %}New Mission{% endif %} - PixelMinds{% endblock %}
{% block content %}
<section class="section">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-sm">
<div class="card-body p-5">
<h1 class="card-title text-center mb-4">{% if object %}Edit Mission{% else %}Create a New Mission{% endif %}</h1>
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<div class="d-grid gap-2 d-md-flex justify-content-md-start">
<button type="submit" class="btn btn-primary">{% if object %}Save Changes{% else %}Create Mission{% endif %}</button>
<a href="{% url 'core:mission_list' %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends 'base.html' %}
{% block title %}My Missions - PixelMinds{% endblock %}
{% block content %}
<section class="section">
<div class="container">
<div class="row mb-4 align-items-center">
<div class="col-md-8">
<h1 class="section-title mb-0">My Missions</h1>
<p class="lead">Create and manage your personal missions.</p>
</div>
<div class="col-md-4 text-md-end">
<a href="{% url 'core:mission_create' %}" class="btn btn-primary">+ Create a New Mission</a>
</div>
</div>
{% if missions %}
<div class="list-group">
{% for mission in missions %}
<div class="list-group-item list-group-item-action flex-column align-items-start">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">
<a href="{% url 'core:mission_detail' mission.pk %}">{{ mission.title }}</a>
</h5>
<small>{{ mission.created_at|date:"d M Y" }}</small>
</div>
<p class="mb-1">{{ mission.description|truncatechars:150 }}</p>
<div class="mt-3">
<a href="{% url 'core:mission_update' mission.pk %}" class="btn btn-sm btn-outline-primary me-2">Edit</a>
<a href="{% url 'core:mission_delete' mission.pk %}" class="btn btn-sm btn-outline-danger">Delete</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center p-5">
<p class="lead">You haven't created any missions yet.</p>
<p class="text-muted">Get started by creating your first mission.</p>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% block title %}Confirm Delete - My Notes{% endblock %}
{% block content %}
<div class="container mt-5">
<h1>Confirm Delete</h1>
<p>Are you sure you want to delete the note "<strong>{{ note.title }}</strong>"?</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Yes, Delete</button>
<a href="{% url 'core:note_detail' note.pk %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block title %}{{ note.title }} - My Notes{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>{{ note.title }}</h1>
<div>
<a href="{% url 'core:note_update' note.pk %}" class="btn btn-outline-secondary">Edit</a>
<a href="{% url 'core:note_delete' note.pk %}" class="btn btn-outline-danger">Delete</a>
</div>
</div>
<p><small class="text-muted">Last updated: {{ note.updated_at|date:"F d, Y H:i" }}</small></p>
<hr>
<div class="mt-4">
{{ note.content|linebreaksbr }}
</div>
<div class="mt-5">
<a href="{% url 'core:note_list' %}" class="btn btn-secondary">Back to Notes</a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% block title %}{% if form.instance.pk %}Edit Note{% else %}New Note{% endif %} - PixelMinds{% endblock %}
{% block content %}
<div class="container mt-5">
<h1>{% if form.instance.pk %}Edit Note{% else %}New Note{% endif %}</h1>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="id_title" class="form-label">Title</label>
<input type="text" name="title" id="id_title" class="form-control" value="{{ form.title.value|default:'' }}" required>
</div>
<div class="mb-3">
<label for="id_content" class="form-label">Content</label>
<textarea name="content" id="id_content" class="form-control" rows="10" required>{{ form.content.value|default:'' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a href="{% if form.instance.pk %}{% url 'core:note_detail' form.instance.pk %}{% else %}{% url 'core:note_list' %}{% endif %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends 'base.html' %}
{% block title %}My Notes - PixelMinds{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>My Notes</h1>
<a href="{% url 'core:note_create' %}" class="btn btn-primary">New Note</a>
</div>
<div class="row">
{% for note in notes %}
<div class="col-md-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title"><a href="{% url 'core:note_detail' note.pk %}">{{ note.title }}</a></h5>
<p class="card-text">{{ note.content|truncatechars:100 }}</p>
<p class="card-text"><small class="text-muted">Last updated: {{ note.updated_at|date:"F d, Y" }}</small></p>
<a href="{% url 'core:note_update' note.pk %}" class="btn btn-sm btn-outline-secondary">Edit</a>
<a href="{% url 'core:note_delete' note.pk %}" class="btn btn-sm btn-outline-danger">Delete</a>
</div>
</div>
</div>
{% empty %}
<div class="col">
<p>You don't have any notes yet. <a href="{% url 'core:note_create' %}">Create your first note!</a></p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -1,6 +1,10 @@
from django.urls import path
from .views import home, create_mood_entry, dashboard, AboutView, ContactView, PrivacyPolicyView, SignUpView, save_mood_entry
from .views import (
home, create_mood_entry, dashboard, AboutView, ContactView, PrivacyPolicyView, SignUpView, save_mood_entry,
NoteListView, NoteDetailView, NoteCreateView, NoteUpdateView, NoteDeleteView,
MissionListView, MissionDetailView, MissionCreateView, MissionUpdateView, MissionDeleteView
)
app_name = 'core'
@ -13,4 +17,14 @@ urlpatterns = [
path('about/', AboutView.as_view(), name='about'),
path('contact/', ContactView.as_view(), name='contact'),
path('privacy-policy/', PrivacyPolicyView.as_view(), name='privacy_policy'),
path('notes/', NoteListView.as_view(), name='note_list'),
path('note/<int:pk>/', NoteDetailView.as_view(), name='note_detail'),
path('note/new/', NoteCreateView.as_view(), name='note_create'),
path('note/<int:pk>/edit/', NoteUpdateView.as_view(), name='note_update'),
path('note/<int:pk>/delete/', NoteDeleteView.as_view(), name='note_delete'),
path('missions/', MissionListView.as_view(), name='mission_list'),
path('mission/new/', MissionCreateView.as_view(), name='mission_create'),
path('mission/<int:pk>/', MissionDetailView.as_view(), name='mission_detail'),
path('mission/<int:pk>/edit/', MissionUpdateView.as_view(), name='mission_update'),
path('mission/<int:pk>/delete/', MissionDeleteView.as_view(), name='mission_delete'),
]

View File

@ -7,14 +7,101 @@ from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.views.generic import TemplateView, FormView, CreateView
from django_pandas.io import read_frame
from django.contrib import messages
from django.views.generic import TemplateView, FormView, CreateView, ListView, DetailView, UpdateView, DeleteView
from django.db import models
from .forms import MoodEntryForm, ContactForm, SignUpForm, DateRangeFilterForm
from .models import MoodEntry, Activity
from .models import MoodEntry, Activity, Note, Mission
from .mail import send_contact_message
from ai.local_ai_api import LocalAIApi
class MissionListView(LoginRequiredMixin, ListView):
model = Mission
template_name = 'core/mission_list.html'
context_object_name = 'missions'
def get_queryset(self):
return Mission.objects.filter(user=self.request.user).order_by('-is_active', '-created_at')
class MissionDetailView(LoginRequiredMixin, DetailView):
model = Mission
template_name = 'core/mission_detail.html'
context_object_name = 'mission'
def get_queryset(self):
return Mission.objects.filter(user=self.request.user)
class MissionCreateView(LoginRequiredMixin, CreateView):
model = Mission
template_name = 'core/mission_form.html'
fields = ['title', 'description', 'is_active']
success_url = reverse_lazy('core:mission_list')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class MissionUpdateView(LoginRequiredMixin, UpdateView):
model = Mission
template_name = 'core/mission_form.html'
fields = ['title', 'description', 'is_active']
success_url = reverse_lazy('core:mission_list')
def get_queryset(self):
return Mission.objects.filter(user=self.request.user)
class MissionDeleteView(LoginRequiredMixin, DeleteView):
model = Mission
template_name = 'core/mission_confirm_delete.html'
success_url = reverse_lazy('core:mission_list')
def get_queryset(self):
return Mission.objects.filter(user=self.request.user)
class NoteListView(LoginRequiredMixin, ListView):
model = Note
template_name = 'core/note_list.html'
context_object_name = 'notes'
def get_queryset(self):
return Note.objects.filter(user=self.request.user).order_by('-updated_at')
class NoteDetailView(LoginRequiredMixin, DetailView):
model = Note
template_name = 'core/note_detail.html'
context_object_name = 'note'
class NoteCreateView(LoginRequiredMixin, CreateView):
model = Note
template_name = 'core/note_form.html'
fields = ['title', 'content']
success_url = reverse_lazy('core:note_list')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class NoteUpdateView(LoginRequiredMixin, UpdateView):
model = Note
template_name = 'core/note_form.html'
fields = ['title', 'content']
success_url = reverse_lazy('core:note_list')
class NoteDeleteView(LoginRequiredMixin, DeleteView):
model = Note
template_name = 'core/note_confirm_delete.html'
success_url = reverse_lazy('core:note_list')
from django_pandas.io import read_frame
from django.contrib import messages
class SignUpView(CreateView):
@ -82,7 +169,39 @@ def dashboard(request):
if not df.empty:
df['date_time'] = df['date_time'].dt.strftime('%Y-%m-%d')
chart_data = df.to_json(orient='records')
return render(request, 'core/dashboard.html', {'chart_data': chart_data, 'form': form})
# Activity-Mood Correlation
activity_mood_data = []
activities = Activity.objects.all()
for activity in activities:
entries_with_activity = mood_entries.filter(activities=activity)
if entries_with_activity.exists():
avg_mood = entries_with_activity.aggregate(avg_mood=models.Avg('mood_score'))['avg_mood']
activity_mood_data.append({
'activity': activity.name,
'avg_mood': round(avg_mood, 2)
})
# AI-powered insights
ai_insight = ''
if mood_entries.exists():
latest_mood_data = list(mood_entries.order_by('-date_time')[:7].values('date_time', 'mood_score'))
prompt = f"Based on the following recent mood entries, provide a brief summary and one actionable recommendation. Mood data: {json.dumps(latest_mood_data, default=str)}"
ai_response = LocalAIApi.create_response({
"input": [
{"role": "system", "content": "You are a mental wellness assistant. Provide concise, actionable advice."},
{"role": "user", "content": prompt},
]
})
if ai_response.get("success"):
ai_insight = LocalAIApi.extract_text(ai_response)
return render(request, 'core/dashboard.html', {
'chart_data': chart_data,
'form': form,
'activity_mood_data': json.dumps(activity_mood_data),
'ai_insight': ai_insight,
})
@login_required