Umbau auf 7 reihen
This commit is contained in:
70
app.py
70
app.py
@@ -18,6 +18,32 @@ def get_last_30_days():
|
|||||||
dates.append(date.strftime('%Y-%m-%d'))
|
dates.append(date.strftime('%Y-%m-%d'))
|
||||||
return dates[::-1] # Reverse to have oldest first
|
return dates[::-1] # Reverse to have oldest first
|
||||||
|
|
||||||
|
# Helper function to get dates organized by weekday for more weeks to fill the width
|
||||||
|
def get_weekly_dates():
|
||||||
|
today = datetime.now()
|
||||||
|
# Find the most recent Monday (start of current week)
|
||||||
|
days_since_monday = today.weekday() # Monday = 0, Sunday = 6
|
||||||
|
current_monday = today - timedelta(days=days_since_monday)
|
||||||
|
|
||||||
|
weekly_data = {}
|
||||||
|
weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag']
|
||||||
|
|
||||||
|
# Use more weeks to fill the available width (about 15-20 weeks)
|
||||||
|
num_weeks = 18 # This should provide good coverage without being too much
|
||||||
|
|
||||||
|
for weekday_idx in range(7): # 0 = Monday, 6 = Sunday
|
||||||
|
weekly_data[weekdays[weekday_idx]] = []
|
||||||
|
for week_offset in range(num_weeks):
|
||||||
|
date = current_monday + timedelta(days=weekday_idx - (week_offset * 7))
|
||||||
|
if date <= today: # Only include dates up to today
|
||||||
|
weekly_data[weekdays[weekday_idx]].append(date.strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
|
# Reverse each weekday list to show oldest first (left to right)
|
||||||
|
for weekday in weekly_data:
|
||||||
|
weekly_data[weekday] = weekly_data[weekday][::-1]
|
||||||
|
|
||||||
|
return weekly_data
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
@@ -26,7 +52,7 @@ def index():
|
|||||||
def get_habits():
|
def get_habits():
|
||||||
raw_habits = db.all()
|
raw_habits = db.all()
|
||||||
formatted_habits = []
|
formatted_habits = []
|
||||||
last_30_days = get_last_30_days()
|
weekly_dates = get_weekly_dates()
|
||||||
|
|
||||||
for habit_doc in raw_habits:
|
for habit_doc in raw_habits:
|
||||||
# Handle migration from old format (list) to new format (dict)
|
# Handle migration from old format (list) to new format (dict)
|
||||||
@@ -48,17 +74,21 @@ def get_habits():
|
|||||||
'daily_target': habit_doc.get('daily_target', 1)
|
'daily_target': habit_doc.get('daily_target', 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
completion_history = {}
|
# Create weekly completion history
|
||||||
for date_str in last_30_days:
|
weekly_completion = {}
|
||||||
|
for weekday, dates in weekly_dates.items():
|
||||||
|
weekly_completion[weekday] = []
|
||||||
|
for date_str in dates:
|
||||||
completed_count = completed_dates.get(date_str, 0)
|
completed_count = completed_dates.get(date_str, 0)
|
||||||
target_count = current_habit_data['daily_target']
|
target_count = current_habit_data['daily_target']
|
||||||
completion_history[date_str] = {
|
weekly_completion[weekday].append({
|
||||||
|
'date': date_str,
|
||||||
'completed': completed_count,
|
'completed': completed_count,
|
||||||
'target': target_count,
|
'target': target_count,
|
||||||
'is_complete': completed_count >= target_count
|
'is_complete': completed_count >= target_count
|
||||||
}
|
})
|
||||||
|
|
||||||
current_habit_data['completion_history'] = completion_history
|
current_habit_data['weekly_completion'] = weekly_completion
|
||||||
formatted_habits.append(current_habit_data)
|
formatted_habits.append(current_habit_data)
|
||||||
|
|
||||||
return jsonify(formatted_habits)
|
return jsonify(formatted_habits)
|
||||||
@@ -160,6 +190,34 @@ def update_habit(habit_id):
|
|||||||
db.update(update_data, doc_ids=[habit_id])
|
db.update(update_data, doc_ids=[habit_id])
|
||||||
return jsonify({'message': f'Habit {habit_id} updated', **update_data})
|
return jsonify({'message': f'Habit {habit_id} updated', **update_data})
|
||||||
|
|
||||||
|
@app.route('/habits/<int:habit_id>/reset', methods=['POST'])
|
||||||
|
def reset_habit(habit_id):
|
||||||
|
date_to_reset = request.json.get('date', get_current_date())
|
||||||
|
|
||||||
|
habit = db.get(doc_id=habit_id)
|
||||||
|
if not habit:
|
||||||
|
return jsonify({'error': 'Habit not found'}), 404
|
||||||
|
|
||||||
|
# Handle migration from old format (list) to new format (dict)
|
||||||
|
completed_dates = habit.get('completed_dates', {})
|
||||||
|
if isinstance(completed_dates, list):
|
||||||
|
new_completed_dates = {}
|
||||||
|
for date in completed_dates:
|
||||||
|
new_completed_dates[date] = 1
|
||||||
|
completed_dates = new_completed_dates
|
||||||
|
|
||||||
|
# Reset completion count for the date to 0
|
||||||
|
if date_to_reset in completed_dates:
|
||||||
|
del completed_dates[date_to_reset]
|
||||||
|
|
||||||
|
db.update({'completed_dates': completed_dates}, doc_ids=[habit_id])
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'message': f'Habit {habit_id} reset for {date_to_reset}',
|
||||||
|
'completed_dates': completed_dates,
|
||||||
|
'current_count': 0
|
||||||
|
})
|
||||||
|
|
||||||
@app.route('/habits/<int:habit_id>', methods=['DELETE'])
|
@app.route('/habits/<int:habit_id>', methods=['DELETE'])
|
||||||
def delete_habit(habit_id):
|
def delete_habit(habit_id):
|
||||||
habit = db.get(doc_id=habit_id)
|
habit = db.get(doc_id=habit_id)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"_default": {"1": {"name": "Mega Zock", "completed_dates": {"2025-07-14": 1, "2025-08-01": 1, "2025-08-09": 1, "2025-08-17": 1, "2025-07-01": 1, "2025-07-16": 1, "2025-07-10": 1, "2025-07-15": 1, "2025-07-09": 1, "2025-07-11": 1, "2025-07-12": 1, "2025-07-13": 1, "2025-07-06": 1, "2025-07-05": 1, "2025-07-04": 1, "2025-07-03": 1, "2025-07-02": 1, "2025-07-08": 1, "2025-07-07": 1}, "color": "#14cc1a"}, "2": {"name": "Laufen", "completed_dates": {}, "color": "#1a3ed1"}, "4": {"name": "Essen", "completed_dates": {"2025-07-14": 3, "2025-07-15": 3, "2025-07-16": 3, "2025-07-17": 3, "2025-07-18": 3}, "color": "#f01000", "daily_target": 3}}}
|
{"_default": {"1": {"name": "Mega Zock", "completed_dates": {"2025-07-14": 1, "2025-08-01": 1, "2025-08-09": 1, "2025-08-17": 1, "2025-07-01": 1, "2025-07-10": 1, "2025-07-15": 1, "2025-07-11": 1, "2025-07-12": 1, "2025-07-13": 1, "2025-07-06": 1, "2025-07-05": 1, "2025-07-04": 1, "2025-07-03": 1, "2025-07-08": 1, "2025-07-07": 1, "2025-07-19": 1, "2025-07-16": 1, "2025-07-02": 1, "2025-07-09": 1, "2025-07-17": 1, "2025-07-18": 1}, "color": "#14cc1a"}, "2": {"name": "Laufen", "completed_dates": {"2025-07-14": 3, "2025-07-15": 4, "2025-06-02": 1, "2025-06-03": 1, "2025-07-19": 1}, "color": "#1a3ed1", "daily_target": 1}, "4": {"name": "Essen", "completed_dates": {"2025-07-14": 3, "2025-07-15": 3, "2025-07-16": 3, "2025-07-17": 3, "2025-07-18": 3}, "color": "#f01000", "daily_target": 3}}}
|
||||||
@@ -105,6 +105,38 @@ h1 {
|
|||||||
color: var(--habit-color, #4CAF50);
|
color: var(--habit-color, #4CAF50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Weekly Grid Layout */
|
||||||
|
.weekly-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-label {
|
||||||
|
width: 30px;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #b0b0b0;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-row .date-square {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
max-width: 25px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy date-grid for backward compatibility */
|
||||||
.date-grid {
|
.date-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(30, 1fr);
|
grid-template-columns: repeat(30, 1fr);
|
||||||
@@ -133,6 +165,11 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.date-square-readonly {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
.date-square.completed {
|
.date-square.completed {
|
||||||
background-color: var(--habit-color, #4CAF50);
|
background-color: var(--habit-color, #4CAF50);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ async function fetchHabits() {
|
|||||||
|
|
||||||
function renderHabits(habits) {
|
function renderHabits(habits) {
|
||||||
habitListDiv.innerHTML = ''; // Clear previous habits
|
habitListDiv.innerHTML = ''; // Clear previous habits
|
||||||
const last30Days = getPastDates(30);
|
|
||||||
|
|
||||||
habits.forEach(habit => {
|
habits.forEach(habit => {
|
||||||
const habitItem = document.createElement('div');
|
const habitItem = document.createElement('div');
|
||||||
@@ -127,27 +126,48 @@ function renderHabits(habits) {
|
|||||||
habitHeader.appendChild(habitActions);
|
habitHeader.appendChild(habitActions);
|
||||||
habitItem.appendChild(habitHeader);
|
habitItem.appendChild(habitHeader);
|
||||||
|
|
||||||
const dateGrid = document.createElement('div');
|
// Create weekly grid instead of single row
|
||||||
dateGrid.className = 'date-grid';
|
const weeklyGrid = document.createElement('div');
|
||||||
|
weeklyGrid.className = 'weekly-grid';
|
||||||
|
|
||||||
last30Days.forEach(date => {
|
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
|
||||||
|
|
||||||
|
weekdays.forEach(weekday => {
|
||||||
|
const weekdayRow = document.createElement('div');
|
||||||
|
weekdayRow.className = 'weekday-row';
|
||||||
|
|
||||||
|
// Weekday label
|
||||||
|
const weekdayLabel = document.createElement('div');
|
||||||
|
weekdayLabel.className = 'weekday-label';
|
||||||
|
weekdayLabel.textContent = weekday.substring(0, 2); // Show first 2 letters
|
||||||
|
weekdayRow.appendChild(weekdayLabel);
|
||||||
|
|
||||||
|
// Date squares for this weekday
|
||||||
|
const weekdayDates = habit.weekly_completion[weekday] || [];
|
||||||
|
weekdayDates.forEach(dateInfo => {
|
||||||
const dateSquare = document.createElement('div');
|
const dateSquare = document.createElement('div');
|
||||||
dateSquare.className = 'date-square';
|
dateSquare.className = 'date-square date-square-readonly';
|
||||||
const dateCount = habit.completed_dates[date] || 0;
|
dateSquare.dataset.date = dateInfo.date;
|
||||||
const isCompleted = dateCount >= dailyTarget;
|
|
||||||
|
|
||||||
// Apply gradient styling based on completion percentage
|
// Apply gradient styling based on completion percentage
|
||||||
applyCompletionStyling(dateSquare, dateCount, dailyTarget, habitColor);
|
applyCompletionStyling(dateSquare, dateInfo.completed, dateInfo.target, habitColor);
|
||||||
|
|
||||||
// Show count in tooltip if target > 1
|
// Show count in tooltip if target > 1
|
||||||
if (dailyTarget > 1) {
|
if (dateInfo.target > 1) {
|
||||||
dateSquare.title = `${date}: ${dateCount}/${dailyTarget}`;
|
dateSquare.title = `${dateInfo.date}: ${dateInfo.completed}/${dateInfo.target}`;
|
||||||
} else {
|
} else {
|
||||||
dateSquare.title = date;
|
dateSquare.title = dateInfo.date;
|
||||||
}
|
}
|
||||||
dateGrid.appendChild(dateSquare);
|
|
||||||
|
// No click handler - dates are read-only in main view
|
||||||
|
|
||||||
|
weekdayRow.appendChild(dateSquare);
|
||||||
});
|
});
|
||||||
habitItem.appendChild(dateGrid);
|
|
||||||
|
weeklyGrid.appendChild(weekdayRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
habitItem.appendChild(weeklyGrid);
|
||||||
|
|
||||||
// Streak Counter hinzufügen
|
// Streak Counter hinzufügen
|
||||||
const streakContainer = document.createElement('div');
|
const streakContainer = document.createElement('div');
|
||||||
@@ -421,15 +441,53 @@ async function deleteHabitFromModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleTodayCompletion(habitId, date, buttonElement, dailyTarget = 1) {
|
async function toggleDateCompletion(event, habitId, date) {
|
||||||
try {
|
try {
|
||||||
// Always increment completion count
|
// Always increment completion count for weekly grid clicks
|
||||||
const response = await fetch(`/habits/${habitId}/complete`, {
|
const response = await fetch(`/habits/${habitId}/complete`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ date: date })
|
body: JSON.stringify({ date: date })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
// Refresh the entire habits display to show updated completion status
|
||||||
|
fetchHabits();
|
||||||
|
} else {
|
||||||
|
console.error('Failed to toggle habit completion:', data.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error toggling habit completion:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleTodayCompletion(habitId, date, buttonElement, dailyTarget = 1) {
|
||||||
|
try {
|
||||||
|
// Get current count first to check if we should increment or reset
|
||||||
|
const currentHabits = await fetch('/habits');
|
||||||
|
const habits = await currentHabits.json();
|
||||||
|
const currentHabit = habits.find(h => h.id === habitId);
|
||||||
|
const currentCount = currentHabit?.completed_dates[date] || 0;
|
||||||
|
|
||||||
|
let response;
|
||||||
|
|
||||||
|
if (currentCount >= dailyTarget) {
|
||||||
|
// If already at or above target, reset to 0 with single API call
|
||||||
|
response = await fetch(`/habits/${habitId}/reset`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ date: date })
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Increment completion count
|
||||||
|
response = await fetch(`/habits/${habitId}/complete`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ date: date })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const newCount = data.current_count;
|
const newCount = data.current_count;
|
||||||
|
|||||||
Reference in New Issue
Block a user