Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0443b0e2cb | ||
|
|
8a19b38bb7 | ||
|
|
a22cc53e72 | ||
|
|
0f85f7204c | ||
|
|
a5f160a740 | ||
|
|
a3acd1329c | ||
|
|
4c0ed8bcd5 | ||
|
|
05303880c9 |
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -18,10 +18,21 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from core.views import SignUpView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("", include("core.urls")),
|
path("", include("core.urls")),
|
||||||
|
path('signup/', SignUpView.as_view(), name='signup'),
|
||||||
|
path('login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
|
||||||
|
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||||
|
path('password_change/', auth_views.PasswordChangeView.as_view(template_name='registration/password_change.html'), name='password_change'),
|
||||||
|
path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='registration/password_change_done.html'), name='password_change_done'),
|
||||||
|
path('password_reset/', auth_views.PasswordResetView.as_view(template_name='registration/password_reset.html'), name='password_reset'),
|
||||||
|
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='registration/password_reset_done.html'), name='password_reset_done'),
|
||||||
|
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(template_name='registration/password_reset_confirm.html'), name='password_reset_confirm'),
|
||||||
|
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='registration/password_reset_complete.html'), name='password_reset_complete'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|||||||
Binary file not shown.
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/mail.cpython-311.pyc
Normal file
BIN
core/__pycache__/mail.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import MoodEntry, Activity, Mission
|
||||||
|
|
||||||
# Register your models here.
|
admin.site.register(MoodEntry)
|
||||||
|
admin.site.register(Activity)
|
||||||
|
admin.site.register(Mission)
|
||||||
35
core/forms.py
Normal file
35
core/forms.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .models import MoodEntry, Activity
|
||||||
|
|
||||||
|
class DateRangeFilterForm(forms.Form):
|
||||||
|
start_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}))
|
||||||
|
end_date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}))
|
||||||
|
|
||||||
|
class SignUpForm(UserCreationForm):
|
||||||
|
email = forms.EmailField(max_length=254, help_text='Required. Inform a valid email address.')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'email')
|
||||||
|
|
||||||
|
class MoodEntryForm(forms.ModelForm):
|
||||||
|
activities = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Activity.objects.all(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = MoodEntry
|
||||||
|
fields = ['mood_score', 'note', 'activities']
|
||||||
|
widgets = {
|
||||||
|
'mood_score': forms.NumberInput(attrs={'class': 'form-control', 'min': 1, 'max': 10}),
|
||||||
|
'note': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactForm(forms.Form):
|
||||||
|
name = forms.CharField(max_length=100, widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||||
|
email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control'}))
|
||||||
|
message = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 5}))
|
||||||
25
core/mail.py
Normal file
25
core/mail.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
def send_contact_message(name, email, message):
|
||||||
|
"""Sends an email from the contact form."""
|
||||||
|
try:
|
||||||
|
subject = f'Contact Form Message from {name}'
|
||||||
|
message_body = f'You have received a new message from your website contact form.\n\n' \
|
||||||
|
f'Here are the details:\n\n' \
|
||||||
|
f'Name: {name}\n' \
|
||||||
|
f'Email: {email}\n\n' \
|
||||||
|
f'Message:\n{message}\n'
|
||||||
|
|
||||||
|
send_mail(
|
||||||
|
subject,
|
||||||
|
message_body,
|
||||||
|
settings.DEFAULT_FROM_EMAIL, # from
|
||||||
|
[settings.CONTACT_EMAIL_TO], # to
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
# In a real app, you'd want to log this error
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
49
core/migrations/0001_initial.py
Normal file
49
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-22 18:33
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Activity',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('icon', models.CharField(max_length=50)),
|
||||||
|
('category', models.CharField(choices=[('Social', 'Social'), ('Health', 'Health'), ('Hobbies', 'Hobbies'), ('Obligation', 'Obligation')], max_length=50)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Mission',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('trigger_mood_score', models.IntegerField()),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MoodEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date_time', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('mood_score', models.IntegerField()),
|
||||||
|
('note', models.TextField(blank=True, null=True)),
|
||||||
|
('color_code', models.CharField(max_length=7)),
|
||||||
|
('activities', models.ManyToManyField(to='core.activity')),
|
||||||
|
('mission', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.mission')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
27
core/migrations/0002_note.py
Normal file
27
core/migrations/0002_note.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-12-22 19:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Note',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255)),
|
||||||
|
('content', models.TextField()),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
BIN
core/migrations/__pycache__/0002_note.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0002_note.cpython-311.pyc
Normal file
Binary file not shown.
@ -1,3 +1,49 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class Activity(models.Model):
|
||||||
|
class Category(models.TextChoices):
|
||||||
|
SOCIAL = 'Social'
|
||||||
|
HEALTH = 'Health'
|
||||||
|
HOBBIES = 'Hobbies'
|
||||||
|
OBLIGATION = 'Obligation'
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
icon = models.CharField(max_length=50)
|
||||||
|
category = models.CharField(max_length=50, choices=Category.choices)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
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()
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class MoodEntry(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
date_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
mood_score = models.IntegerField()
|
||||||
|
note = models.TextField(blank=True, null=True)
|
||||||
|
color_code = models.CharField(max_length=7)
|
||||||
|
activities = models.ManyToManyField(Activity)
|
||||||
|
mission = models.ForeignKey(Mission, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.user.username} - {self.date_time.strftime("%Y-%m-%d")}'
|
||||||
|
|
||||||
|
class Note(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
|
content = models.TextField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="h-100">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
<title>{% block title %}PixelMinds{% endblock %}</title>
|
||||||
{% if project_description %}
|
{% if project_description %}
|
||||||
<meta name="description" content="{{ project_description }}">
|
<meta name="description" content="{{ project_description }}">
|
||||||
<meta property="og:description" content="{{ project_description }}">
|
<meta property="og:description" content="{{ project_description }}">
|
||||||
@ -13,13 +13,116 @@
|
|||||||
<meta property="og:image" content="{{ project_image_url }}">
|
<meta property="og:image" content="{{ project_image_url }}">
|
||||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
<meta property="twitter:image" content="{{ project_image_url }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
{% load static %}
|
{% 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 }}">
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body class="d-flex flex-column h-100">
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-white border-bottom shadow-sm">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand fw-bold" href="/">PixelMinds</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'core:about' %}">About</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'core:contact' %}">Contact</a>
|
||||||
|
</li>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<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">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-person-circle"></i> {{ user.username }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a></li>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'logout' %}">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'login' %}">Login</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{% url 'core:signup' %}" class="btn btn-primary">Sign Up</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="flex-shrink-0">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer mt-auto py-5 bg-light border-top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<h5>PixelMinds</h5>
|
||||||
|
<p class="text-muted">Your personal health companion. Take control of your well-being.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-2 mb-3">
|
||||||
|
<h5>Links</h5>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item mb-2"><a href="/" class="nav-link p-0 text-muted">Home</a></li>
|
||||||
|
<li class="nav-item mb-2"><a href="{% url 'core:dashboard' %}" class="nav-link p-0 text-muted">Dashboard</a></li>
|
||||||
|
<li class="nav-item mb-2"><a href="{% url 'core:about' %}" class="nav-link p-0 text-muted">About</a></li>
|
||||||
|
<li class="nav-item mb-2"><a href="{% url 'core:contact' %}" class="nav-link p-0 text-muted">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-md-2 mb-3">
|
||||||
|
<h5>Legal</h5>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item mb-2"><a href="{% url 'core:privacy_policy' %}" class="nav-link p-0 text-muted">Privacy Policy</a></li>
|
||||||
|
<li class="nav-item mb-2"><a href="#" class="nav-link p-0 text-muted">Terms of Service</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<h5>Follow Us</h5>
|
||||||
|
<a href="#" class="text-muted me-3"><i class="bi bi-twitter fs-4"></i></a>
|
||||||
|
<a href="#" class="text-muted me-3"><i class="bi bi-instagram fs-4"></i></a>
|
||||||
|
<a href="#" class="text-muted"><i class="bi bi-facebook fs-4"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column flex-sm-row justify-content-between py-4 my-4 border-top">
|
||||||
|
<p class="text-muted">© 2025 PixelMinds, Inc. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="{% static 'bootstrap/dist/js/bootstrap.bundle.min.js' %}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
58
core/templates/core/about.html
Normal file
58
core/templates/core/about.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}About Us - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8 text-center">
|
||||||
|
<h1 class="section-title">About PixelMinds</h1>
|
||||||
|
<p class="lead">We are a team of passionate developers, designers, and healthcare professionals dedicated to helping you live a healthier, happier life.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Our Mission</h2>
|
||||||
|
<p>Our mission is to make mental and physical health tracking simple, accessible, and insightful for everyone. We believe that technology can empower individuals to take control of their well-being by providing them with the right tools and data-driven guidance.</p>
|
||||||
|
<p>We leverage cutting-edge AI to provide you with personalized insights and recommendations based on your mood and activity data. By understanding your patterns, you can make informed decisions to improve your well-being.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-center">
|
||||||
|
<img src="https://via.placeholder.com/500x300.png/E0E7FF/4F46E5?text=Health+Innovation" class="img-fluid rounded shadow" alt="Health Innovation">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h2>Meet the Team</h2>
|
||||||
|
<p class="lead">A small group with a big vision.</p>
|
||||||
|
</div>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<img src="https://via.placeholder.com/150.png/E0E7FF/4F46E5?text=AV" class="rounded-circle mb-3" alt="Team Member">
|
||||||
|
<h5>Alex Vance</h5>
|
||||||
|
<p class="text-muted">Lead Developer</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<img src="https://via.placeholder.com/150.png/E0E7FF/4F46E5?text=BS" class="rounded-circle mb-3" alt="Team Member">
|
||||||
|
<h5>Brenda Shaw</h5>
|
||||||
|
<p class="text-muted">UX Designer</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<img src="https://via.placeholder.com/150.png/E0E7FF/4F46E5?text=CD" class="rounded-circle mb-3" alt="Team Member">
|
||||||
|
<h5>Dr. Carlin Day</h5>
|
||||||
|
<p class="text-muted">Health Advisor</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
61
core/templates/core/contact.html
Normal file
61
core/templates/core/contact.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Contact Us - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8 text-center">
|
||||||
|
<h1 class="section-title">Get in Touch</h1>
|
||||||
|
<p class="lead">We'd love to hear from you. Whether you have a question, feedback, or just want to say hello, please don't hesitate to reach out.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section pt-0">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-7">
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card shadow-lg border-0">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<form method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form.name.label_tag }}
|
||||||
|
{{ form.name }}
|
||||||
|
{% if form.name.errors %}<div class="invalid-feedback d-block">{{ form.name.errors|striptags }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form.email.label_tag }}
|
||||||
|
{{ form.email }}
|
||||||
|
{% if form.email.errors %}<div class="invalid-feedback d-block">{{ form.email.errors|striptags }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ form.message.label_tag }}
|
||||||
|
{{ form.message }}
|
||||||
|
{% if form.message.errors %}<div class="invalid-feedback d-block">{{ form.message.errors|striptags }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Send Message</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
165
core/templates/core/dashboard.html
Normal file
165
core/templates/core/dashboard.html
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Dashboard - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-4 align-items-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h1 class="section-title mb-0">Your Dashboard</h1>
|
||||||
|
<p class="lead">An overview of your mood entries.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-md-end">
|
||||||
|
<a href="{% url 'core:create_mood_entry' %}" class="btn btn-primary">+ New Mood Entry</a>
|
||||||
|
</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>
|
||||||
|
<form method="get" class="row g-3 align-items-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label for="{{ form.start_date.id_for_label }}" class="form-label">Start Date</label>
|
||||||
|
{{ form.start_date }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label for="{{ form.end_date.id_for_label }}" class="form-label">End Date</label>
|
||||||
|
{{ form.end_date }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
|
<button type="submit" class="btn btn-secondary w-100">Filter</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card p-4 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title fw-bold mb-3">Mood Over Time</h5>
|
||||||
|
{% if chart_data != '[]' %}
|
||||||
|
<canvas id="moodChart"></canvas>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center p-5">
|
||||||
|
<p class="lead">No mood entries found for the selected period.</p>
|
||||||
|
<p class="text-muted">Try adjusting the filter or <a href="{% url 'core:create_mood_entry' %}">add a new entry</a>.</p>
|
||||||
|
</div>
|
||||||
|
{% 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>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const chartData = JSON.parse('{{ chart_data|safe }}');
|
||||||
|
if (chartData.length > 0) {
|
||||||
|
const ctx = document.getElementById('moodChart').getContext('2d');
|
||||||
|
const dates = chartData.map(d => new Date(d.date_time));
|
||||||
|
const scores = chartData.map(d => d.mood_score);
|
||||||
|
|
||||||
|
const moodChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: dates,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Mood Score',
|
||||||
|
data: scores,
|
||||||
|
borderColor: '#4f46e5', // primary-color
|
||||||
|
backgroundColor: 'rgba(79, 70, 229, 0.1)',
|
||||||
|
borderWidth: 2,
|
||||||
|
fill: true,
|
||||||
|
tension: 0.4
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
time: {
|
||||||
|
unit: 'day',
|
||||||
|
tooltipFormat: 'MMM dd, yyyy',
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
max: 10,
|
||||||
|
grid: {
|
||||||
|
color: '#e5e7eb'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 %}
|
||||||
@ -1,145 +1,100 @@
|
|||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}{{ project_name }}{% endblock %}
|
{% block title %}PixelMinds - Your Personal Health Companion{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 100% 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2.5rem 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1.2rem;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
opacity: 0.92;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
margin: 1.5rem auto;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border: 4px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.runtime code {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
padding: 0.15rem 0.45rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<div class="breathing-canvas">
|
||||||
|
<div class="pulsing-circle"></div>
|
||||||
|
</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">
|
||||||
<h1>Analyzing your requirements and generating your app…</h1>
|
<div class="card-body">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<h5 class="card-title">{{ entry.date_time|date:"F d, Y" }}</h5>
|
||||||
<span class="sr-only">Loading…</span>
|
<p class="card-text">Mood Score: {{ entry.mood_score }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
|
||||||
<p class="runtime">
|
|
||||||
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
|
||||||
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
<footer>
|
{% endfor %}
|
||||||
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
</div>
|
||||||
</footer>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
const modal = document.getElementById('mood-modal');
|
||||||
|
const pulsingCircle = document.querySelector('.pulsing-circle');
|
||||||
|
const closeButton = document.querySelector('.close-button');
|
||||||
|
const moodSlider = document.getElementById('mood-slider');
|
||||||
|
const activityBubbles = document.querySelectorAll('.activity-bubble');
|
||||||
|
const body = document.querySelector('body');
|
||||||
|
const saveButton = document.getElementById('save-mood-button');
|
||||||
|
|
||||||
|
const moodColors = {
|
||||||
|
1: ['#4b0082', '#8a2be2'], // Sad (Indigo, Deep Purple)
|
||||||
|
2: ['#6a5acd', '#836fff'], // Stressed (SlateBlue, MediumSlateBlue)
|
||||||
|
3: ['#87ceeb', '#add8e6'], // Neutral (SkyBlue, LightBlue)
|
||||||
|
4: ['#ffd700', '#ffec8b'], // Energetic (Gold, LightGoldenrod)
|
||||||
|
5: ['#ffb6c1', '#ffc0cb'], // Happy (LightPink, Pink)
|
||||||
|
};
|
||||||
|
|
||||||
|
pulsingCircle.addEventListener('click', () => {
|
||||||
|
modal.style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', () => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('click', (event) => {
|
||||||
|
if (event.target == modal) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
moodSlider.addEventListener('input', (event) => {
|
||||||
|
const moodValue = event.target.value;
|
||||||
|
const [color1, color2] = moodColors[moodValue];
|
||||||
|
body.style.background = `linear-gradient(45deg, ${color1}, ${color2})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
activityBubbles.forEach(bubble => {
|
||||||
|
bubble.addEventListener('click', () => {
|
||||||
|
bubble.classList.toggle('selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
saveButton.addEventListener('click', () => {
|
||||||
|
const moodScore = moodSlider.value;
|
||||||
|
const selectedBubbles = document.querySelectorAll('.activity-bubble.selected');
|
||||||
|
const activityIds = Array.from(selectedBubbles).map(bubble => bubble.dataset.activityId);
|
||||||
|
|
||||||
|
fetch('{% url "core:save_mood_entry" %}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
mood_score: moodScore,
|
||||||
|
activity_ids: activityIds,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
alert('There was an error saving your entry.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
28
core/templates/core/mission_confirm_delete.html
Normal file
28
core/templates/core/mission_confirm_delete.html
Normal 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 %}
|
||||||
16
core/templates/core/mission_detail.html
Normal file
16
core/templates/core/mission_detail.html
Normal 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 %}
|
||||||
38
core/templates/core/mission_form.html
Normal file
38
core/templates/core/mission_form.html
Normal 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 %}
|
||||||
44
core/templates/core/mission_list.html
Normal file
44
core/templates/core/mission_list.html
Normal 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 %}
|
||||||
64
core/templates/core/mood_entry_form.html
Normal file
64
core/templates/core/mood_entry_form.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}New Mood Entry - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="card shadow-lg border-0">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<h1 class="section-title text-center mb-4">How are you feeling?</h1>
|
||||||
|
<form method="post" novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="{{ form.mood_score.id_for_label }}" class="form-label fs-5">Your Mood Score (1-10)</label>
|
||||||
|
{{ form.mood_score }}
|
||||||
|
{% if form.mood_score.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.mood_score.errors|striptags }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="{{ form.note.id_for_label }}" class="form-label fs-5">Notes</label>
|
||||||
|
{{ form.note }}
|
||||||
|
{% if form.note.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.note.errors|striptags }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fs-5">Activities</label>
|
||||||
|
<div id="id_activities" class="border rounded p-3 bg-light">
|
||||||
|
{% for checkbox in form.activities %}
|
||||||
|
<div class="form-check form-check-inline me-3">
|
||||||
|
{{ checkbox.tag }}
|
||||||
|
<label class="form-check-label" for="{{ checkbox.id_for_label }}">{{ checkbox.choice_label }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% if form.activities.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.activities.errors|striptags }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Save Entry</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
21
core/templates/core/mood_modal.html
Normal file
21
core/templates/core/mood_modal.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div id="mood-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close-button">×</span>
|
||||||
|
<h2>How are you feeling?</h2>
|
||||||
|
<div class="mood-slider-container">
|
||||||
|
<input type="range" min="1" max="5" value="3" class="mood-slider" id="mood-slider">
|
||||||
|
<div class="mood-labels">
|
||||||
|
<span>Sad</span>
|
||||||
|
<span>Neutral</span>
|
||||||
|
<span>Happy</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>What have you been up to?</h2>
|
||||||
|
<div class="activity-bubbles">
|
||||||
|
{% for activity in activities %}
|
||||||
|
<div class="activity-bubble" data-activity-id="{{ activity.id }}">{{ activity.name }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<button id="save-mood-button">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
15
core/templates/core/note_confirm_delete.html
Normal file
15
core/templates/core/note_confirm_delete.html
Normal 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 %}
|
||||||
23
core/templates/core/note_detail.html
Normal file
23
core/templates/core/note_detail.html
Normal 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 %}
|
||||||
22
core/templates/core/note_form.html
Normal file
22
core/templates/core/note_form.html
Normal 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 %}
|
||||||
31
core/templates/core/note_list.html
Normal file
31
core/templates/core/note_list.html
Normal 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 %}
|
||||||
50
core/templates/core/privacy_policy.html
Normal file
50
core/templates/core/privacy_policy.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Privacy Policy - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="section-title">Privacy Policy</h1>
|
||||||
|
<p class="lead">Your privacy is important to us. Last updated: December 22, 2025</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>It is PixelMinds's policy to respect your privacy regarding any information we may collect from you across our website. This Privacy Policy applies to all of our services.</p>
|
||||||
|
|
||||||
|
<h2 class="h4 mt-5">1. Information We Collect</h2>
|
||||||
|
<p>We only collect information about you if we have a reason to do so – for example, to provide our services, to communicate with you, or to make our services better. We collect this information from three sources: if and when you provide information to us, automatically through operating our services, and from outside sources.</p>
|
||||||
|
|
||||||
|
<h3 class="h5 mt-4">Information You Provide to Us</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Account Information:</strong> We collect information from you when you create an account, such as your name, email address, and password.</li>
|
||||||
|
<li><strong>Health Data:</strong> We collect the mood scores, notes, and activities you log through our service.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="h4 mt-5">2. How We Use Information</h2>
|
||||||
|
<p>We use the information we collect in various ways, including to:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Provide, operate, and maintain our website and services.</li>
|
||||||
|
<li>Improve, personalize, and expand our services.</li>
|
||||||
|
<li>Understand and analyze how you use our services.</li>
|
||||||
|
<li>Develop new products, services, features, and functionality.</li>
|
||||||
|
<li>Communicate with you for customer service, updates, and marketing purposes.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="h4 mt-5">3. Security</h2>
|
||||||
|
<p>We take reasonable precautions and follow industry best practices to protect your personal information and ensure that it is not inappropriately lost, misused, accessed, disclosed, altered, or destroyed.</p>
|
||||||
|
|
||||||
|
<h2 class="h4 mt-5">4. Changes to This Policy</h2>
|
||||||
|
<p>We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page. You are advised to review this Privacy Policy periodically for any changes.</p>
|
||||||
|
|
||||||
|
<hr class="my-5">
|
||||||
|
<p class="text-muted small">This is a generic, placeholder privacy policy. It is not legal advice. You must consult with a legal professional to create a policy that is compliant with all applicable laws and regulations for your business.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
42
core/templates/registration/login.html
Normal file
42
core/templates/registration/login.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Login - PixelMinds{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card shadow-lg border-0">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<h1 class="section-title text-center mb-4">Welcome Back</h1>
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
Your username and password didn't match. Please try again.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_username" class="form-label">Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" id="id_username" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_password" class="form-label">Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" id="id_password" required>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid mb-3">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Login</button>
|
||||||
|
</div>
|
||||||
|
<p class="text-center"><a href="{% url 'password_reset' %}">Forgot password?</a></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-center mt-4">Don't have an account? <a href="{% url 'signup' %}">Sign up</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
48
core/templates/registration/password_change.html
Normal file
48
core/templates/registration/password_change.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Change Password - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Change Password</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Change My Password</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
21
core/templates/registration/password_change_done.html
Normal file
21
core/templates/registration/password_change_done.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Password Change Complete - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Password Change Complete</h3></div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="lead">Your password has been successfully changed.</p>
|
||||||
|
<a href="{% url 'core:index' %}" class="btn btn-primary mt-3">Return to Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
53
core/templates/registration/password_reset.html
Normal file
53
core/templates/registration/password_reset.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Forgot Your Password? - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Password Recovery</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="small mb-3 text-muted">Enter your email address and we will send you a link to reset your password.</div>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<small class="form-text text-muted">{{ field.help_text }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center justify-content-between mt-4 mb-0">
|
||||||
|
<a class="small" href="{% url 'login' %}">Return to login</a>
|
||||||
|
<button type="submit" class="btn btn-primary">Reset Password</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center py-3">
|
||||||
|
<div class="small"><a href="{% url 'core:signup' %}">Need an account? Sign up!</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
21
core/templates/registration/password_reset_complete.html
Normal file
21
core/templates/registration/password_reset_complete.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Complete - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Password Reset Complete</h3></div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="lead">Your password has been set. You may go ahead and log in now.</p>
|
||||||
|
<a href="{% url 'login' %}" class="btn btn-primary mt-3">Return to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
58
core/templates/registration/password_reset_confirm.html
Normal file
58
core/templates/registration/password_reset_confirm.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Enter New Password - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
{% if validlink %}
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Reset Password</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted text-center small mb-4">Please enter your new password twice so we can verify you typed it in correctly.</p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<small class="form-text text-muted">{{ field.help_text|safe }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Change My Password</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Password Reset Failed</h3></div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="lead text-danger">The password reset link was invalid, possibly because it has already been used.</p>
|
||||||
|
<p>Please request a new password reset.</p>
|
||||||
|
<a href="{% url 'password_reset' %}" class="btn btn-primary mt-3">Request a New Password Reset</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
22
core/templates/registration/password_reset_done.html
Normal file
22
core/templates/registration/password_reset_done.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Sent - 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-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Password Reset Sent</h3></div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="lead">We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.</p>
|
||||||
|
<p class="text-muted">If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder.</p>
|
||||||
|
<a href="{% url 'login' %}" class="btn btn-primary mt-3">Return to Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
50
core/templates/registration/signup.html
Normal file
50
core/templates/registration/signup.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card shadow-lg border-0 rounded-lg mt-5">
|
||||||
|
<div class="card-header"><h3 class="text-center font-weight-light my-4">Create Account</h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% 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 %}
|
||||||
|
<small class="form-text text-muted">{{ field.help_text }}</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Sign Up</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-center py-3">
|
||||||
|
<div class="small"><a href="{% url 'login' %}">Have an account? Go to login</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
27
core/urls.py
27
core/urls.py
@ -1,7 +1,30 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import home
|
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'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path("", home, name="index"),
|
||||||
|
path('signup/', SignUpView.as_view(), name='signup'),
|
||||||
|
path('mood/new/', create_mood_entry, name='create_mood_entry'),
|
||||||
|
path('mood/save/', save_mood_entry, name='save_mood_entry'),
|
||||||
|
path('dashboard/', dashboard, name='dashboard'),
|
||||||
|
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'),
|
||||||
]
|
]
|
||||||
257
core/views.py
257
core/views.py
@ -1,25 +1,254 @@
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import json
|
||||||
|
|
||||||
from django import get_version as django_version
|
from django import get_version as django_version
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
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, ListView, DetailView, UpdateView, DeleteView
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .forms import MoodEntryForm, ContactForm, SignUpForm, DateRangeFilterForm
|
||||||
|
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):
|
||||||
|
form_class = SignUpForm
|
||||||
|
success_url = reverse_lazy('login')
|
||||||
|
template_name = 'registration/signup.html'
|
||||||
|
|
||||||
|
|
||||||
|
class AboutView(TemplateView):
|
||||||
|
template_name = "core/about.html"
|
||||||
|
|
||||||
|
|
||||||
|
class ContactView(FormView):
|
||||||
|
template_name = "core/contact.html"
|
||||||
|
form_class = ContactForm
|
||||||
|
success_url = reverse_lazy('core:contact')
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
name = form.cleaned_data['name']
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
message = form.cleaned_data['message']
|
||||||
|
|
||||||
|
# Send email
|
||||||
|
email_sent = send_contact_message(name, email, message)
|
||||||
|
|
||||||
|
if email_sent:
|
||||||
|
messages.success(self.request, 'Thank you for your message! We will get back to you shortly.')
|
||||||
|
else:
|
||||||
|
messages.error(self.request, 'Sorry, there was an error sending your message. Please try again later.')
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class PrivacyPolicyView(TemplateView):
|
||||||
|
template_name = "core/privacy_policy.html"
|
||||||
|
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
"""Render the landing screen with loader and environment details."""
|
"""Render the home page with mood entries."""
|
||||||
host_name = request.get_host().lower()
|
|
||||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
|
||||||
now = timezone.now()
|
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"project_name": "New Style",
|
'project_name': "PixelMinds",
|
||||||
"agent_brand": agent_brand,
|
|
||||||
"django_version": django_version(),
|
|
||||||
"python_version": platform.python_version(),
|
|
||||||
"current_time": now,
|
|
||||||
"host_name": host_name,
|
|
||||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
|
||||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
|
||||||
}
|
}
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
mood_entries = MoodEntry.objects.filter(user=request.user).order_by('-date_time')[:7]
|
||||||
|
context['mood_entries'] = mood_entries
|
||||||
|
activities = Activity.objects.all()
|
||||||
|
context['activities'] = activities
|
||||||
|
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/index.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def dashboard(request):
|
||||||
|
mood_entries = MoodEntry.objects.filter(user=request.user).order_by('date_time')
|
||||||
|
form = DateRangeFilterForm(request.GET)
|
||||||
|
if form.is_valid():
|
||||||
|
start_date = form.cleaned_data.get('start_date')
|
||||||
|
end_date = form.cleaned_data.get('end_date')
|
||||||
|
if start_date:
|
||||||
|
mood_entries = mood_entries.filter(date_time__gte=start_date)
|
||||||
|
if end_date:
|
||||||
|
mood_entries = mood_entries.filter(date_time__lte=end_date)
|
||||||
|
|
||||||
|
df = read_frame(mood_entries, fieldnames=['date_time', 'mood_score'])
|
||||||
|
if not df.empty:
|
||||||
|
df['date_time'] = df['date_time'].dt.strftime('%Y-%m-%d')
|
||||||
|
chart_data = df.to_json(orient='records')
|
||||||
|
|
||||||
|
# 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
|
||||||
|
def create_mood_entry(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = MoodEntryForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
mood_entry = form.save(commit=False)
|
||||||
|
mood_entry.user = request.user
|
||||||
|
|
||||||
|
# Assign color based on mood score
|
||||||
|
mood_score = form.cleaned_data['mood_score']
|
||||||
|
if mood_score == 1:
|
||||||
|
mood_entry.color_code = '#FF0000' # Red
|
||||||
|
elif mood_score == 2:
|
||||||
|
mood_entry.color_code = '#FFC107' # Amber
|
||||||
|
elif mood_score == 3:
|
||||||
|
mood_entry.color_code = '#FFFF00' # Yellow
|
||||||
|
elif mood_score == 4:
|
||||||
|
mood_entry.color_code = '#4CAF50' # Green
|
||||||
|
elif mood_score == 5:
|
||||||
|
mood_entry.color_code = '#2196F3' # Blue
|
||||||
|
|
||||||
|
mood_entry.save()
|
||||||
|
form.save_m2m() # Save the many-to-many relationships
|
||||||
|
return redirect('core:index')
|
||||||
|
else:
|
||||||
|
form = MoodEntryForm()
|
||||||
|
|
||||||
|
return render(request, 'core/mood_entry_form.html', {'form': form})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def save_mood_entry(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
data = json.loads(request.body)
|
||||||
|
mood_score = data.get('mood_score')
|
||||||
|
activity_ids = data.get('activity_ids')
|
||||||
|
|
||||||
|
mood_entry = MoodEntry.objects.create(
|
||||||
|
user=request.user,
|
||||||
|
mood_score=mood_score
|
||||||
|
)
|
||||||
|
|
||||||
|
if activity_ids:
|
||||||
|
activities = Activity.objects.filter(id__in=activity_ids)
|
||||||
|
mood_entry.activities.set(activities)
|
||||||
|
|
||||||
|
return JsonResponse({'status': 'success'})
|
||||||
|
return JsonResponse({'status': 'error'}, status=400)
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
Django==5.2.7
|
Django==5.2.7
|
||||||
mysqlclient==2.2.7
|
mysqlclient==2.2.7
|
||||||
python-dotenv==1.1.1
|
python-dotenv==1.1.1
|
||||||
|
django-pandas==0.6.7
|
||||||
|
|||||||
@ -1,4 +1,232 @@
|
|||||||
/* Custom styles for the application */
|
|
||||||
body {
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Lexend+Deca:wght@600;700&display=swap');
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
|
||||||
|
:root {
|
||||||
|
--primary-color: #8a2be2; /* Deep purple */
|
||||||
|
--secondary-color: #4b0082; /* Indigo */
|
||||||
|
--light-gray: #f8f9fa;
|
||||||
|
--dark-gray: #1f2937;
|
||||||
|
--text-color: #e0e0e0; /* Lighter text for dark background */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
color: var(--text-color);
|
||||||
|
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradient 15s ease infinite;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden; /* Prevent scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathing-canvas {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulsing-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-medium {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: transparent !important;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-family: 'Lexend Deca', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: var(--text-color);
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal styles */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #1f2937;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
border-radius: 20px;
|
||||||
|
animation: bloom 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom {
|
||||||
|
from {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover,
|
||||||
|
.close-button:focus {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mood slider */
|
||||||
|
.mood-slider-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #ddd;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transition: .2s;
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider::-moz-range-thumb {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Activity bubbles */
|
||||||
|
.activity-bubbles {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-bubble {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #4b5563;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-bubble.selected {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,232 @@
|
|||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Lexend+Deca:wght@600;700&display=swap');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg-color-start: #6a11cb;
|
--primary-color: #8a2be2; /* Deep purple */
|
||||||
--bg-color-end: #2575fc;
|
--secondary-color: #4b0082; /* Indigo */
|
||||||
--text-color: #ffffff;
|
--light-gray: #f8f9fa;
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
--dark-gray: #1f2937;
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
--text-color: #e0e0e0; /* Lighter text for dark background */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradient 15s ease infinite;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden; /* Prevent scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathing-canvas {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
height: 100%;
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pulsing-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 10px rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(0.95);
|
||||||
|
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-medium {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-semibold {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw-bold {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: transparent !important;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-family: 'Lexend Deca', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: var(--text-color);
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal styles */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #1f2937;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 500px;
|
||||||
|
border-radius: 20px;
|
||||||
|
animation: bloom 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bloom {
|
||||||
|
from {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover,
|
||||||
|
.close-button:focus {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mood slider */
|
||||||
|
.mood-slider-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #ddd;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transition: .2s;
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-slider::-moz-range-thumb {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mood-labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Activity bubbles */
|
||||||
|
.activity-bubbles {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-bubble {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #4b5563;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-bubble.selected {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user