Compare commits

..

No commits in common. "master" and "multiple_lists" have entirely different histories.

47 changed files with 645 additions and 2362 deletions

3
.gitignore vendored
View File

@ -165,6 +165,3 @@ cython_debug/
db/db.sqlite db/db.sqlite
/HabitTracker.iml /HabitTracker.iml
static/profile_images/*
!/static/profile_images/no_avatar/

BIN
ER.dia

Binary file not shown.

BIN
ER.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

BIN
UML.dia

Binary file not shown.

BIN
UML.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 20 KiB

722
app.py
View File

@ -1,30 +1,25 @@
import datetime import datetime
import hashlib import hashlib
import os
from PIL import Image, ImageSequence from flask import Flask, render_template, redirect, url_for, request
import concurrent.futures
from flask import Flask, render_template, redirect, url_for, request, jsonify
from flask_login import login_required, LoginManager, login_user, logout_user, current_user from flask_login import login_required, LoginManager, login_user, logout_user, current_user
from models.Habit import Habit from models.Habit import Habit
from models.HabitList import HabitList from models.HabitList import HabitList
from models.HabitTracking import HabitTracking from models.HabitTrackings import HabitTrackings
from models.User import User from models.User import User
from utils import anonymous_required from utils import anonymous_required
# Create a new Flask instance # Create a new Flask instance
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'PSSSSSHHHT!' app.secret_key = 'PSSSSSHHHT!'
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Initialize the Flask-Login extension # Initialize the Flask-Login extension
login_manager = LoginManager() login_manager = LoginManager()
login_manager.login_view = 'login' login_manager.login_view = 'login'
login_manager.init_app(app) login_manager.init_app(app)
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
return User.get(user_id) return User.get(user_id)
@ -35,73 +30,18 @@ def inject_user():
return dict(user=current_user) return dict(user=current_user)
@app.context_processor
def inject_notifications():
if current_user.is_authenticated:
habit_lists = current_user.get_unaccepted_habitLists()
lists = []
for habit_list in habit_lists:
# Check if the user is the first user in the list
if habit_list.get_users()[0].id != current_user.id:
lists.append(habit_list)
return dict(notifications=lists)
return dict(notifications=[])
# Create a new route
@app.route('/')
def index():
if current_user.is_authenticated:
habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name
heatmap_values, day = current_user.get_heatmap()
heatmap_color = current_user.heatmap_color
else:
habit_lists = []
name = "Bitte melde dich an."
heatmap_values = []
day = None
heatmap_color = None
# Sort habit_lists based on their order attribute
habit_lists = sorted(habit_lists, key=lambda habitList: habitList.slot)
# Sort habits within each habit_list by slot
for habit_list in habit_lists:
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (habit.checked, habit.slot))
for habit in habit_list.get_habits():
habit.load_statistics()
# Get active_list from query parameter
try:
active_list = int(request.args.get('list'))
except (ValueError, TypeError):
active_list = None
return render_template(
'index.html',
title=name,
habit_lists=habit_lists,
heatmap_values=heatmap_values,
day=day,
color=heatmap_color,
errors={},
active_list=active_list
)
###################### Login & Signup #####################
@app.route('/login') @app.route('/login')
@anonymous_required @anonymous_required
def login(): def login():
return render_template('auth/login.html', errors={}) return render_template('auth/login.html', errors={})
@app.route('/signup') @app.route('/signup')
@anonymous_required @anonymous_required
def signup(): def signup():
return render_template('auth/signup.html', errors={}) return render_template('auth/signup.html', errors={})
@app.route('/login', methods=['POST']) @app.route('/login', methods=['POST'])
def login_post(): def login_post():
email = request.form.get('email') email = request.form.get('email')
@ -135,6 +75,7 @@ def login_post():
# Redirect to login page # Redirect to login page
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/signup', methods=['POST']) @app.route('/signup', methods=['POST'])
def signup_post(): def signup_post():
email = request.form.get('email') email = request.form.get('email')
@ -150,10 +91,6 @@ def signup_post():
if not password: if not password:
errors['password'] = 'Das Passwort ist erforderlich.' errors['password'] = 'Das Passwort ist erforderlich.'
# Check if email is already in use
if User.get_by_email(email):
errors['email'] = 'E-Mail Adresse bereits in Benutzung.'
if errors: if errors:
return render_template( return render_template(
'auth/signup.html', 'auth/signup.html',
@ -170,6 +107,7 @@ def signup_post():
# Redirect to login page # Redirect to login page
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/logout') @app.route('/logout')
@login_required @login_required
def logout(): def logout():
@ -177,11 +115,31 @@ def logout():
logout_user() logout_user()
return redirect(url_for('index')) return redirect(url_for('index'))
###########################################################
# Create a new route
@app.route('/')
def index():
if current_user.is_authenticated:
habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name
else:
habit_lists = []
name = "Bitte melde dich an."
# Sort habits by whether they have been checked today and then by slot
for habit_list in habit_lists:
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (not habit.checked, habit.slot))
return render_template(
'index.html',
title=name,
utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M %A"),
habit_lists=habit_lists,
errors={},
)
########################## Habit ##########################
@app.route('/habit') @app.route('/habit')
@login_required @login_required
def habit_creation(): def habit_creation():
@ -192,6 +150,7 @@ def habit_creation():
errors={}, errors={},
) )
@app.route('/habit', methods=['POST']) @app.route('/habit', methods=['POST'])
@login_required @login_required
def habit_create(): def habit_create():
@ -228,22 +187,6 @@ def habit_create():
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']: if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
errors['unit'] = 'Die Einheit ist ungültig.' errors['unit'] = 'Die Einheit ist ungültig.'
# check if list_id is an int
try:
list_id = int(list_id)
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors: if errors:
return render_template( return render_template(
'habit.html', 'habit.html',
@ -269,125 +212,113 @@ def habit_create():
unit = 1 unit = 1
# Save habit to database # Save habit to database
Habit.create(list_id, name, times, note, unit) habit = Habit.create(current_user.id, name, times, note, unit)
# Back to index # Back to index
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/edit-habit')
@app.route('/habit-list')
@login_required @login_required
def edit_habit(): def habit_list_creation():
habit_id = int(request.args.get("habit"))
habit = Habit.get(habit_id)
units = ["Tag", "Woche", "Monat", "Jahr"]
return render_template( return render_template(
"edit-habit.html", 'habit-list.html',
title=habit.name, title='Erstelle eine Habitliste',
habit=habit.id, errors={},
name=habit.name,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors={}
) )
@app.route('/edit-habit', methods=['POST'])
@app.route('/habit-list', methods=['POST'])
@login_required @login_required
def edit_habit_change(): def habit_list_create():
units = ["Tag", "Woche", "Monat", "Jahr"]
name = request.form.get('name') name = request.form.get('name')
note = request.form.get('note') description = request.form.get('description')
times = request.form.get('times')
unit = request.form.get('unit')
list_id = request.form.get('habit')
habit = Habit.get(list_id)
# Check for errors # Check for errors
errors = {} errors = {}
if not name: if not name:
errors['name'] = 'Der Name ist erforderlich.' errors['name'] = 'Der Name ist erforderlich.'
if not times: if not description:
errors['times'] = 'Die Anzahl ist erforderlich.'
if not note:
note = '' note = ''
if not unit:
errors['unit'] = 'Die Einheit ist erforderlich.'
if not list_id:
errors['habit'] = 'Das Habit ist erforderlich.'
# Check if times is an integer
try:
print(times)
times = int(times)
# Check that times is greater than 0
if times <= 0:
errors['times'] = 'Die Anzahl muss größer als 0 sein.'
except ValueError:
errors['times'] = 'Die Anzahl muss eine Zahl sein.'
# Check that unit is valid
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
errors['unit'] = 'Die Einheit ist ungültig.'
# check if list_id is an int
try:
int(list_id)
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors: if errors:
return render_template( return render_template(
"edit-habit.html", 'habit-list.html',
title=habit.name, title='Erstelle eine Habitliste',
habit=habit.id, name=name,
name=habit.name, description=description,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors=errors errors=errors
) )
# Map unit to integer
if unit == 'Tag':
unit = 0
elif unit == 'Woche':
unit = 1
elif unit == 'Monat':
unit = 2
elif unit == 'Jahr':
unit = 3
else:
unit = 1
# Save habit to database # Save habit to database
print(name, note, times, unit) habit = HabitList.create(current_user.id, name, description)
habit.name, habit.note, habit.times, habit.unit = name, note, times, unit
habit.update()
# Back to index # Back to index
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route('/profile')
@login_required
def profile():
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
errors={}
)
@app.route('/profile', methods=['POST'])
@login_required
def profile_change():
newName = request.form.get('newName')
newEmail = request.form.get('newEmail')
newPassword = request.form.get('newPassword')
oldPassword = request.form.get('oldPassword')
# Check for errors
errors = {}
if not newName:
errors['newName'] = 'Der Name ist erforderlich.'
if not newEmail:
errors['newEmail'] = 'Die E-Mail Adresse ist erforderlich.'
if not oldPassword:
errors['oldPassword'] = 'Du musst dein aktuelles Passwort angeben.'
else:
if hashlib.sha256(oldPassword.encode()).hexdigest() != current_user.password:
errors['oldPassword'] = 'Das Passwort ist falsch.'
if errors:
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
errors=errors
)
# Update user
current_user.name = newName
current_user.email = newEmail
if newPassword:
current_user.password = hashlib.sha256(newPassword.encode()).hexdigest()
current_user.update()
# Back to profile
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
errors={}
)
@app.route('/check', methods=['POST']) @app.route('/check', methods=['POST'])
@login_required @login_required
def check_habit(): def check_habit():
habit_id = request.get_json()["habitId"] habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id) habit = Habit.get(habit_id)
if habit is None: if habit is None:
@ -407,22 +338,17 @@ def check_habit():
delete_tracking = tracking delete_tracking = tracking
if not delete_tracking: if not delete_tracking:
HabitTracking.create(habit_id) HabitTrackings.create(habit_id, 1)
habit.fill_statistics()
else: else:
delete_tracking.delete() delete_tracking.delete()
habit.reset_statistics()
habit.load_statistics() # Update habit
heatmap_values, day = current_user.get_heatmap() habit.fill_statistics()
return { return {
"habitId": habit_id, "habitId": habit_id,
"unchecked": not delete_tracking, "unchecked": not delete_tracking,
"percentage": habit.percentage, "percentage": habit.percentage,
"streak": habit.streak,
"heatmap": heatmap_values,
"day": day,
} }
@app.route('/delete', methods=['POST']) @app.route('/delete', methods=['POST'])
@ -442,10 +368,11 @@ def delete_habit():
habit.delete() habit.delete()
return {} return {}
@app.route('/reorder-habit', methods=['POST'])
@app.route('/reorder', methods=['POST'])
@login_required @login_required
def reorder_habits(): def reorder_habits():
new_index = request.get_json()["newIndex"] + 1 new_index = request.get_json()["newIndex"]
habit = Habit.get(request.get_json()["habitId"]) habit = Habit.get(request.get_json()["habitId"])
if habit is None: if habit is None:
@ -459,431 +386,8 @@ def reorder_habits():
habit.update_slot(new_index) habit.update_slot(new_index)
return {} return {}
###########################################################
######################## HabitList ########################
@app.route('/habit-list')
@login_required
def habit_list_creation():
return render_template(
'habit-list.html',
title='Erstelle eine Habitliste',
errors={},
)
@app.route('/habit-list', methods=['POST'])
@login_required
def habit_list_create():
name = request.form.get('name')
description = request.form.get('description')
# Check for errors
errors = {}
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not description:
description = ''
if errors:
return render_template(
'habit-list.html',
title='Erstelle eine Habitliste',
name=name,
description=description,
errors=errors
)
# Save habit to database
HabitList.create(current_user.id, name, description)
# Back to index
return redirect(url_for('index'))
@app.route('/delete-list', methods=['POST'])
@login_required
def delete_list():
list_id = request.get_json()["listId"]
habit_list = HabitList.get(list_id)
if habit_list is None:
return {"error": "List not found"}
# Check if habit belongs to user
if current_user not in habit_list.get_users():
return {"error": "List does not belong to user"}
habit_list.delete(current_user.id)
return {}
@app.route('/users')
@login_required
def users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users.html',
title='Teilnehmer',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/users', methods=['POST'])
@login_required
def add_user():
email = request.form.get('email')
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
# Check for errors
errors = {}
if not email:
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Check if user exists
user = User.get_by_email(email)
if not user:
errors['email'] = 'E-Mail Adresse nicht gefunden.'
if user and user.id == current_user.id:
errors['email'] = 'Du kannst dich nicht selbst hinzufügen.'
# Check if user is already in the habit list
already = False
if user:
for u in habit_list.get_users():
if u.id == user.id:
already = True
break
if already:
errors['email'] = 'Teilnehmer ist bereits in der Liste.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Add user to habit list
habit_list = HabitList.get(int(habit_list_id))
habit_list.add_user(user)
return redirect(url_for('index', habit_list=habit_list.id))
@app.route('/users-edit')
@login_required
def edit_users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/user-delete', methods=['POST'])
@login_required
def delete_user_from_list():
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
habit_user_id = request.form.get('habit_user_id')
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
# Check for errors
errors = {}
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if not habit_list_id:
errors['habit_user'] = 'Ein User ist erforderlich.'
if errors:
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
# delete user from habit list
id = int(habit_user_id)
habit_list.delete(id)
return redirect(url_for('index', habit_list=habit_list.id))
@app.route('/users-leave')
@login_required
def user_leave():
list_id = request.args.get('habit_list')
habit_list = HabitList.get(list_id)
if habit_list is None:
return {"error": "List not found"}
# Check if habit belongs to user
if current_user not in habit_list.get_users():
return {"error": "List does not belong to user"}
habit_list.remove_user(current_user.id)
return redirect(url_for("index"))
@app.route('/accept-list', methods=['POST'])
@login_required
def accept_list():
list_id = request.json.get('list_id')
habit_list = HabitList.get(int(list_id))
users = habit_list.get_users()
# Check if user is part of the list
found = False
for user in users:
if user.id == current_user.id:
found = True
break
if not found:
return {}
current_user.accept_List(habit_list.id)
return {}
@app.route('/deny-list', methods=['POST'])
@login_required
def deny_list():
list_id = request.json.get('list_id')
habit_list = HabitList.get(int(list_id))
users = habit_list.get_users()
# Check if user is part of the list
found = False
for user in users:
if user.id == current_user.id:
found = True
break
if not found:
return {}
habit_list.remove_user(current_user.id)
return {}
@app.route('/reorder-list', methods=['POST'])
@login_required
def reorder_habit_list():
new_index = request.get_json()["newIndex"] + 1
habitList = HabitList.get(request.get_json()["listId"])
if habitList is None:
return {"error": "HabitList not found"}
# Check if habit belongs to user
users = habitList.get_users()
if current_user not in users:
return {"error": "HabitList does not belong to user"}
habitList.update_slot(current_user.id, new_index)
return {}
###########################################################
######################### Profile #########################
@app.route('/profile')
@login_required
def profile():
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color = current_user.heatmap_color,
title="Profil",
)
@app.route('/profile', methods=['POST'])
@login_required
def profile_change():
newName = request.form.get('newName')
newEmail = request.form.get('newEmail')
# Update user
current_user.name = newName
current_user.email = newEmail
current_user.update()
# Back to profile
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
)
@app.route('/check_password', methods=['POST'])
@login_required
def check_password():
# Get the password sent from the client
password = request.json.get('password')
if hashlib.sha256(password.encode()).hexdigest() == current_user.password:
return jsonify({"valid": True})
else:
return jsonify({"valid": False})
@app.route('/password', methods=['POST'])
@login_required
def password_change():
newPassword = request.form.get('newPassword')
# Update user
if newPassword:
current_user.password = hashlib.sha256(newPassword.encode()).hexdigest()
current_user.update()
# Back to profile
return render_template("profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
)
def save_profile_image(image_file):
filename = image_file.filename
if '.' not in filename:
# Ensure the filename has an extension
raise ValueError("Invalid filename")
# Check if the file extension is allowed
allowed_extensions = {'jpg', 'jpeg', 'png', 'gif'}
file_extension = filename.rsplit('.', 1)[1].lower()
if file_extension not in allowed_extensions:
raise ValueError("Invalid file extension")
# Get the filename from the image path saved in the user
filename = os.path.basename(current_user.profile_image)
# Open the uploaded image
image = Image.open(image_file)
# Function to crop and resize frames
def process_frame(frame, size):
new_size = min(frame.size)
left = (frame.width - new_size) // 2
top = (frame.height - new_size) // 2
right = left + new_size
bottom = top + new_size
cropped_frame = frame.crop((left, top, right, bottom))
return cropped_frame.resize(size)
# Function to process frames in parallel
def process_frames_parallel(frames, size):
with concurrent.futures.ThreadPoolExecutor() as executor:
resized_frames = list(executor.map(lambda f: process_frame(f, size), frames))
return resized_frames
# Check if the image is an animated gif
if file_extension == 'gif':
# Process frames
gif_frames = [frame.copy() for frame in ImageSequence.Iterator(image)]
processed_frames = process_frames_parallel(gif_frames, size=(128, 128))
# Save the modified frames as a new GIF
output_gif_path = os.path.join(app.config['UPLOAD_FOLDER'], filename.replace(".jpg", ".gif"))
processed_frames[0].save(output_gif_path, save_all=True, append_images=processed_frames[1:], loop=0)
return output_gif_path
else:
# Process single image
processed_image = process_frame(image, size=(256, 256))
processed_image = processed_image.convert('RGB')
# Save the processed image
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename.replace(".gif", ".jpg"))
processed_image.save(image_path, 'JPEG', quality=100)
return image_path
@app.route('/upload', methods=['POST'])
@login_required
def upload_profile_image():
if 'file' not in request.files:
return 'No file part'
file = request.files['file']
image_path = save_profile_image(file)
# Update the User
current_user.profile_image = image_path
current_user.update()
# Back to profile
return redirect(url_for('profile'))
@app.route('/save_color', methods=['POST'])
@login_required
def save_heatmap_color():
# Get the color value from the form
new_color = request.form['color']
current_user.heatmap_color = new_color
current_user.update()
# Back to profile
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
)
@app.route('/delete_account', methods=['POST'])
@login_required
def delete_account():
os.remove(current_user.profile_image)
current_user.delete()
return redirect(url_for('index'))
###########################################################
# Run the application # Run the application
if __name__ == '__main__': if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True) app.run(port=5000, debug=True)

View File

@ -1,8 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
import hashlib import hashlib
import sqlite3 import sqlite3
import os
import shutil
def con3(): def con3():
@ -10,40 +8,18 @@ def con3():
return conn return conn
### User ### ### User.py ###
def create_user_profile_image(user_id): def create_user(name: str, email: str, password: str):
script_directory = os.path.dirname(os.path.abspath(__file__))
source_path = os.path.join(script_directory, '../static/profile_images/no_avatar/user.jpg')
destination_directory = os.path.join(script_directory, '../static/profile_images/')
new_filename = f'user{user_id}-profile.jpg'
destination_path = os.path.join(destination_directory, new_filename)
shutil.copyfile(source_path, destination_path)
# Just save the part after static
static_index = destination_path.find('static')
relative_destination_path = destination_path[static_index:]
return relative_destination_path
def create_user(name: str, email: str, password: str, heatmap_color: str, profile_image: str = None):
password = hashlib.sha256(password.encode()).hexdigest() password = hashlib.sha256(password.encode()).hexdigest()
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO users (name, email, password, profile_image, heatmap_color, created_at, updated_at) VALUES " query = (f"INSERT INTO users (name, email, password, created_at, updated_at) VALUES ('{name}', '{email}', "
f"('{name}', '{email}', '{password}', '{profile_image}', '{heatmap_color}', '{now}', '{now}');") f"'{password}', '{now}', '{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
id = cursor.lastrowid
profile_image = create_user_profile_image(id)
query2 = f"UPDATE users SET profile_image = '{profile_image}' WHERE id = '{id}';"
cursor.execute(query2)
conn.commit() conn.commit()
conn.close() conn.close()
return id, profile_image return cursor.lastrowid
def get_user(id: int): def get_user(id: int):
@ -66,15 +42,12 @@ def get_user_by_email(email: str):
return user return user
def update_user(id: int, name: str, email: str, password: str, profile_image: str, heatmap_color: str): def update_user(id: int, name: str, email: str, password: str = None):
now = datetime.now().isoformat() now = datetime.now().isoformat()
if password: if password:
query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', " query = f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', updated_at = '{now}' WHERE id = {id};"
f"profile_image ='{profile_image}', heatmap_color = '{heatmap_color}', updated_at = '{now}' "
f"WHERE id = {id};")
else: else:
query = (f"UPDATE users SET name = '{name}', email = '{email}', profile_image ='{profile_image}', " query = f"UPDATE users SET name = '{name}', email = '{email}', updated_at = '{now}' WHERE id = {id};"
f"heatmap_color = '{heatmap_color}', updated_at = '{now}' WHERE id = {id};")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -84,20 +57,22 @@ def update_user(id: int, name: str, email: str, password: str, profile_image: st
def delete_user(id: int): def delete_user(id: int):
query = f"DELETE FROM users WHERE id = {id};" query = f"DELETE FROM habit_lists WHERE (SELECT list_id FROM habit_users WHERE user_id = {id}) = id;"
query2 = f"DELETE FROM users WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
cursor.execute(query2)
conn.commit() conn.commit()
conn.close() conn.close()
return cursor.lastrowid return cursor.lastrowid
### Habit ### ### Habit.py ###
def create_habit(list_id: int, name: str, note: str, times: int, unit: int, slot: int, checked: bool, count:int, streak: int): def create_habit(list_id: int, name: str, times: int, unit: int, slot: int, note: str | None=None):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, checked, count, streak, created_at, updated_at) " query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, created_at, updated_at) VALUES ('{list_id}', "
f"VALUES ('{list_id}', '{name}', '{note}', '{times}', '{unit}', '{slot}', '{checked}', '{count}', '{streak}', '{now}', '{now}');") f"'{name}', '{note}', '{times}', '{unit}', '{slot}', '{now}', '{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -127,7 +102,9 @@ def get_habits(list_id: int):
def get_heatmap_value(user_id: int, days: int): def get_heatmap_value(user_id: int, days: int):
# Berechnet das Datum, ab dem die Habits gezählt werden sollen
date = (datetime.now() - timedelta(days=days)).date() date = (datetime.now() - timedelta(days=days)).date()
print(date)
# Uses JOINs to get all Habits # Uses JOINs to get all Habits
query = (f"SELECT habits.id FROM habits " query = (f"SELECT habits.id FROM habits "
@ -144,32 +121,40 @@ def get_heatmap_value(user_id: int, days: int):
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
# Execute the queries
cursor.execute(query) cursor.execute(query)
all_habits = cursor.fetchall() all_habits = cursor.fetchall()
cursor.execute(query2) cursor.execute(query2)
checked_habits = cursor.fetchall() checked_habits = cursor.fetchall()
# Close the database connection
count = len(all_habits)
print(count)
count2 = len(checked_habits)
print(count2)
# Close the database connection
conn.close() conn.close()
# Calculate the percentage of checked Habits # Calculate the percentage of checked Habits
count = len(all_habits)
count2 = len(checked_habits)
if count > 0: if count > 0:
return int(count2 / count * 100) return int(count2 / count * 100)
else: else:
return 0 return 0
def habit_get_next_slot(list_id: int): def get_next_slot(list_id: int):
query = f"SELECT slot FROM habits WHERE list_id = {list_id} ORDER BY slot DESC LIMIT 1;" query = f"SELECT slot FROM habits WHERE list_id = {list_id} ORDER BY slot DESC LIMIT 1;"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
slot = cursor.fetchone() slot = cursor.fetchone()
conn.close() conn.close()
return slot[0] + 1 if slot else 1 return slot[0] + 1 if slot else 0
def habit_get_slots(list_id: int): def get_slots(list_id: int):
query = f"SELECT id, slot FROM habits WHERE list_id = {list_id} ORDER BY slot;" query = f"SELECT id, slot FROM habits WHERE list_id = {list_id} ORDER BY slot;"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -179,7 +164,7 @@ def habit_get_slots(list_id: int):
return slots return slots
def habit_update_slot(id: int, slot: int): def update_slot(id: int, slot: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"UPDATE habits SET slot = {slot}, updated_at = '{now}' WHERE id = {id};" query = f"UPDATE habits SET slot = {slot}, updated_at = '{now}' WHERE id = {id};"
conn = con3() conn = con3()
@ -192,20 +177,7 @@ def habit_update_slot(id: int, slot: int):
def update_habit(id: int, name: str, note: str, times: int, unit: int): def update_habit(id: int, name: str, note: str, times: int, unit: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"UPDATE habits SET name = '{name}', note = '{note}', times = {times}, unit = {unit}, updated_at = '{now}' " query = f"UPDATE habits SET name = {name}, note = {note}, times = {times}, unit = {unit}, updated_at = '{now}' WHERE id = {id};"
f"WHERE id = {id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
return cursor.lastrowid
def update_habit_statistics(id: int, checked: bool, count: int, streak: int):
now = datetime.now().isoformat()
query = (f"UPDATE habits SET checked = {checked}, count = {count}, streak = {streak}, updated_at = '{now}' "
f"WHERE id = {id};")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -223,10 +195,11 @@ def delete_habit(id: int):
conn.close() conn.close()
### HabitTracking ### ### HabitTrackings.py ###
def create_habitTracking(habit_id: int): def create_habitTrackings(habit_id: int):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"INSERT INTO habit_trackings (habit_id, created_at) VALUES ('{habit_id}','{now}');" query = (
f"INSERT INTO habit_trackings (habit_id, created_at) VALUES ('{habit_id}','{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -235,7 +208,7 @@ def create_habitTracking(habit_id: int):
return cursor.lastrowid return cursor.lastrowid
def get_habitTracking(id: int): def get_habitTrackings(id: int):
query = f"SELECT * FROM habit_trackings WHERE id = {id};" query = f"SELECT * FROM habit_trackings WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -245,7 +218,7 @@ def get_habitTracking(id: int):
return habit_tracking return habit_tracking
def get_habitTrackings(habit_id: int): def get_habitTrackings_by_habit_id(habit_id: int):
query = f"SELECT * FROM habit_trackings WHERE habit_id = {habit_id};" query = f"SELECT * FROM habit_trackings WHERE habit_id = {habit_id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -255,7 +228,7 @@ def get_habitTrackings(habit_id: int):
return habit_trackings return habit_trackings
def delete_habitTracking(id: int): def delete_habitTrackings(id: int):
query = f"DELETE FROM habit_trackings WHERE id = {id};" query = f"DELETE FROM habit_trackings WHERE id = {id};"
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
@ -264,17 +237,19 @@ def delete_habitTracking(id: int):
conn.close() conn.close()
### HabitList ### ### HabitList.py ###
def create_habitList(user_id: int, name: str, description: str, slot: int): def create_habitList(user_id: int, name: str, description: str):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = (f"INSERT INTO habit_lists (name, description, slot, created_at, updated_at) " query = (
f"VALUES ('{name}', '{description}', '{slot}', '{now}', '{now}');") f"INSERT INTO habit_lists (name, description, created_at, updated_at) VALUES ('{name}', '{description}', '{now}', '{now}');")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
query2 = (f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at, accepted)"
f" VALUES ('{user_id}', '{cursor.lastrowid}', '{now}', '{now}', 1);") query1 = f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at) VALUES ('{user_id}', '{cursor.lastrowid}', '{now}', '{now}');"
cursor.execute(query2)
cursor.execute(query1)
conn.commit() conn.commit()
conn.close() conn.close()
return cursor.lastrowid return cursor.lastrowid
@ -290,42 +265,8 @@ def get_habitList(id: int):
return habit_list return habit_list
def habitList_get_next_slot(user_id: int):
query = (f"SELECT slot FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} ORDER BY slot DESC LIMIT 1;")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
slot = cursor.fetchone()
conn.close()
return slot[0] + 1 if slot else 1
def habitList_get_slots(user_id: int):
query = (f"SELECT habit_lists.id, slot FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} ORDER BY slot;")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
slots = cursor.fetchall()
conn.close()
return slots
def habitList_update_slot(id: int, slot: int):
now = datetime.now().isoformat()
query = f"UPDATE habit_lists SET slot = {slot}, updated_at = '{now}' WHERE id = {id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
return cursor.lastrowid
def get_habitLists(user_id: int): def get_habitLists(user_id: int):
query = (f"SELECT habit_lists.*, habit_users.accepted FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id " query = f"SELECT habit_lists.* FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id WHERE habit_users.user_id = {user_id};"
f"WHERE habit_users.user_id = {user_id};")
conn = con3() conn = con3()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(query) cursor.execute(query)
@ -334,57 +275,6 @@ def get_habitLists(user_id: int):
return habit_lists return habit_lists
def get_unaccepted_habitLists(user_id: int):
query = (f"SELECT habit_lists.* FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} AND habit_users.accepted = false;")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
habit_lists = cursor.fetchall()
conn.close()
return habit_lists
def get_users(list_id: int):
query = (f"SELECT users.* FROM users JOIN habit_users ON users.id = habit_users.user_id WHERE "
f"habit_users.list_id = {list_id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
users = cursor.fetchall()
conn.close()
return users
def add_user(list_id: int, user_id: int):
now = datetime.now().isoformat()
query = (f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at)"
f" VALUES ('{user_id}', '{list_id}', '{now}', '{now}');")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def accept_List(list_id: int, user_id: int):
query = f"UPDATE habit_users SET accepted = 1 WHERE habit_users.user_id = {user_id} AND habit_users.list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def remove_user(list_id: int, user_id: int):
query = f"DELETE FROM habit_users WHERE user_id = {user_id} AND list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def update_habitList(id: int, name: str, description: str): def update_habitList(id: int, name: str, description: str):
now = datetime.now().isoformat() now = datetime.now().isoformat()
query = f"UPDATE habit_lists SET name = {name}, description = {description}, updated_at = '{now}' WHERE id = {id};" query = f"UPDATE habit_lists SET name = {name}, description = {description}, updated_at = '{now}' WHERE id = {id};"
@ -405,6 +295,16 @@ def delete_habitList(id: int):
conn.close() conn.close()
def get_users(list_id: int):
query = f"SELECT users.* FROM users JOIN habit_users ON users.id = habit_users.user_id WHERE habit_users.list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
users = cursor.fetchall()
conn.close()
return users
if __name__ == "__main__": if __name__ == "__main__":
habits = get_habits(1) habits = get_habits(1)
for habit in habits: for habit in habits:

View File

@ -21,7 +21,7 @@ def init_db():
def create_migration(conn: Connection, migration: str): def create_migration(conn: Connection, migration: str):
conn.execute(f""" conn.execute(f"""
INSERT INTO migrations (file, created_at) VALUES ('{migration}', '{datetime.datetime.now()}'); INSERT INTO migrations (file, created_at) VALUES ("{migration}", "{datetime.datetime.now()}");
""") """)
""" """
@ -29,7 +29,7 @@ Returns True, if the migration has already been migrates once.
""" """
def check_migration(conn: Connection, migration: str): def check_migration(conn: Connection, migration: str):
res = conn.cursor().execute(f""" res = conn.cursor().execute(f"""
SELECT EXISTS(SELECT file FROM migrations WHERE file = '{migration}'); SELECT EXISTS(SELECT file FROM migrations WHERE file = "{migration}");
""") """)
return res.fetchone()[0] == 1 return res.fetchone()[0] == 1

View File

@ -1 +0,0 @@
DROP TABLE users;

View File

@ -1,10 +0,0 @@
CREATE TABLE IF NOT EXISTS users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
profile_image TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -1 +0,0 @@
DROP TABLE habits;

View File

@ -1,16 +0,0 @@
CREATE TABLE IF NOT EXISTS habits
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
list_id INTEGER NOT NULL,
name TEXT NOT NULL,
note TEXT,
times INTEGER NOT NULL,
unit INTEGER,
slot INTEGER NOT NULL,
checked BOOLEAN NOT NULL,
count INTEGER NOT NULL,
streak INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (list_id) REFERENCES habit_lists(id)
);

View File

@ -1 +0,0 @@
DROP TABLE users;

View File

@ -1,11 +0,0 @@
CREATE TABLE IF NOT EXISTS users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
profile_image TEXT NOT NULL,
heatmap_color TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -1,2 +0,0 @@
ALTER TABLE habit_users
ADD COLUMN accepted BOOLEAN DEFAULT false;

View File

@ -1 +0,0 @@
DROP TABLE habit_lists;

View File

@ -1,8 +0,0 @@
CREATE TABLE IF NOT EXISTS habit_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
slot INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -1,18 +1,17 @@
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from datetime import timedelta
from models.HabitTracking import HabitTracking from models.HabitTrackings import HabitTrackings
from db.SQLiteClient import (create_habit, get_habit, update_habit, delete_habit, habit_get_next_slot, habit_get_slots, from db.SQLiteClient import update_slot, create_habit, get_habit, delete_habit, get_next_slot, \
habit_update_slot, get_habitTrackings, get_habitList, update_habit_statistics) get_habitTrackings_by_habit_id, get_slots, update_habit, get_habitList, get_habitLists
# unit will be represented by integers like this: # Unit wird als Integers wie folgt gemessen:
# 0: day # 0: Tag
# 1: week (default) # 1: Woche (Default)
# 2: month # 2: Monat
# 3: year # 3: Jahr
@dataclass @dataclass
class Habit: class Habit:
@ -23,120 +22,103 @@ class Habit:
times: int times: int
unit: int unit: int
slot: int slot: int
checked: bool
count: int
streak: int
percentage: int = 0 percentage: int = 0
def __post_init__(self): def __post_init__(self):
self.load_statistics() self.fill_statistics()
@staticmethod @staticmethod
def create(list_id: int, name: str, times: int, note: str = None, unit: int = 1, checked: bool = False, count: int = 0, streak: int = 0): def create(list_id: int, name: str, times: int, note: str | None = None, unit: int | None = 1):
slot = habit_get_next_slot(list_id) slot = get_next_slot(list_id)
id = create_habit(list_id, name, note, times, unit, slot, checked, count, streak) print(slot)
return Habit(id, list_id, name, note, times, unit, slot, checked, count, streak) id = create_habit(list_id, name, times, unit, slot, note)
return Habit(id, list_id, name, note, times, unit, slot)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
habit = get_habit(id) habit = get_habit(id)
return Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6], habit[7], habit[8], habit[9]) if habit else None habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6]) if habit else None
return habit
# Updates: name, note, times, unit def update(self, name: str=None, note: str=None, times: int=None, unit: int=None):
def update(self): update_habit(self.id, name, note, times, unit)
update_habit(self.id, self.name, self.note, self.times, self.unit) if name is not None:
self.name = name
if note is not None:
self.note = note
if times is not None:
self.times = times
if unit is not None:
self.unit = unit
# Updates the slot and reorders the HabitList accordingly
def update_slot(self, new_slot: int): def update_slot(self, new_slot: int):
# Fetches a list with the following structure [(id, slot), (id, slot), ...] slots = get_slots(self.list_id)
slots = habit_get_slots(self.list_id) if new_slot > self.slot:
slots = slots[self.slot:new_slot]
# Splits the list depending on whether the new slot is higher or lower than the current one
if new_slot > self.slot: # Example self.slot=1 new_slot=4
slots = slots[self.slot:new_slot] # Expected list: [(id, 2), (id, 3), (id, 4)]
for slot in slots: for slot in slots:
habit_update_slot(slot[0], slot[1]-1) update_slot(slot[0], slot[1]-1)
if new_slot < self.slot: # Example self.slot=4 new_slot=1 if new_slot < self.slot:
slots = slots[new_slot-1:self.slot-1] # Expected list: [(id, 1), (id, 2), (id, 3)] slots = slots[new_slot-1:self.slot-1]
for slot in slots: for slot in slots:
habit_update_slot(slot[0], slot[1]+1) update_slot(slot[0], slot[1]+1)
update_slot(self.id, new_slot)
# Update the slot of the current habit
habit_update_slot(self.id, new_slot)
# Deletes the Habit
def delete(self): def delete(self):
# Reorders the slots slots = get_slots(self.list_id)[self.slot+1:]
slots = habit_get_slots(self.list_id)[self.slot+1:] print(slots)
for slot in slots: for slot in slots:
habit_update_slot(slot[0], slot[1] - 1) update_slot(slot[0], slot[1] - 1)
# Deletes all track-records associated with the Habit
trackings = self.get_habitTrackings()
for tracking in trackings:
tracking.delete()
# Deletes the current Habit
delete_habit(self.id) delete_habit(self.id)
# Returns all track-records for a Habit def get_habitTrackings(self) -> list[HabitTrackings]:
def get_habitTrackings(self) -> list:
trackings = [] trackings = []
for rawTracking in get_habitTrackings(self.id): for rawTracking in get_habitTrackings_by_habit_id(self.id):
trackings.append(HabitTracking(rawTracking[0], rawTracking[1], trackings.append(HabitTrackings(rawTracking[0], rawTracking[1], datetime.strptime(rawTracking[2], "%Y-%m-%dT%H:%M:%S.%f")))
datetime.strptime(rawTracking[2], "%Y-%m-%dT%H:%M:%S.%f")))
return trackings return trackings
# Returns the HabitList in which the Habit is located
def habit_list(self) -> list:
from models.HabitList import HabitList
raw_habitLists = get_habitList(self.list_id)
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2], raw_habitLists[3]) if raw_habitLists else None
# Loads the progress and checks if the streak is not broken
def load_statistics(self):
today = datetime.today().date()
yesterday = today - timedelta(days=1)
tracking_dates = [tracking.created_at.date() for tracking in self.get_habitTrackings()]
if not today in tracking_dates:
self.checked = False
if not yesterday in tracking_dates:
self.streak = 0
update_habit_statistics(self.id, self.count, self.count, self.streak)
# Reset count based on time unit
if self.unit == 0:
self.count = 0
elif self.unit == 1 and today.weekday() == 0:
self.count = 0
elif self.unit == 2 and today.day == 1:
self.count = 0
elif self.unit == 3 and today.month == 1 and today.day == 1:
self.count = 0
self.percentage = int(self.count / self.times * 100)
# Saves the progress count and streak
def fill_statistics(self): def fill_statistics(self):
self.checked = True count = 0
self.streak += 1
self.count += 1
update_habit_statistics(self.id, self.checked, self.count, self.streak)
# Turns the statistics back to the unchecked state
def reset_statistics(self):
self.checked = False self.checked = False
self.streak -= 1 for tracking in self.get_habitTrackings():
self.count -= 1 if tracking.created_at == datetime.today():
update_habit_statistics(self.id, self.checked, self.count, self.streak) self.checked = True
# day
if self.unit == 0:
if tracking.created_at == datetime.today():
# self.checked = True
count += 1
# week
elif self.unit == 1:
if tracking.created_at.isocalendar()[1] == datetime.today().isocalendar()[1]:
# self.checked = True
count += 1
# month
elif self.unit == 2:
if tracking.created_at.month == datetime.today().month:
# self.checked = True
count += 1
# year
elif self.unit == 3:
if tracking.created_at.year == datetime.today().year:
# self.checked = True
count += 1
self.percentage = int(count / self.times * 100)
def to_json(self): def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
def habit_list(self):
from models.HabitList import HabitList
raw_habitLists = get_habitList(self.list_id)
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2], datetime.strptime(raw_habitLists[3], "%Y-%m-%dT%H:%M:%S.%f"), datetime.strptime(raw_habitLists[4], "%Y-%m-%dT%H:%M:%S.%f")) if raw_habitLists else None

View File

@ -1,10 +1,10 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date, datetime
from db.SQLiteClient import create_habitTrackings, get_habitTrackings, delete_habitTrackings, create_habitList, \
get_habitList, get_habits, get_users
from models.Habit import Habit from models.Habit import Habit
from models.User import User from models.User import User
from db.SQLiteClient import (create_habitList, get_habitList, get_habits, get_users, add_user, remove_user,
update_habitList, delete_habitList, habitList_get_next_slot, habitList_get_slots,
habitList_update_slot)
@dataclass @dataclass
@ -12,89 +12,37 @@ class HabitList:
id: int id: int
name: str name: str
description: str description: str
slot: int created_at: date
updated_at: date
habits: list = None habits: list = None
@staticmethod @staticmethod
def create(user_id: int, name: str, description: str): def create(user_id: int, name: str, description: str):
slot = habitList_get_next_slot(user_id) id = create_habitList(user_id, name, description)
id = create_habitList(user_id, name, description, slot) return HabitList(id, name, description, datetime.now(), datetime.now())
return HabitList(id, name, description, slot)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
habitList = get_habitList(id) habitList = get_habitList(id)
return HabitList(habitList[0], habitList[1], habitList[2], habitList[3]) if habitList else None return HabitList(habitList[0], habitList[1], habitList[2], datetime.strptime(habitList[3], "%Y-%m-%dT%H:%M:%S.%f"), datetime.strptime(habitList[4], "%Y-%m-%dT%H:%M:%S.%f")) if habitList else None
def delete(self):
delete_habitTrackings(self.id)
# Updates: name, description def get_habits(self):
def update(self):
update_habitList(self.id, self.name, self.description)
# Updates the slot and reorders the HabitLists accordingly
def update_slot(self,user_id: int, new_slot: int):
# Fetches a list with the following structure [(id, slot), (id, slot), ...]
slots = habitList_get_slots(user_id)
# Splits the list depending on whether the new slot is higher or lower than the current one
if new_slot > self.slot: # Example self.slot=1 new_slot=4
slots = slots[self.slot:new_slot] # Expected list: [(id, 2), (id, 3), (id, 4)]
for slot in slots:
habitList_update_slot(slot[0], slot[1]-1)
if new_slot < self.slot: # Example self.slot=4 new_slot=1
slots = slots[new_slot-1:self.slot-1] # Expected list: [(id, 1), (id, 2), (id, 3)]
for slot in slots:
habitList_update_slot(slot[0], slot[1]+1)
# Update the slot of the current habitList
habitList_update_slot(self.id, new_slot)
# Deletes the HabitList | The id of the current user is necessary
def delete(self, user_id):
# Reorders the slots
slots = habitList_get_slots(user_id)[self.slot+1:]
for slot in slots:
habitList_update_slot(slot[0], slot[1] - 1)
if len(get_users(self.id)) > 1:
self.remove_user(user_id)
else:
for habit in self.get_habits():
habit.delete()
delete_habitList(self.id)
# Returns the Habits connected with the HabitList
def get_habits(self) -> list:
raw_habits = get_habits(self.id) raw_habits = get_habits(self.id)
habits = [] habits = []
for habit in raw_habits: for habit in raw_habits:
habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6], habit[7], habit[8], habit[9]) habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6])
habits.append(habit) habits.append(habit)
return habits return habits
def get_users(self):
# Returns the Users connected with the HabitList
def get_users(self) -> list:
raw_users = get_users(self.id) raw_users = get_users(self.id)
users = [] users = []
for user in raw_users: for user in raw_users:
user = User(user[0], user[1], user[2], user[3], user[4], user[5]) user = User(user[0], user[1], user[2], user[3])
users.append(user) users.append(user)
return users return users
# Adds a User by email to the HabitList
def add_user(self, user: User):
if user:
add_user(self.id, user.id)
else:
return None
# Removes a User from the HabitList
def remove_user(self, user_id):
remove_user(self.id, user_id)

View File

@ -1,26 +0,0 @@
from datetime import date, datetime
from dataclasses import dataclass
from db.SQLiteClient import create_habitTracking, get_habitTracking, delete_habitTracking
@dataclass
class HabitTracking:
id: int
habit_id: int
created_at: date
@staticmethod
def create(habit_id: int):
id = create_habitTracking(habit_id)
return HabitTracking(id, habit_id, datetime.now())
@staticmethod
def get(id: int):
habitTrackings = get_habitTracking(id)
return HabitTracking(habitTrackings[0], habitTrackings[1],
datetime.strptime(habitTrackings[2], "%Y-%m-%dT%H:%M:%S.%f")) \
if habitTrackings else None
# Deletes the HabitTracking
def delete(self):
delete_habitTracking(self.id)

24
models/HabitTrackings.py Normal file
View File

@ -0,0 +1,24 @@
from dataclasses import dataclass
from datetime import date, datetime
from db.SQLiteClient import create_habitTrackings, get_habitTrackings, delete_habitTrackings
@dataclass
class HabitTrackings:
id: int
habit_id: int
created_at: date
@staticmethod
def create(habit_id: int, times: int):
id = create_habitTrackings(habit_id)
return HabitTrackings(id, habit_id, datetime.now())
@staticmethod
def get(id: int):
habitTrackings = get_habitTrackings(id)
return HabitTrackings(habitTrackings[0], habitTrackings[1], datetime.strptime(habitTrackings[2], "%Y-%m-%dT%H:%M:%S.%f")) if habitTrackings else None
def delete(self):
delete_habitTrackings(self.id)

View File

@ -1,95 +1,53 @@
from datetime import datetime from datetime import datetime
from flask_login import UserMixin from flask_login import UserMixin
from db.SQLiteClient import (create_user, get_user, get_user_by_email, update_user, delete_user, from db.SQLiteClient import create_user, get_user, get_user_by_email, get_habits, delete_user, update_user, \
get_habitLists, get_heatmap_value, accept_List, get_unaccepted_habitLists) get_habitLists
class User(UserMixin): class User(UserMixin):
def __init__(self, id: int, name: str, email: str, password: str=None, profile_image:str=None, heatmap_color: str=None): def __init__(self, id: int, name: str, email: str, password: str | None = None):
self.id = id self.id = id
self.name = name self.name = name
self.email = email self.email = email
self.password = password self.password = password
self.profile_image = profile_image
self.heatmap_color = heatmap_color
@staticmethod @staticmethod
def create(name: str, email: str, password: str): def create(name: str, email: str, password: str):
heatmap_color = "#00FF00" id = create_user(name, email, password)
id, profile_image = create_user(name, email, password, heatmap_color) return User(id, name, email)
return User(id=id, name=name, email=email, profile_image=profile_image, heatmap_color=heatmap_color)
@staticmethod @staticmethod
def get(id: int): def get(id: int):
user = get_user(id) user = get_user(id)
return User(user[0], user[1], user[2], user[3], user[4], user[5]) if user else None return User(user[0], user[1], user[2], user[3]) if user else None
@staticmethod @staticmethod
def get_by_email(email: str): def get_by_email(email: str):
user = get_user_by_email(email) user = get_user_by_email(email)
return User(user[0], user[1], user[2], user[3], user[4], user[5]) if user else None return User(user[0], user[1], user[2], user[3]) if user else None
# Updates: name, email, password
def update(self): def update(self):
update_user(self.id, self.name, self.email, self.password if self.password else None, self.profile_image, self.heatmap_color) update_user(self.id, self.name, self.email, self.password if self.password else None)
# Deletes the User
def delete(self): def delete(self):
# calls the deletion of the users habitLists
habitLists = self.get_habitLists()
for habitList in habitLists:
habitList.delete(self.id)
# deletes the user
delete_user(self.id) delete_user(self.id)
# def get_habits(self):
# raw_habits = get_habits(self.id)
# habits = []
# for habit in raw_habits:
# habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6])
# habits.append(habit)
# return habits
# Returns all HabitLists connected with the user def get_habitLists(self):
def get_habitLists(self) -> list:
from models.HabitList import HabitList from models.HabitList import HabitList
raw_habitLists = get_habitLists(self.id) raw_habitLists = get_habitLists(self.id)
habitLists = [] habitLists = []
for habitList in raw_habitLists: for habitList in raw_habitLists:
accepted = habitList[6] habitList = HabitList(habitList[0], habitList[1], habitList[2], datetime.strptime(habitList[3], "%Y-%m-%dT%H:%M:%S.%f"), datetime.strptime(habitList[4], "%Y-%m-%dT%H:%M:%S.%f"))
habitList = HabitList(habitList[0], habitList[1], habitList[2], habitList[3])
if accepted == 1:
habitLists.append(habitList)
return habitLists
def get_unaccepted_habitLists(self) -> list:
from models.HabitList import HabitList
raw_habitLists = get_unaccepted_habitLists(self.id)
habitLists = []
for habitList in raw_habitLists:
habitList = HabitList(habitList[0], habitList[1], habitList[2], habitList[3])
habitLists.append(habitList) habitLists.append(habitList)
return habitLists return habitLists
# Returns all heatmap-values from the last 28 days
def get_heatmap(self) -> tuple:
# get current day of week as integer. monday is 0 and sunday is 6
weekday = 6 - datetime.today().weekday()
heatmap = [100]
# append the heatmap values of the current week
for day in range(0, weekday):
heatmap.append(0)
for day in range (0, 28-weekday):
value = get_heatmap_value(self.id, day)
heatmap.append(value)
heatmap.reverse()
day = 27-weekday
return heatmap, day
def accept_List(self, HabitList_id):
accept_List(HabitList_id, self.id)

View File

@ -1,3 +0,0 @@
pillow~=10.2.0
flask~=3.0.0
flask-login~=0.6.3

View File

@ -1,73 +0,0 @@
#heatmap {
display: grid;
grid-template-columns: repeat(7, 0fr); /* 7 Tage in einer Woche */
gap: 5px;
width: 100%;
table-layout: fixed;
}
.day {
width: 50px;
height: 50px;
border: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
table-layout: fixed;
}
@media (max-width: 1400px) {
.day {
width: 40px;
height: 40px;
}
}
@media (max-width: 1200px) {
.day {
width: 35px;
height: 35px;
}
}
@media (max-width: 992px) {
.day {
width: 30px;
height: 30px;
}
}
@media (max-width: 767px) {
.day {
width: 50px;
height: 50px;
}
}
@media (max-width: 450px) {
.day {
width: 40px;
height: 40px;
}
}
@media (max-width: 400px) {
.day {
width: 35px;
height: 35px;
}
}
@media (max-width: 350px) {
.day {
width: 30px;
height: 30px;
}
}
@media (max-width: 300px) {
.day {
width: 25px;
height: 25px;
}
}

