better looking html code and fixed a bug

This commit is contained in:
janphilippweinsheimer 2024-02-20 20:55:06 +01:00
parent 0f35a67391
commit 9e1bd7b65e
6 changed files with 223 additions and 258 deletions

15
app.py
View File

@ -125,9 +125,11 @@ def index():
if current_user.is_authenticated: if current_user.is_authenticated:
habit_lists = current_user.get_habitLists() habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name name = "Hallo " + current_user.name
heatmap_values = current_user.get_heatmap()
else: else:
habit_lists = [] habit_lists = []
name = "Bitte melde dich an." name = "Bitte melde dich an."
heatmap_values = []
# Sort habits by whether they have been checked today and then by slot # Sort habits by whether they have been checked today and then by slot
for habit_list in habit_lists: for habit_list in habit_lists:
@ -138,7 +140,7 @@ def index():
title=name, title=name,
utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M %A"), utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M %A"),
habit_lists=habit_lists, habit_lists=habit_lists,
heatmap_values=current_user.get_heatmap(), heatmap_values=heatmap_values,
errors={}, errors={},
) )
@ -296,6 +298,7 @@ def profile_change():
profile_image_url=current_user.profile_image, profile_image_url=current_user.profile_image,
) )
@app.route('/check_password', methods=['POST']) @app.route('/check_password', methods=['POST'])
@login_required @login_required
def check_password(): def check_password():
@ -307,6 +310,7 @@ def check_password():
else: else:
return jsonify({"valid": False}) return jsonify({"valid": False})
@app.route('/password', methods=['POST']) @app.route('/password', methods=['POST'])
@login_required @login_required
def password_change(): def password_change():
@ -325,9 +329,11 @@ def password_change():
profile_image_url=current_user.profile_image, profile_image_url=current_user.profile_image,
) )
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def save_profile_image(image_file): def save_profile_image(image_file):
filename = image_file.filename filename = image_file.filename
if '.' not in filename: if '.' not in filename:
@ -368,6 +374,7 @@ def save_profile_image(image_file):
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image.save(image_path, 'JPEG', quality=100) image.save(image_path, 'JPEG', quality=100)
@app.route('/upload', methods=['POST']) @app.route('/upload', methods=['POST'])
def upload_profile_image(): def upload_profile_image():
if 'file' not in request.files: if 'file' not in request.files:
@ -423,6 +430,7 @@ def check_habit():
"percentage": habit.percentage, "percentage": habit.percentage,
} }
@app.route('/delete', methods=['POST']) @app.route('/delete', methods=['POST'])
@login_required @login_required
def delete_habit(): def delete_habit():
@ -440,10 +448,11 @@ def delete_habit():
habit.delete() habit.delete()
return {} return {}
@app.route('/reorder', 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"] + 1
habit = Habit.get(request.get_json()["habitId"]) habit = Habit.get(request.get_json()["habitId"])
if habit is None: if habit is None:
@ -461,4 +470,4 @@ def reorder_habits():
# Run the application # Run the application
if __name__ == '__main__': if __name__ == '__main__':
app.run(port=5000, debug=True) app.run(port=5000, debug=True)

View File

@ -27,7 +27,7 @@ def create_user_profile_image(user_id):
return relative_destination_path return relative_destination_path
def create_user(name: str, email: str, password: str, profile_image:str = None): def create_user(name: str, email: str, password: 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, created_at, updated_at) VALUES " query = (f"INSERT INTO users (name, email, password, profile_image, created_at, updated_at) VALUES "

View File

@ -0,0 +1,94 @@
<div class="flex-fill col-md-8 col-12 card bg-light p-6 mb-6">
<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>
<ul class="nav nav-tabs card-header-tabs" role="tablist">
{% for habit_list in habit_lists %}
<li class="nav-item" role="presentation">
<a class="nav-link {% if 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>
<div class="tab-content pt-5" id="tab-content">
{% for habit_list in habit_lists %}
<div class="tab-pane {% if habit_list == habit_lists[0] %} active {% endif %}"
id="simple-tabpanel-{{habit_list.id}}" role="tabpanel" aria-labelledby="simple-tab-{{habit_list.id}}">
<div class="row mb-3">
<h2 class="col-9"></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-5 text-black text-opacity-50"
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 not habit.streak == 0 %}
{{ habit.streak }} 🔥
{% 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>
</div>
{% endfor %}
</div>
</div>
<script>
var selectedHabitId = null;
function setSelectedHabitId(habitId) {
selectedHabitId = habitId;
}
</script>

View File

@ -1,6 +1,3 @@
{% extends 'index.html' %}
{% block heatmap %}
<div class="flex-fill col-md-4 col-12 card bg-light mb-6"> <div class="flex-fill col-md-4 col-12 card bg-light mb-6">
<div class="card-body"> <div class="card-body">
@ -56,5 +53,3 @@
createHeatmap(activityData); createHeatmap(activityData);
</script> </script>
{% endblock %}

View File

@ -0,0 +1,93 @@
<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);
});
}
document.addEventListener('DOMContentLoaded', (event) => {
var el = document.querySelector('.task-list');
Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
var habitId = el.children[evt.newIndex].id.split('-')[1];
var oldIndex = evt.oldIndex;
var newIndex = evt.newIndex;
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>

View File

@ -1,7 +1,7 @@
{% extends 'layouts/main.html' %} {% extends 'layouts/main.html' %}
{% block content %} {% block content %}
<h1> <img class="avatar avatar-xl" src="{{user.profile_image}}"/> <h1><img class="avatar avatar-xl" src="{{user.profile_image}}"/>
{{ title }}</h1> {{ title }}</h1>
<h3>{{ utc_dt }}</h3> <h3>{{ utc_dt }}</h3>
@ -24,268 +24,42 @@
justify-content: center; justify-content: center;
table-layout: fixed; table-layout: fixed;
} }
</style> </style>
<div class="d-md-flex gap-3"> <div class="d-md-flex gap-3">
<div class="flex-fill col-md-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> {% include 'components/heatmap.html' %}
// Simulierte Aktivitätsdaten (ersetze dies durch deine echten Daten)
const activityData = {{ heatmap_values }};
// 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 < 4; j++) {
// console.log(i * 7 + j, data[i * 7 + j], Math.max(...data));
const opacity = data[i * 7 + j] / (Math.max(...data) <= 0 ? 1 : 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);
}
}
}
var left = 7 - (new Date()).getDay();
}
// Erstelle die Heatmap mit den simulierten Daten
createHeatmap(activityData);
</script>
<div class="flex-fill col-md-8 col-12 card bg-light p-6 mb-6"> {% include 'components/habit_lists.html' %}
<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>
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<ul class="nav nav-tabs card-header-tabs" role="tablist"> <div class="modal-dialog">
{% for habit_list in habit_lists %} <div class="modal-content">
<div class="modal-header">
<li class="nav-item" role="presentation"> <h1 class="modal-title fs-5" id="exampleModalLabel">Bestätige</h1>
<a class="nav-link {% if 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"> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
{{habit_list.name}}
</a>
</li>
{% endfor %}
</ul>
<div class="tab-content pt-5" id="tab-content">
{% for habit_list in habit_lists %}
<div class="tab-pane {% if habit_list == habit_lists[0] %} active {% endif %}" id="simple-tabpanel-{{habit_list.id}}" role="tabpanel" aria-labelledby="simple-tab-{{habit_list.id}}">
<div class="row mb-3">
<h2 class="col-9"></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-5 text-black text-opacity-50"
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 not habit.streak == 0 %}
{{ habit.streak }} 🔥
{% 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>
</div> </div>
{% endfor %} <div class="modal-body">
</div> Möchtest du dieses Habit wirklich löschen?
</div>
</div> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<script> <button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
var selectedHabitId = null; onclick="deleteHabit(selectedHabitId)">Löschen
</button>
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> </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>
<script> {% include 'components/scripts.html' %}
document.addEventListener('DOMContentLoaded', (event) => {
var el = document.querySelector('.task-list');
Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
var habitId = el.children[evt.newIndex].id.split('-')[1];
var oldIndex = evt.oldIndex;
var newIndex = evt.newIndex;
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 %}