View File

@ -1,73 +0,0 @@
/* Profile image */
.profile-image-container {
position: relative;
width: 150px; /* Adjust the size as needed */
height: 150px; /* Adjust the size as needed */
}
.profile-image {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.profile-image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Grey overlay */
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 16px;
opacity: 0; /* Initially hidden */
transition: opacity 0.3s ease;
cursor: pointer;
}
.profile-image-overlay:hover {
opacity: 1; /* Show overlay on hover */
}
.profile-image-overlay span {
/* Center the text and make it bold */
text-align: center;
font-weight: bold;
}
.profile-image-overlay:hover span {
/* Style the text when hovering */
color: white;
}
/* Edit-Modal Close Button */
.close-icon {
fill: #aaa;
cursor: pointer;
}
.close-icon:hover {
fill: #777; /* Change the color to whatever you'd like on hover */
}
.fade-out {
-webkit-animation: fadeOut 3s forwards;
animation: fadeOut 3s forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; display: none; }
}
@-webkit-keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; display: none; }
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 30" version="1.1" x="0px" y="0px"><title>324324</title><desc>Created with Sketch.</desc><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M12.7296635,21.1661847 C12.7296635,21.6239997 12.3585312,21.995132 11.9007162,21.995132 C11.4429012,21.995132 11.0717688,21.6239997 11.0717688,21.1661847 L11.0717688,20.0609215 C11.0717688,19.6031065 11.4429012,19.2319741 11.9007162,19.2319741 C12.3585312,19.2319741 12.7296635,19.6031065 12.7296635,20.0609215 L12.7296635,21.1661847 Z" fill="#000000" fill-rule="nonzero"/><path d="M8.30861091,20.5510749 C8.30861091,21.0088899 7.93747853,21.3800223 7.47966354,21.3800223 C7.02184855,21.3800223 6.65071617,21.0088899 6.65071617,20.5510749 L6.65071617,19.4458117 C6.65071617,18.9879968 7.02184855,18.6168644 7.47966354,18.6168644 C7.93747853,18.6168644 8.30861091,18.9879968 8.30861091,19.4458117 L8.30861091,20.5510749 Z" fill="#000000" fill-rule="nonzero" transform="translate(7.479664, 19.998443) rotate(20.000000) translate(-7.479664, -19.998443) "/><path d="M17.1507162,20.5510749 C17.1507162,21.0088899 16.7795838,21.3800223 16.3217688,21.3800223 C15.8639538,21.3800223 15.4928214,21.0088899 15.4928214,20.5510749 L15.4928214,19.4458117 C15.4928214,18.9879968 15.8639538,18.6168644 16.3217688,18.6168644 C16.7795838,18.6168644 17.1507162,18.9879968 17.1507162,19.4458117 L17.1507162,20.5510749 Z" fill="#000000" fill-rule="nonzero" transform="translate(16.321769, 19.998443) scale(-1, 1) rotate(20.000000) translate(-16.321769, -19.998443) "/><path d="M21.0631579,18.5616012 C21.0631579,19.0194162 20.6920255,19.3905486 20.2342105,19.3905486 C19.7763955,19.3905486 19.4052632,19.0194162 19.4052632,18.5616012 L19.4052632,17.4563381 C19.4052632,16.9985231 19.7763955,16.6273907 20.2342105,16.6273907 C20.6920255,16.6273907 21.0631579,16.9985231 21.0631579,17.4563381 L21.0631579,18.5616012 Z" fill="#000000" fill-rule="nonzero" transform="translate(20.234211, 18.008970) scale(-1, 1) rotate(35.000000) translate(-20.234211, -18.008970) "/><path d="M4.26315789,18.5616012 C4.26315789,19.0194162 3.89202552,19.3905486 3.43421053,19.3905486 C2.97639554,19.3905486 2.60526316,19.0194162 2.60526316,18.5616012 L2.60526316,17.4563381 C2.60526316,16.9985231 2.97639554,16.6273907 3.43421053,16.6273907 C3.89202552,16.6273907 4.26315789,16.9985231 4.26315789,17.4563381 L4.26315789,18.5616012 Z" fill="#000000" fill-rule="nonzero" transform="translate(3.434211, 18.008970) rotate(35.000000) translate(-3.434211, -18.008970) "/><path d="M2.18356954,13.4118965 C3.65553254,16.0777919 8.10236709,18.625 12,18.625 C15.8799564,18.625 20.3024429,16.1029471 21.7976771,13.4455402 C22.0007957,13.0845475 21.8728134,12.6272449 21.5118207,12.4241264 C21.150828,12.2210078 20.6935255,12.3489901 20.4904069,12.7099828 C19.2639874,14.8896385 15.3442219,17.125 12,17.125 C8.64124122,17.125 4.70099679,14.8679737 3.49670178,12.686856 C3.29648738,12.3242446 2.84022687,12.1925958 2.47761541,12.3928102 C2.11500396,12.5930246 1.98335513,13.0492851 2.18356954,13.4118965 Z" fill="#000000" fill-rule="nonzero"/></g><text x="0" y="39" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Sonya Nikolaeva</text><text x="0" y="44" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 30" version="1.1" x="0px" y="0px"><title>123123</title><desc>Created with Sketch.</desc><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><path d="M12,18.625 C16.6985283,18.625 22.25,14.8199617 22.25,11.9375 C22.25,9.05503835 16.6985283,5.25 12,5.25 C7.30147173,5.25 1.75,9.05503835 1.75,11.9375 C1.75,14.8199617 7.30147173,18.625 12,18.625 Z M12,17.125 C8.0358394,17.125 3.25,13.8447343 3.25,11.9375 C3.25,10.0302657 8.0358394,6.75 12,6.75 C15.9641606,6.75 20.75,10.0302657 20.75,11.9375 C20.75,13.8447343 15.9641606,17.125 12,17.125 Z" fill="#000000" fill-rule="nonzero"/><path d="M12,16.25 C14.381728,16.25 16.3125,14.319228 16.3125,11.9375 C16.3125,9.55577202 14.381728,7.625 12,7.625 C9.61827202,7.625 7.6875,9.55577202 7.6875,11.9375 C7.6875,14.319228 9.61827202,16.25 12,16.25 Z M12,14.75 C10.4466991,14.75 9.1875,13.4908009 9.1875,11.9375 C9.1875,10.3841991 10.4466991,9.125 12,9.125 C13.5533009,9.125 14.8125,10.3841991 14.8125,11.9375 C14.8125,13.4908009 13.5533009,14.75 12,14.75 Z" fill="#000000" fill-rule="nonzero"/></g><text x="0" y="39" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Sonya Nikolaeva</text><text x="0" y="44" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,213 +0,0 @@
// Erstellen der Heatmap
function createHeatmap(data, day) {
const heatmapContainer = document.getElementById('heatmap');
const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
for (let i = 0; i < 7; i++) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.textContent = days[i];
heatmapContainer.appendChild(dayElement);
}
// Aktuelles Datum des Montags in der neuen linken Spalte
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 7; j++) {
const opacity = data[i * 7 + j] / (Math.max(...data) <= 0 ? 1 : Math.max(...data)); // Berechne die Opazität basierend auf Aktivitätsanzahl
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.style.backgroundColor = `rgba(${color}, ${opacity})`;
if (day === i * 7 + j){
dayElement.style.borderColor = `rgba(0, 0, 0)`;
dayElement.style.borderWidth = "2px";
}
heatmapContainer.appendChild(dayElement);
}
}
}
// Löschen der Heatmap
function deleteHeatmap() {
const heatmapContainer = document.getElementById('heatmap');
const dayElements = heatmapContainer.getElementsByClassName('day');
// Convert HTMLCollection to array and iterate to remove each element
Array.from(dayElements).forEach(element => {
element.remove();
});
}
// Animation der Progressbar
function checkCompletionAndAnimate(habitId, percentage) {
const progressBar = document.getElementById("progress-bar-" + habitId);
const habitBlock = document.getElementById("habit-" + habitId);
if (percentage === 100) {
progressBar.style.backgroundColor = "green";
habitBlock.classList.add("animate-bounce");
setTimeout(function () {
habitBlock.classList.remove("animate-bounce");
}, 2000);
} else {
progressBar.style.backgroundColor = "";
habitBlock.classList.remove("animate-bounce");
}
}
// Senden einer Post-Request, sobald ein Habit abgehackt wird
function sendPostRequest(checkboxId) {
// Get the habit id from the checkbox id attribute
const habitId = checkboxId;
// Make a POST request to /check with the habit id
axios.post('/check', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Set the percentage of the habit. percentage received as integer
const percentage = response.data.percentage;
const progressBar = document.getElementById("progress-bar-" + habitId);
progressBar.style.width = percentage + "%";
checkCompletionAndAnimate(habitId, percentage);
const streak = response.data.streak;
const streakSymbol = document.getElementById("streak-" + habitId);
streakSymbol.innerText = streak > 0 ? streak.toString() + " 🔥" : "";
const heatmapValues = response.data.heatmap;
deleteHeatmap()
createHeatmap(heatmapValues, day, color)
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
// Senden einer Post-Request, sobald ein Habit gelöscht wird
function deleteHabit(habitId) {
// Make a POST request to /delete with the habit id
axios.post('/delete', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
const habitElement = document.getElementById("habit-" + habitId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
// Senden einer Post-Request, sobald eine HabitList gelöscht wird
function deleteList(listId) {
// Make a POST request to /delete with the habit id
axios.post('/delete-list', {listId: listId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
let habitElement = document.getElementById("simple-tabpanel-" + listId);
habitElement.remove();
habitElement = document.getElementById("tab-" + listId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
document.addEventListener('DOMContentLoaded', () => {
const elements = document.querySelectorAll('.task-list').values()
// loop through the elements
for (let el of elements) {
Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
const habitId = el.children[evt.newIndex].id.split('-')[1];
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;
axios.post('/reorder-habit', {habitId: habitId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
}
});
document.addEventListener('DOMContentLoaded', () => {
const listElements = document.querySelectorAll('.nav-tabs').values();
// Loop through the list elements
for (let listEl of listElements) {
Sortable.create(listEl, {
handle: '.nav-link', // Use the nav-link as the handle for dragging
animation: 150,
onEnd: function (evt) {
const listId = evt.item.id.split('-')[1];
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;
// Send a POST request to reorder the list
axios.post('/reorder-list', { listId: listId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
}
});
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
// Aktualisieren der Uhrzeit
function updateCurrentTime() {
const currentTimeElement = document.getElementById('current-time');
const currentDate = new Date();
const options = { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false };
const currentDateTime = currentDate.toLocaleString('de-DE', options);
currentTimeElement.innerText = currentDateTime.replace(',', ',') + ' ' + currentDate.toLocaleString('de-DE', { weekday: 'long' });
}
// Erstellt die Heatmap mit den simulierten Daten
createHeatmap(activityData, day, color);
updateCurrentTime();
setInterval(updateCurrentTime, 1000);

View File

@ -1,195 +0,0 @@
document.addEventListener("DOMContentLoaded", function() {
// Get elements
const profileImage = document.getElementById("profileImage");
const profileImageOverlay = document.getElementById("profileImageOverlay");
const profileImageInput = document.getElementById("profileImageInput");
const uploadForm = document.getElementById("uploadForm");
const editButton = document.getElementById("editButton");
const saveChangesButton = document.getElementById("saveChangesButton");
const editForm = document.getElementById("editForm");
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
const submitPasswordChangeButton = document.getElementById("submitPasswordChange");
const DeleteAccountButton = document.getElementById('deleteAccountButton')
const confirmDeleteModal = new bootstrap.Modal(document.getElementById('confirmDeleteModal'));
const ConfirmDeleteButton = document.getElementById('confirmDeleteButton')
const deleteAccountForm = document.getElementById('deleteAccountForm')
// Open file input when profile image is clicked
profileImageOverlay.addEventListener("click", function() {
profileImageInput.click();
});
// Change profile image when a new file is selected
profileImageInput.addEventListener("change", function() {
const file = this.files[0];
const reader = new FileReader();
reader.onload = function(e) {
profileImage.src = e.target.result;
};
reader.readAsDataURL(file);
// Submit the form
uploadForm.submit();
});
// Add event listener to edit button to open modal
editButton.addEventListener("click", function() {
editModal.show();
});
// Add event listener to save changes button to submit form
saveChangesButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validateForm()
.then(isValid => {
if (isValid) {
editForm.submit();
}
})
.catch(error => {
// Handle validation error
console.log("Account Form validation failed", error);
});
});
// Function to perform client-side validation
async function validateForm() {
let isValid = true;
isValid = validateInput("newName", "nameFeedback", "Bitte geben Sie einen neuen Namen ein.") && isValid;
isValid = validateEmail("newEmail", "emailFeedback", "Bitte geben Sie eine gültige E-Mail-Adresse ein.") && isValid;
try {
const passwordValid = await validatePassword("password", "passwordFeedback", "Bitte geben Sie Ihr Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
return isValid;
}
// Function to validate input fields
function validateInput(inputId, feedbackId, errorMessage) {
const input = document.getElementById(inputId);
const feedback = document.getElementById(feedbackId);
if (!input.value.trim()) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate email
function validateEmail(emailId, feedbackId, errorMessage) {
const input = document.getElementById(emailId);
const feedback = document.getElementById(feedbackId);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // Regular expression for email validation
if (!input.value.trim()) {
feedback.textContent = "Bitte geben Sie eine neue E-Mail-Adresse ein.";
input.classList.add("is-invalid");
return false;
} else if (!emailRegex.test(input.value.trim())) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate password
function validatePassword(passwordId, passwordFeedbackId, errorMessage) {
return new Promise((resolve, reject) => {
const passwordInput = document.getElementById(passwordId);
const passwordFeedback = document.getElementById(passwordFeedbackId);
const password = passwordInput.value; // Get the password entered by the user
if (!passwordInput.value.trim()) {
passwordFeedback.textContent = errorMessage;
passwordInput.classList.add("is-invalid");
reject(false);
} else {
axios.post('/check_password', { password: password })
.then(response => {
if (!response.data.valid) {
passwordFeedback.textContent = "Falsches Passwort.";
passwordInput.classList.add("is-invalid");
passwordInput.value = "";
reject(false);
} else {
passwordInput.classList.remove("is-invalid");
resolve(true);
}
})
.catch(error => {
console.error('Error checking password:', error);
reject(false);
});
}
});
}
submitPasswordChangeButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validatePasswordChangeForm()
.then(isValid => {
if (isValid) {
document.getElementById("editPasswordForm").submit();
}
})
.catch(error => {
// Handle validation error
console.log("Password change form validation failed", error);
});
});
async function validatePasswordChangeForm() {
let isValid = true;
try {
const passwordValid = await validatePassword("oldPassword", "oldPasswordFeedback", "Bitte geben Sie Ihr altes Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
isValid = validateInput("newPassword", "newPasswordFeedback", "Bitte geben Sie Ihr neues Passwort ein.") && isValid;
isValid = validateInput("confirmPassword", "confirmPasswordFeedback", "Bitte bestätigen Sie Ihr neues Passwort.") && isValid;
// Check if new password matches confirm password
const newPassword = document.getElementById("newPassword").value.trim();
const confirmPassword = document.getElementById("confirmPassword").value.trim();
if (newPassword !== confirmPassword) {
document.getElementById("confirmPasswordFeedback").textContent = "Die Passwörter stimmen nicht überein.";
document.getElementById("confirmPassword").classList.add("is-invalid");
isValid = false;
} else {
document.getElementById("confirmPasswordFeedback").textContent = "";
document.getElementById("confirmPassword").classList.remove("is-invalid");
}
return isValid;
}
// Add event listener to edit button to open modal
DeleteAccountButton.addEventListener("click", function() {
confirmDeleteModal.show();
});
//Submit delete account form
ConfirmDeleteButton.addEventListener('click', function () {
deleteAccountForm.submit();
});
});

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<div class="card bg-light mt-4"> <div class="card">
<h1 class="card-header">Login</h1> <h5 class="card-header">Login</h5>
<div class="card-body column"> <div class="card-body column">
<form method="POST" action="/login"> <form method="POST" action="/login">
<div class="mb-3 row"> <div class="mb-3 row">

View File

@ -1,38 +1,33 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<div class="card bg-light mt-4 p-5"> <div class="column">
<div class="column"> <h3>Registrieren</h3>
<h1>Registrieren</h1> <form method="POST" action="/signup">
<form method="POST" action="/signup"> <div class="mb-3">
<div class="mb-3"> <label for="email" class="form-label">Email-Adresse</label>
<label for="email" class="form-label">Email-Adresse</label> <input type="email" class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" name="email" placeholder="name@example.com" value="{{ email }}">
<input type="email" class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" <div class="invalid-feedback">
name="email" placeholder="name@example.com" value="{{ email }}"> {{ errors.get('email', '') }}
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
</div> </div>
<div class="mb-3"> </div>
<label for="name" class="form-label">Name</label> <div class="mb-3">
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name" <label for="name" class="form-label">Name</label>
name="name" placeholder="Max" value="{{ name }}"> <input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name" name="name" placeholder="Max" value="{{ name }}">
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ errors.get('name', '') }} {{ errors.get('name', '') }}
</div>
</div> </div>
<div class="mb-3"> </div>
<label for="password" class="form-label">Passwort</label> <div class="mb-3">
<input type="password" class="form-control {% if errors.get('password') %} is-invalid {% endif %}" <label for="password" class="form-label">Passwort</label>
id="password" name="password" value="{{ password }}"> <input type="password" class="form-control {% if errors.get('password') %} is-invalid {% endif %}" id="password" name="password" value="{{ password }}">
<div class="invalid-feedback"> <div class="invalid-feedback">
{{ errors.get('password', '') }} {{ errors.get('password', '') }}
</div>
</div> </div>
<div class="col-auto"> </div>
<button type="submit" class="btn btn-primary mb-3">Registrieren</button> <div class="col-auto">
</div> <button type="submit" class="btn btn-primary mb-3">Registrieren</button>
</form> </div>
</div> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,20 +0,0 @@
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du dieses Habit wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">abbrechen</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteHabit(selectedHabitId)">Löschen
</button>
</div>
</div>
</div>
</div>

View File

@ -1,20 +0,0 @@
<div class="modal fade" id="listenModal" tabindex="-1" aria-labelledby="listenModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="listenModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du diese Liste wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">abbrechen</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteList(localStorage.getItem('selectedListId'));">Löschen
</button>
</div>
</div>
</div>
</div>

View File

@ -1,221 +0,0 @@
<!--suppress HtmlUnknownTarget -->
<div class="flex-fill col-md-7 col-lg-8 col-12 card bg-light p-6 mb-6">
<!-- Listen erstellen -->
<div class="row mb-3">
<h5 class="col-9">📋 Gewohnheiten</h5>
<a class="col-3 btn btn-primary p" role="button" href="/habit-list">Neue Liste erstellen</a>
</div>
<!-- Tabs zur Auswahl -->
<ul class="nav nav-tabs card-header-tabs" role="tablist">
{% for habit_list in habit_lists %}
<li class="nav-item" role="presentation" id="tab-{{ habit_list.id }}">
<a class="nav-link {% if (active_list is not none and active_list == habit_list.id) or (active_list is none and habit_list == habit_lists[0]) %} active {% endif %}"
id="simple-tab-{{habit_list.id}}"
data-bs-toggle="tab" href="#simple-tabpanel-{{habit_list.id}}" role="tab"
aria-controls="simple-tabpanel-{{habit_list.id}}" aria-selected="true">
{{habit_list.name}}
</a>
</li>
{% endfor %}
</ul>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Select all the tab links
const tabLinks = document.querySelectorAll('.nav-link');
tabLinks.forEach(function(link) {
// Add a click event listener to each tab link
link.addEventListener('click', function(e) {
// Prevent the default action
e.preventDefault();
// Get the tab ID
const tabId = this.getAttribute('href').match(/simple-tabpanel-(\d+)/)[1];
// Update the URL with the new query parameter
const newUrl = updateQueryStringParameter(window.location.href, 'list', tabId);
window.history.pushState({path:newUrl}, '', newUrl);
});
});
// Update the URL with the new query parameter
function updateQueryStringParameter(uri, key, value) {
const re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
const separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
});
</script>
<div class="tab-content pt-5" id="tab-content">
{% for habit_list in habit_lists %}
<div class="tab-pane {% if (active_list is not none and active_list == habit_list.id) or (active_list is none and habit_list == habit_lists[0]) %} active {% endif %}"
id="simple-tabpanel-{{habit_list.id}}" role="tabpanel" aria-labelledby="simple-tab-{{habit_list.id}}">
<!-- Beschreibung und Löschen von der Liste -->
<div class="row">
<div class="col">
{{ habit_list.description }}
</div>
<div class="col-1">
<button type="button" class="btn btn-xs me-3" data-bs-toggle="modal"
data-bs-target="#listenModal"
onclick="{
localStorage.setItem('selectedListId', {{ habit_list.id }});
}">
<!---onclick="setSelectedListId({{ habit_list.id }})"-->
<i class="bi bi-trash3"></i>
</button>
</div>
</div>
<div class="row mb-3 align-items-center">
<!-- Personen die zur Liste gehören -->
{% if habit_list.get_users()[0].id == current_user.id %}
{% if habit_list.get_users()|length > 1 %}
<div class="col">
<div class="avatar-stack">
{% for user in habit_list.get_users() %}
{% if current_user.id != user.id %}
<img class="avatar" src="/{{user.profile_image}}" data-toggle="tooltip" data-placement="top"
title="{{user.name}}" alt=""/>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<div class="col">
<!-- Knopf für das Hinzufügen einer Person zur gemeinsamen Liste -->
<a class="me-5" href="/users?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Benutzer einladen">
<i class="bi bi-person-fill-add" style="font-size: 24px;"></i>
</a>
<!-- Knopf für das Bearbeiten von Personen zur gemeinsamen Liste -->
{% if habit_list.get_users()|length > 1 %}
<a href="/users-edit?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Benutzer bearbeiten">
<i class="bi bi-pencil"></i>
</a>
{% endif %}
</div>
{% else %}
<div class="row">
<a class="me-5" href="/users-leave?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Liste verlassen">
<i class="bi bi-box-arrow-left" style="font-size: 24px;"></i>
</a>
<div class="col">
<div class="avatar-stack">
{% for user in habit_list.get_users() %}
{% if current_user.id != user.id %}
<img class="avatar" src="/{{user.profile_image}}" data-toggle="tooltip" data-placement="top"
title="{{user.name}}" alt=""/>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<div class="col-4"></div>
<!-- neue Gewohnheiten erstellen -->
<a class="col-3 btn btn-primary" role="button" href="/habit?list={{ habit_list.id }}">
Gewohnheit erstellen
</a>
</div>
<ul class="task-list row">
{% for habit in habit_list.habits %}
<li class="row d-flex align-items-center mb-2" id="habit-{{ habit.id }}">
<!-- Handle zum Verschieben -->
<div class="col-auto drag-handle" style="cursor: grab;">
<i class="bi bi-grip-vertical"></i>
</div>
<!-- Checkbox -->
<div class="col-auto">
<input {% if habit.checked %} checked {% endif %} type="checkbox" class="task-checkbox"
id="{{ habit.id }}" onclick="sendPostRequest('{{ habit.id }}')">
</div>
<!-- Name -->
<div class="col" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.name }}
</div>
<!-- Beschreibung -->
<div class="col-md-4 d-none d-md-block text-black text-opacity-50"
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.note }}
</div>
<!-- Streak -->
<div class="col-2" id="streak-{{ habit.id }}"
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{% if not habit.streak == 0 %}
{{ habit.streak }} 🔥
{% endif %}
</div>
<!-- Knopf für das Löschen einer Gewohnheit -->
<button type="button" class="btn btn-xs me-3" data-bs-toggle="modal"
data-bs-target="#exampleModal" style="width: 40px; height: 40px"
onclick="setSelectedHabitId({{ habit.id }})">
<i class="bi bi-trash3"></i>
</button>
<!-- Knopf für das Bearbeiten der Gewohnheit -->
<a type="button" class="btn" href="{{ url_for('edit_habit') }}?habit={{ habit.id }}"
aria-current="page"
style="width: 40px; height: 40px; min-height: 3em;">
<i class="bi bi-pencil"></i>
</a>
<!-- Progressbar -->
<div class="col-12">
<div class="progress" style="height: 2px; width: 90%">
<div class="progress-bar" id="progress-bar-{{ habit.id }}" role="progressbar"
style="width: {{ habit.percentage }}%; background-color: {% if habit.percentage >= 100 %} green {% else %} primary {% endif %}"
aria-valuenow="{{ habit.percentage }}" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</div>
</div>
<script>
var selectedHabitId = null;
function setSelectedHabitId(habitId) {
selectedHabitId = habitId;
}
var selectedListId = null;
function setSelectedListId(listId) {
selectedlistId = listId;
}
</script>

View File

@ -1,20 +0,0 @@
<div class="flex-fill col-md-5 col-lg-4 col-12 card bg-light mb-6">
<div class="card-body">
<h5 class="card-title">📅 Heatmap</h5>
<div id="heatmap"></div>
</div>
</div>
<script>
function hexToRgb(hex) {
hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => r + r + g + g + b + b);
const [, r, g, b] = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
}
// Generates activity based on the Values given by the Backend
const activityData = {{ heatmap_values }};
const day = {{ day }};
const color = hexToRgb("{{ color }}");
</script>

View File

@ -1,86 +0,0 @@
{% extends 'index.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Habit Bearbeiten📋</h1>
<form action="/edit-habit" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name der Gewohnheit</label>
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name"
name="name" value="{{name}}">
<div class="invalid-feedback">
{{ errors.get('name', '') }}
</div>
</div>
<div class="mb-3">
<label for="note" class="form-label">Beschreibung</label>
<input type="text" class="form-control {% if errors.get('note') %} is-invalid {% endif %}" id="note"
name="note" value="{{note}}">
<div class="invalid-feedback">
{{ errors.get('note', '') }}
</div>
</div>
<div class="row">
<div class="mb-3 col-2">
<label for="times" class="form-label">Häufigkeit</label>
<input type="number" min="1" class="form-control {% if errors.get('times') %} is-invalid {% endif %}"
id="times" name="times" value="{{times}}">
<div class="invalid-feedback">
{{ errors.get('times', '') }}
</div>
</div>
<div class="mb-3 col-10">
<label for="unit" class="form-label">Im Zeitraum</label>
<select class="form-select {% if errors.get('unit') %} is-invalid {% endif %}" id="unit" name="unit">
<option value="Tag">Tag</option>
<option value="Woche">Woche</option>
<option value="Monat">Monat</option>
<option value="Jahr">Jahr</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', () => {
let selectedElement = document.getElementById('unit');
for (let option of selectedElement.options) {
if (option.value === '{{ unit }}') {
option.selected = true;
break;
}
}
});
</script>
<div class="invalid-feedback">
{{ errors.get('unit', '') }}
</div>
</div>
</div>
<input type="hidden" name="habit" id="habit" class="{% if errors.get('habit') %} is-invalid {% endif %}">
<div class="invalid-feedback">
{{ errors.get('list_query', '') }}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Extracting the list-query from the URL
let listQuery = new URLSearchParams(window.location.search).get('habit');
if ("{{ habit }}" !== "") {
listQuery = "{{ habit }}";
// Add the list_id to the URL
const url = new URL(window.location.href);
url.searchParams.set('habit', listQuery);
// window.history.pushState({}, '', url);
}
// Setting the list-query as the value of the hidden input field
document.getElementById('habit').value = listQuery;
});
</script>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Gewohnheitsliste erstellen📋</h1> <h1 class="mt-5">Habitliste erstellen📋</h1>
<form action="/habit-list" method="POST"> <form action="/habit-list" method="POST">
<div class="mb-3"> <div class="mb-3">
@ -21,5 +21,5 @@
</div> </div>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
</div>
{% endblock %} {% endblock %}

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Gewohnheit erstellen📋</h1> <h1 class="mt-5">Habit erstellen📋</h1>
<form action="/habit" method="POST"> <form action="/habit" method="POST">
<div class="mb-3"> <div class="mb-3">
@ -37,11 +37,11 @@
<option value="Jahr">Jahr</option> <option value="Jahr">Jahr</option>
</select> </select>
<script> <script>
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', (event) => {
let selectedElement = document.getElementById('unit'); let selectedElement = document.getElementById('unit');
for (let option of selectedElement.options) { for (let option of selectedElement.options) {
if (option.value === '{{ unit }}') { if (option.value == '{{ unit }}') {
option.selected = true; option.selected = true;
break; break;
} }
@ -61,13 +61,13 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Extracting the list-query from the URL // Extracting the list-query from the URL
let listQuery = new URLSearchParams(window.location.search).get('list'); var listQuery = new URLSearchParams(window.location.search).get('list');
if ("{{ list_id }}" !== "") { if ("{{ list_id }}" != "") {
listQuery = "{{ list_id }}"; listQuery = "{{ list_id }}";
// Add the list_id to the URL // Add the list_id to the URL
const url = new URL(window.location.href); var url = new URL(window.location.href);
url.searchParams.set('list', listQuery); url.searchParams.set('list', listQuery);
// window.history.pushState({}, '', url); // window.history.pushState({}, '', url);
} }
@ -78,5 +78,5 @@
</script> </script>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
</div>
{% endblock %} {% endblock %}

View File

@ -1,39 +1,266 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<div class = "row"> <h1>{{ title }}</h1>
<div class="col"> <h3>{{ utc_dt }}</h3>
<h1>
{% if (current_user.is_authenticated) %}<img class="avatar avatar-xl" src="{{user.profile_image}}" alt="no image"/>{% endif %}
{{ title }} <style>
</h1> #heatmap {
<h3 id="current-time"></h3> display: grid;
grid-template-columns: repeat(7, 0fr); /* 7 Tage in einer Woche */
gap: 5px;
}
.day {
width: 50px;
height: 50px;
border: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
}
</style>
<div class="row">
<div class="col-md-5 col-12">
<div id="heatmap"></div>
</div>
<script>
// Funktion zur Rückgabe des Montagsdatums
function getMonday(date) {
const day = date.getDay();
const diff = date.getDate() - day + (day === 0 ? -6 : 1); // Anpassung für Sonntag
return new Date(date.setDate(diff));
}
// Simulierte Aktivitätsdaten (ersetze dies durch deine echten Daten)
const activityData = [5, 3, 10, 5, 24, 2, 10, 47, 32, 45, 9, 5, 11, 39, 24, 2, 10, 47, 32, 45];
// Funktion zum Erstellen der Heatmap
function createHeatmap(data) {
const heatmapContainer = document.getElementById('heatmap');
// Aktuelles Datum des Montags
const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
for (let i = 0; i < 7; i++) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.textContent = days[i];
heatmapContainer.appendChild(dayElement);
// currentDate.setDate(currentDate.getDate() + 1);
}
// Aktuelles Datum des Montags in der neuen linken Spalte
for (let i = 0; i < 7; i++) {
for (let j = 0; j < 7; j++) {
// console.log(i * 7 + j, data[i * 7 + j], Math.max(...data));
const opacity = data[i * 7 + j] / Math.max(...data); // Berechne die Opazität basierend auf Aktivitätsanzahl
if (data[i * 7 + j]) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.style.backgroundColor = `rgba(0, 255, 0, ${opacity})`;
heatmapContainer.appendChild(dayElement);
} else {
const dayElement = document.createElement('div');
// dayElement.classList.add('day');
// dayElement.style.backgroundColor = `rgba(0, 255, 0, ${opacity})`;
heatmapContainer.appendChild(dayElement);
}
}
}
}
// Erstelle die Heatmap mit den simulierten Daten
createHeatmap(activityData);
</script>
<div class="col-md-7 col-12">
<div class="row mb-3">
<h2 class="col-9">Gewohnheiten</h2>
<a class="col-3 btn btn-primary" role="button" href="/habit-list">Neue Liste erstellen</a>
</div>
{% for habit_list in habit_lists %}
<div class="row mb-3">
<h2 class="col-9">{{ habit_list.name }}</h2>
<a class="col-3 btn btn-primary" role="button" href="/habit?list={{ habit_list.id }}">Gewohnheit erstellen</a>
</div>
<ul class="task-list row">
{% for habit in habit_list.habits %}
<li class="row d-flex align-items-center mb-2" id="habit-{{ habit.id }}">
<div class="col-auto drag-handle" style="cursor: grab;">
<i class="bi bi-grip-vertical"></i>
</div>
<div class="col-auto">
<input {% if habit.checked %} checked {% endif %} type="checkbox" class="task-checkbox"
id="{{ habit.id }}"
onclick="sendPostRequest('{{ habit.id }}')">
</div>
<div class="col" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.name }}
</div>
<div class="col-6" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.note }}
</div>
<div class="col-2" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{% if habit %}
5 🔥
{% endif %}
</div>
<button type="button" class="btn btn-xs btn-danger rounded-circle" data-bs-toggle="modal"
data-bs-target="#exampleModal" style="width: 40px; height: 40px"
onclick="setSelectedHabitId({{ habit.id }})">
<i class="bi bi-trash3"></i>
</button>
<div class="col-12">
<div class="progress" style="height: 2px; width: 90%">
<div class="progress-bar" id="progress-bar-{{ habit.id }}" role="progressbar"
style="width: {{ habit.percentage }}%; background-color: {% if habit.percentage >= 100 %} green {% else %} primary {% endif %}"
aria-valuenow="{{ habit.percentage }}" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
<script>
var selectedHabitId = null;
function setSelectedHabitId(habitId) {
selectedHabitId = habitId;
}
</script>
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du dieses Habit wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteHabit(selectedHabitId)">Löschen
</button>
</div>
</div>
</div>
</div>
<script>
function checkCompletionAndAnimate(habitId, percentage) {
var progressBar = document.getElementById("progress-bar-" + habitId);
var habitBlock = document.getElementById("habit-" + habitId);
if (percentage == 100) {
progressBar.style.backgroundColor = "green";
habitBlock.classList.add("animate-bounce");
setTimeout(function () {
habitBlock.classList.remove("animate-bounce");
}, 2000);
} else {
progressBar.style.backgroundColor = "";
habitBlock.classList.remove("animate-bounce");
}
}
function sendPostRequest(checkboxId) {
// Get the checkbox element using the provided ID
var checkbox = document.getElementById(checkboxId);
// console.log(checkbox);
// Get the habit id from the checkbox id attribute
var habitId = checkboxId;
// Make a POST request to /check with the habit id
axios.post('/check', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Set the percentage of the habit. percentage received as integer
var percentage = response.data.percentage;
var progressBar = document.getElementById("progress-bar-" + habitId);
progressBar.style.width = percentage + "%";
checkCompletionAndAnimate(habitId, percentage);
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
function deleteHabit(habitId) {
// Make a POST request to /delete with the habit id
axios.post('/delete', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
var habitElement = document.getElementById("habit-" + habitId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
</script>
</div> </div>
<div class="col-2">
<a href="https://anti-stress-team.de/blog/selbstmanagement/80-spannende-ideen-fuer-deinen-habit-tracker/" role="button" class="btn btn-discovery">Beliebte Habits</a>
</div>
</div>
<div class="d-md-flex gap-3"> <script>
document.addEventListener('DOMContentLoaded', (event) => {
{% if current_user.is_authenticated %} var el = document.querySelector('.task-list');
{% include 'components/heatmap.html' %} Sortable.create(el, {
{% endif %} handle: '.drag-handle',
animation: 150,
{% if current_user.is_authenticated %} onEnd: function (evt) {
{% include 'components/habit_lists.html' %} var habitId = el.children[evt.newIndex].id.split('-')[1];
{% endif %} var oldIndex = evt.oldIndex;
var newIndex = evt.newIndex;
{% if current_user.is_authenticated %}
{% include 'components/delete_button.html' %}
{% endif %}
{% if current_user.is_authenticated %}
{% include 'components/delete_list.html' %}
{% endif %}
</div>
<script src="../static/script/script-index.js"></script>
axios.post('/reorder', {habitId: habitId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
});
</script>
{% endblock %} {% endblock %}

View File

@ -2,132 +2,45 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="viewport" content="width=device-width, initial-scale=1" >
<title>{{ title }} - HabitTracker</title> <title>{{ title }} - HabitTracker</title>
<link rel="icon" href="/static/icon.ico">
<!-- CSS --> <!-- CSS -->
<link href="../../static/css/background.css" rel="stylesheet" type="text/css"> <link rel="stylesheet" href="/static/main.css">
<link href="../../static/css/profile.css" rel="stylesheet" type="text/css"> <link href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css" rel="stylesheet" integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" crossorigin="anonymous">
<link href="../../static/css/heatmap.css" rel="stylesheet" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css"
integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- Bootstrap JS (including Popper.js for Bootstrap 5) -->
<script crossorigin="anonymous"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<!-- Axios Library--> <!-- Axios Library-->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head> </head>
<body style="background-color: White"> <body style="background-color: White">
<nav class="navbar navbar-expand-lg" style="background-color: #000000"> <nav class="navbar navbar-expand-lg" style="background-color: #000000">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}" style="color: ivory">HabitTracker</a> <a class="navbar-brand" href="{{ url_for('index') }}" style="color: ivory">HabitTracker</a>
<button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="me-auto"></ul> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link text-white {% if title == 'Home' %} active {% endif %}" aria-current="page" href="{{ url_for('index') }}">Home</a>
</li>
</ul>
<ul class="navbar-nav mb-2 mb-lg-0"> <ul class="navbar-nav mb-2 mb-lg-0">
{% if not current_user.is_authenticated %} {% if not current_user.is_authenticated %}
<li class="nav-item me-2"> <li class="nav-item me-2">
<a aria-current="page" class="btn text-white btn-primary" href="{{ url_for('login') }}">Login</a> <a class="btn text-white btn-primary" aria-current="page" href="{{ url_for('login') }}">Login</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a aria-current="page" class="btn btn-outline-secondary" href="{{ url_for('signup') }}">Signup</a> <a class="btn btn-outline-secondary" aria-current="page" href="{{ url_for('signup') }}">Signup</a>
</li> </li>
{% else %} {% else %}
{% if notifications %} <li class="nav-item me-2">
<li class="nav-item me-4 mb-2 mb-lg-0"> <a class="btn text-white btn-primary" aria-current="page" href="{{ url_for('profile') }}">Profil</a>
<div class="dropdown">
<!--<button class="btn btn-default dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Page actions
</button>-->
<i class="bi bi-bell-fill dropdown-toggle" data-bs-toggle="dropdown"
style="color: white; cursor: pointer"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">
{{ notifications|length }}
<span class="visually-hidden">unread messages</span>
</span>
<ul class="dropdown-menu" role="menu">
{% for notification in notifications %}
<li>
<div class="dropdown-item">
<div class="row">
<div class="col">
{{ notification.name }}
</div>
<div class="col">
<a class="accept-button" data-id="{{ notification.id }}" style="cursor: pointer"><i class="bi bi-check-circle-fill" style="color: green"></i></a>
</div>
<div class="col">
<a class="deny-button" data-id="{{ notification.id }}" style="cursor: pointer"><i class="bi bi-x-circle-fill" style="color: red"></i></a>
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var acceptButtons = document.querySelectorAll('.accept-button');
acceptButtons.forEach(function(button) {
button.addEventListener('click', function() {
var notificationId = this.getAttribute('data-id');
console.log('Notification accepted:', notificationId);
axios.post('/accept-list', {list_id: notificationId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(() => {
location.reload();
});
});
});
});
document.addEventListener('DOMContentLoaded', function() {
var acceptButtons = document.querySelectorAll('.deny-button');
acceptButtons.forEach(function(button) {
button.addEventListener('click', function() {
var notificationId = this.getAttribute('data-id');
console.log('Notification accepted:', notificationId);
axios.post('/deny-list', {list_id: notificationId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(() => {
location.reload();
});
});
});
});
</script>
</li>
{% endif %}
<li class="nav-item me-2 mb-2 mb-lg-0">
<a aria-current="page" class="btn text-white btn-primary" href="{{ url_for('profile') }}">Profil</a>
</li> </li>
<li class="nav-item me-2"> <li class="nav-item me-2">
<a aria-current="page" class="btn btn-primary" href="{{ url_for('logout') }}">Logout</a> <a class="btn btn-primary" aria-current="page" href="{{ url_for('logout') }}">Logout</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@ -138,6 +51,9 @@
<div class="container mt-3 pb-3"> <div class="container mt-3 pb-3">
{% block content %} {% endblock %} {% block content %} {% endblock %}
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
</div> </div>
</body> </body>
</html> </html>

View File

@ -2,146 +2,45 @@
{% block content %} {% block content %}
<div class="container mt-5"> <h1 class="mt-5">Account Einstellungen👤</h1>
<h1 class="mb-4">Account Einstellungen👤</h1>
<!-- Account information fields -->
<div class="card bg-light mb-4"> <form action="/profile" method="POST">
<div class="card-body d-flex"> <div class="form-group mb-3">
<div> <label for="newName">Neuer Name:</label>
<h5 class="card-title">Profilbild</h5> <input type="text" class="form-control {% if errors.get('newName') %} is-invalid {% endif %}" id="newName" name="newName" value="{{name}}">
<div class="mb-3 profile-image-container" id="profileImageContainer"> <div class="invalid-feedback">
<img src="{{ profile_image_url }}" alt="Profile Image" class="profile-image" id="profileImage"> {{ errors.get('newName', '') }}
<div class="profile-image-overlay" id="profileImageOverlay">
<span>Profilbild aktualisieren</span>
</div>
</div>
<div class="mb-3">
<form id="uploadForm" action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" class="form-control-file" id="profileImageInput" name="file" style="display: none;">
</form>
</div>
</div>
<div class="ml-5" style="margin-left: 50px;">
<h5 class="card-title">Name</h5>
<p>{{ name }}</p>
<h5 class="card-title">Email</h5>
<p>{{ email }}</p>
<button type="button" class="btn btn-primary" id="editButton" data-toggle="modal" data-target="#editModal">
Bearbeiten
</button>
</div>
<form id="colorForm" action="/save_color" method="POST">
<div class="ml-5" style="margin-left: 50px;">
<h5 class="card-title">Heatmap Farbe</h5>
<div style="display: flex; align-items: center;">
<input type="color" name="color" class="form-control form-control-color" id="exampleColorInput" value="{{color}}" title="Choose your color" style="margin-right: 10px;">
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</div>
</form>
</div> </div>
</div> </div>
<!-- Password change fields --> <div class="form-group mb-3">
<div class="card bg-light mb-4"> <label for="newEmail">Neue E-Mail:</label>
<div class="card-body"> <input type="email" class="form-control {% if errors.get('newEmail') %} is-invalid {% endif %}" id="newEmail" name="newEmail" value="{{email}}">
<h5 class="card-title">Passwort ändern</h5> <div class="invalid-feedback">
<form id="editPasswordForm" action="/password" method="POST"> {{ errors.get('newEmail', '') }}
<div class="form-group mb-3">
<label for="oldPassword">Altes Passwort:</label>
<input type="password" class="form-control" id="oldPassword" name="oldPassword" autocomplete="current-password">
<div class="invalid-feedback" id="oldPasswordFeedback"></div>
</div>
<div class="form-group mb-3">
<label for="newPassword">Neues Passwort:</label>
<input type="password" class="form-control" id="newPassword" name="newPassword" autocomplete="new-password">
<div class="invalid-feedback" id="newPasswordFeedback"></div>
</div>
<div class="form-group mb-3">
<label for="confirmPassword">Neues Passwort bestätigen:</label>
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" autocomplete="new-password">
<div class="invalid-feedback" id="confirmPasswordFeedback"></div>
</div>
<button type="button" class="btn btn-primary" id="submitPasswordChange">Änderungen speichern</button>
</form>
<div style="margin-top: 30px;">
<h5 class="card-title">Konto löschen</h5>
<form id="deleteAccountForm" action="/delete_account" method="POST">
<button type="button" class="btn btn-danger" id="deleteAccountButton" data-toggle="modal" data-target="#confirmDeleteModal">
Konto dauerhaft löschen
</button>
</form>
</div>
</div> </div>
</div> </div>
</div>
<div class="form-group mb-5">
<!-- Delete Account Confirmation Modal --> <label for="newPassword">Neues Passwort:</label>
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" role="dialog" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true"> <input type="text" class="form-control {% if errors.get('newPassword') %} is-invalid {% endif %}" id="newPassword" name="newPassword">
<div class="modal-dialog mt-20" role="document"> <div class="invalid-feedback">
<div class="modal-content"> {{ errors.get('newPassword', '') }}
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteModalLabel">Konto löschen</h5>
<span class="close-icon" data-bs-dismiss="modal" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 72 72" class="close-icon">
<path d="M 19 15 C 17.977 15 16.951875 15.390875 16.171875 16.171875 C 14.609875 17.733875 14.609875 20.266125 16.171875 21.828125 L 30.34375 36 L 16.171875 50.171875 C 14.609875 51.733875 14.609875 54.266125 16.171875 55.828125 C 16.951875 56.608125 17.977 57 19 57 C 20.023 57 21.048125 56.609125 21.828125 55.828125 L 36 41.65625 L 50.171875 55.828125 C 51.731875 57.390125 54.267125 57.390125 55.828125 55.828125 C 57.391125 54.265125 57.391125 51.734875 55.828125 50.171875 L 41.65625 36 L 55.828125 21.828125 C 57.390125 20.266125 57.390125 17.733875 55.828125 16.171875 C 54.268125 14.610875 51.731875 14.609875 50.171875 16.171875 L 36 30.34375 L 21.828125 16.171875 C 21.048125 15.391875 20.023 15 19 15 z" stroke="none"></path>
</svg>
</span>
</div>
<div class="modal-body">
Sind Sie sicher, dass Sie Ihr Konto dauerhaft löschen möchten?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-danger" id="confirmDeleteButton">Löschen</button>
</div>
</div> </div>
</div> </div>
</div>
<div class="form-group mb-3">
<!-- Edit Modal --> <label for="oldPassword">Altes Passwort:</label>
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModalLabel" aria-hidden="true"> <input type="password" class="form-control {% if errors.get('oldPassword') %} is-invalid {% endif %}" id="oldPassword" name="oldPassword">
<div class="modal-dialog mt-20" role="document"> <div class="invalid-feedback">
<div class="modal-content"> {{ errors.get('oldPassword', '') }}
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Bearbeiten</h5>
<span class="close-icon" data-bs-dismiss="modal" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 72 72" class="close-icon">
<path d="M 19 15 C 17.977 15 16.951875 15.390875 16.171875 16.171875 C 14.609875 17.733875 14.609875 20.266125 16.171875 21.828125 L 30.34375 36 L 16.171875 50.171875 C 14.609875 51.733875 14.609875 54.266125 16.171875 55.828125 C 16.951875 56.608125 17.977 57 19 57 C 20.023 57 21.048125 56.609125 21.828125 55.828125 L 36 41.65625 L 50.171875 55.828125 C 51.731875 57.390125 54.267125 57.390125 55.828125 55.828125 C 57.391125 54.265125 57.391125 51.734875 55.828125 50.171875 L 41.65625 36 L 55.828125 21.828125 C 57.390125 20.266125 57.390125 17.733875 55.828125 16.171875 C 54.268125 14.610875 51.731875 14.609875 50.171875 16.171875 L 36 30.34375 L 21.828125 16.171875 C 21.048125 15.391875 20.023 15 19 15 z" stroke="none"></path>
</svg>
</span>
</div>
<div class="modal-body">
<form id="editForm" action="/profile" method="POST">
<div class="form-group">
<label for="newName">Neuer Name:</label>
<input type="text" class="form-control" id="newName" name="newName" value="{{ name }}" autocomplete="username">
<div class="invalid-feedback" id="nameFeedback"></div>
</div>
<div class="form-group">
<label for="newEmail">Neue E-Mail:</label>
<input type="email" class="form-control" id="newEmail" name="newEmail" value="{{ email }}" autocomplete="username">
<div class="invalid-feedback" id="emailFeedback"></div>
</div>
<div class="form-group">
<label for="password">Passwort:</label>
<input type="password" class="form-control" id="password" name="password" autocomplete="current-password">
<div class="invalid-feedback" id="passwordFeedback"></div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="button" class="btn btn-primary" id="saveChangesButton">Änderungen speichern</button>
</div>
</div> </div>
</div> </div>
</div>
<script src="../static/script/script-profile.js"></script> <button type="submit" class="btn btn-primary">Änderungen speichern</button>
</form>
{% endblock %} {% endblock %}

View File

@ -1,37 +0,0 @@
{% extends 'index.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1 class="mb-5">
{{ title }}
</h1>
{% for user in users %}
<form action="/user-delete" class="row" method="POST">
<div class="col">
<img src="{{ user.profile_image }}" class="avatar" alt=""/>
</div>
<div class="col">
{{ user.name }}
</div>
<div class="col">
{{ user.email }}
</div>
<div class="col">
<label>
<input hidden="hidden" name="habit_list_id" value="{{ habit_list.id }}">
</label>
<label>
<input hidden="hidden" name="habit_user_id" value="{{ user.id }}">
</label>
<button type="submit" class="btn btn-primary btn-danger">
Löschen
</button>
</div>
</form>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,27 +0,0 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light mt-4 p-5">
<h1>{{ habit_list.name }}: {{ title }}</h1>
<p>Lade Nutzer per ihrer E-Mail-Adresse ein</p>
<form action="/users" method="POST">
<div class="mb-3">
<label for="email" class="form-label">E-Mail</label>
<input type="text" placeholder="beispiel@cimeyclust.com"
class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" name="email"
value="{{email}}">
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
</div>
<label>
<input hidden="hidden" name="habit_list_id" value="{{ habit_list.id }}">
</label>
<!-- submit button -->
<button type="submit" class="btn btn-primary">Einladen</button>
</form>
</div>
{% endblock %}