feat: add floating quick task flow
This commit is contained in:
19
README.md
19
README.md
@@ -7,6 +7,7 @@ Putzliga ist eine moderne, leichte Haushaltsaufgaben-Web-App mit spielerischem C
|
|||||||
- Mehrere Nutzer mit Login, Admin-Nutzermanagement und Profil-/Avatar-Einstellungen
|
- Mehrere Nutzer mit Login, Admin-Nutzermanagement und Profil-/Avatar-Einstellungen
|
||||||
- Trennung zwischen `TaskTemplate` und `TaskInstance`
|
- Trennung zwischen `TaskTemplate` und `TaskInstance`
|
||||||
- Aufgaben anlegen, bearbeiten, zuweisen und erledigen
|
- Aufgaben anlegen, bearbeiten, zuweisen und erledigen
|
||||||
|
- globale Schnellaufgabe per Plus-Button mit Titel + Aufwand und automatisch passender Punktezahl
|
||||||
- Wiederholungen für einmalig, alle X Tage, alle X Wochen und alle X Monate
|
- Wiederholungen für einmalig, alle X Tage, alle X Wochen und alle X Monate
|
||||||
- Saubere Erledigungslogik für fremd zugewiesene Aufgaben mit Auswahl, wer wirklich erledigt hat
|
- Saubere Erledigungslogik für fremd zugewiesene Aufgaben mit Auswahl, wer wirklich erledigt hat
|
||||||
- Statuslogik für offen, bald fällig, überfällig und erledigt
|
- Statuslogik für offen, bald fällig, überfällig und erledigt
|
||||||
@@ -128,6 +129,24 @@ Admins können Nutzer zusätzlich direkt in der App unter `Optionen -> Profil &
|
|||||||
|
|
||||||
Badges werden dauerhaft pro Nutzer gespeichert und automatisch freigeschaltet. Die Badge-Regeln werden für Admins auf einer eigenen Seite unter `Optionen -> Badges` gepflegt.
|
Badges werden dauerhaft pro Nutzer gespeichert und automatisch freigeschaltet. Die Badge-Regeln werden für Admins auf einer eigenen Seite unter `Optionen -> Badges` gepflegt.
|
||||||
|
|
||||||
|
## Schnellaufgabe
|
||||||
|
|
||||||
|
Über den global sichtbaren Plus-Button rechts unten kannst du auf jeder eingeloggten Seite eine Schnellaufgabe anlegen.
|
||||||
|
|
||||||
|
- nur `Titel` und `Aufwand`
|
||||||
|
- die Aufgabe wird automatisch dem gerade eingeloggten Nutzer zugewiesen
|
||||||
|
- Fälligkeit ist direkt `heute`
|
||||||
|
- die Punkte hängen vom Aufwand ab
|
||||||
|
|
||||||
|
Die Aufwand-Stufen sind:
|
||||||
|
|
||||||
|
- Schnell
|
||||||
|
- Normal
|
||||||
|
- Dauert etwas
|
||||||
|
- Aufwendig
|
||||||
|
|
||||||
|
Admins können die Punkte je Aufwand unter `Optionen -> Profil & Team` anpassen.
|
||||||
|
|
||||||
### 5. Entwicklungsserver starten
|
### 5. Entwicklungsserver starten
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ from config import Config
|
|||||||
|
|
||||||
from .cli import register_cli, seed_badges
|
from .cli import register_cli, seed_badges
|
||||||
from .extensions import csrf, db, login_manager
|
from .extensions import csrf, db, login_manager
|
||||||
|
from .forms import QuickTaskForm
|
||||||
from .routes import auth, main, scoreboard, settings, tasks
|
from .routes import auth, main, scoreboard, settings, tasks
|
||||||
from .routes.main import load_icon_svg
|
from .routes.main import load_icon_svg
|
||||||
|
from .services.app_settings import get_quick_task_config
|
||||||
from .services.badges import sync_existing_badges
|
from .services.badges import sync_existing_badges
|
||||||
from .services.bootstrap import ensure_schema_and_admins
|
from .services.bootstrap import ensure_schema_and_admins
|
||||||
from .services.dates import MONTH_NAMES, local_now
|
from .services.dates import MONTH_NAMES, local_now
|
||||||
@@ -47,6 +49,12 @@ def create_app(config_class: type[Config] = Config) -> Flask:
|
|||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_globals():
|
def inject_globals():
|
||||||
|
quick_task_form = QuickTaskForm(prefix="quick")
|
||||||
|
quick_task_config = get_quick_task_config()
|
||||||
|
quick_task_form.effort.choices = [
|
||||||
|
(key, f"{values['label']} · {values['points']} Punkte")
|
||||||
|
for key, values in quick_task_config.items()
|
||||||
|
]
|
||||||
return {
|
return {
|
||||||
"app_name": app.config["APP_NAME"],
|
"app_name": app.config["APP_NAME"],
|
||||||
"nav_items": [
|
"nav_items": [
|
||||||
@@ -59,6 +67,8 @@ def create_app(config_class: type[Config] = Config) -> Flask:
|
|||||||
],
|
],
|
||||||
"icon_svg": lambda name: load_icon_svg(name, app.static_folder),
|
"icon_svg": lambda name: load_icon_svg(name, app.static_folder),
|
||||||
"now_local": local_now(),
|
"now_local": local_now(),
|
||||||
|
"quick_task_form": quick_task_form,
|
||||||
|
"quick_task_config": quick_task_config,
|
||||||
}
|
}
|
||||||
|
|
||||||
@app.template_filter("date_de")
|
@app.template_filter("date_de")
|
||||||
|
|||||||
19
app/forms.py
19
app/forms.py
@@ -15,6 +15,7 @@ from wtforms import (
|
|||||||
from wtforms.validators import DataRequired, EqualTo, Length, NumberRange, Optional, Regexp, ValidationError
|
from wtforms.validators import DataRequired, EqualTo, Length, NumberRange, Optional, Regexp, ValidationError
|
||||||
|
|
||||||
from .models import User
|
from .models import User
|
||||||
|
from .services.app_settings import QUICK_TASK_EFFORTS
|
||||||
|
|
||||||
|
|
||||||
EMAIL_LIKE = Regexp(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", message="Bitte gib eine gültige E-Mail-Adresse ein.")
|
EMAIL_LIKE = Regexp(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", message="Bitte gib eine gültige E-Mail-Adresse ein.")
|
||||||
@@ -111,3 +112,21 @@ class AdminUserForm(FlaskForm):
|
|||||||
value = field.data.lower().strip()
|
value = field.data.lower().strip()
|
||||||
if User.query.filter_by(email=value).first():
|
if User.query.filter_by(email=value).first():
|
||||||
raise ValidationError("Diese E-Mail-Adresse wird bereits verwendet.")
|
raise ValidationError("Diese E-Mail-Adresse wird bereits verwendet.")
|
||||||
|
|
||||||
|
|
||||||
|
class QuickTaskForm(FlaskForm):
|
||||||
|
title = StringField("Titel", validators=[DataRequired(), Length(min=2, max=160)])
|
||||||
|
effort = SelectField(
|
||||||
|
"Aufwand",
|
||||||
|
choices=[(key, label) for key, label, _ in QUICK_TASK_EFFORTS],
|
||||||
|
validators=[DataRequired()],
|
||||||
|
)
|
||||||
|
submit = SubmitField("Schnellaufgabe speichern")
|
||||||
|
|
||||||
|
|
||||||
|
class QuickTaskConfigForm(FlaskForm):
|
||||||
|
fast_points = IntegerField("Schnell", validators=[DataRequired(), NumberRange(min=1, max=500)])
|
||||||
|
normal_points = IntegerField("Normal", validators=[DataRequired(), NumberRange(min=1, max=500)])
|
||||||
|
medium_points = IntegerField("Dauert etwas", validators=[DataRequired(), NumberRange(min=1, max=500)])
|
||||||
|
heavy_points = IntegerField("Aufwendig", validators=[DataRequired(), NumberRange(min=1, max=500)])
|
||||||
|
submit = SubmitField("Punkte speichern")
|
||||||
|
|||||||
@@ -184,3 +184,9 @@ class UserBadge(db.Model):
|
|||||||
context = db.Column(db.Text, nullable=True)
|
context = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
__table_args__ = (db.UniqueConstraint("user_id", "badge_definition_id", name="uq_user_badge_once"),)
|
__table_args__ = (db.UniqueConstraint("user_id", "badge_definition_id", name="uq_user_badge_once"),)
|
||||||
|
|
||||||
|
|
||||||
|
class AppSetting(TimestampMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
key = db.Column(db.String(120), nullable=False, unique=True, index=True)
|
||||||
|
value = db.Column(db.String(255), nullable=False)
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ from flask_login import current_user, login_required
|
|||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from ..extensions import csrf, db
|
from ..extensions import csrf, db
|
||||||
from ..forms import AdminUserForm, SettingsProfileForm
|
from ..forms import AdminUserForm, QuickTaskConfigForm, SettingsProfileForm
|
||||||
from ..models import BadgeDefinition, MonthlyScoreSnapshot, NotificationLog, PushSubscription, TaskInstance, TaskTemplate, User
|
from ..models import BadgeDefinition, MonthlyScoreSnapshot, NotificationLog, PushSubscription, TaskInstance, TaskTemplate, User
|
||||||
|
from ..services.app_settings import get_quick_task_config, set_setting_int
|
||||||
from ..services.badges import earned_badges_for_user
|
from ..services.badges import earned_badges_for_user
|
||||||
from ..services.notifications import push_enabled
|
from ..services.notifications import push_enabled
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ def _save_avatar(file_storage) -> str:
|
|||||||
def index():
|
def index():
|
||||||
form = SettingsProfileForm(original_email=current_user.email, obj=current_user)
|
form = SettingsProfileForm(original_email=current_user.email, obj=current_user)
|
||||||
admin_form = AdminUserForm(prefix="admin")
|
admin_form = AdminUserForm(prefix="admin")
|
||||||
|
quick_task_config_form = QuickTaskConfigForm(prefix="quickconfig")
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
current_user.name = form.name.data.strip()
|
current_user.name = form.name.data.strip()
|
||||||
current_user.email = form.email.data.lower().strip()
|
current_user.email = form.email.data.lower().strip()
|
||||||
@@ -64,6 +66,8 @@ def index():
|
|||||||
"settings/index.html",
|
"settings/index.html",
|
||||||
form=form,
|
form=form,
|
||||||
admin_form=admin_form,
|
admin_form=admin_form,
|
||||||
|
quick_task_config_form=quick_task_config_form,
|
||||||
|
quick_task_config=get_quick_task_config(),
|
||||||
users=User.query.order_by(User.is_admin.desc(), User.name.asc()).all(),
|
users=User.query.order_by(User.is_admin.desc(), User.name.asc()).all(),
|
||||||
earned_badges=earned_badges_for_user(current_user.id),
|
earned_badges=earned_badges_for_user(current_user.id),
|
||||||
push_ready=push_enabled(),
|
push_ready=push_enabled(),
|
||||||
@@ -127,6 +131,28 @@ def create_user():
|
|||||||
return redirect(url_for("settings.index"))
|
return redirect(url_for("settings.index"))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/quick-task-config", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def update_quick_task_config():
|
||||||
|
if not _require_admin():
|
||||||
|
return redirect(url_for("settings.index"))
|
||||||
|
|
||||||
|
form = QuickTaskConfigForm(prefix="quickconfig")
|
||||||
|
if not form.validate_on_submit():
|
||||||
|
for field_errors in form.errors.values():
|
||||||
|
for error in field_errors:
|
||||||
|
flash(error, "error")
|
||||||
|
return redirect(url_for("settings.index"))
|
||||||
|
|
||||||
|
set_setting_int("quick_task_points_fast", form.fast_points.data)
|
||||||
|
set_setting_int("quick_task_points_normal", form.normal_points.data)
|
||||||
|
set_setting_int("quick_task_points_medium", form.medium_points.data)
|
||||||
|
set_setting_int("quick_task_points_heavy", form.heavy_points.data)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Schnellaufgaben-Punkte wurden aktualisiert.", "success")
|
||||||
|
return redirect(url_for("settings.index"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/users/<int:user_id>/toggle-admin", methods=["POST"])
|
@bp.route("/users/<int:user_id>/toggle-admin", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def toggle_admin(user_id: int):
|
def toggle_admin(user_id: int):
|
||||||
|
|||||||
@@ -7,10 +7,17 @@ from datetime import date
|
|||||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
from ..forms import TaskForm
|
from ..forms import QuickTaskForm, TaskForm
|
||||||
from ..models import TaskInstance, User
|
from ..models import TaskInstance, User
|
||||||
|
from ..services.app_settings import get_quick_task_config
|
||||||
from ..services.dates import month_label, today_local
|
from ..services.dates import month_label, today_local
|
||||||
from ..services.tasks import complete_task, create_task_template_and_instance, refresh_task_statuses, update_template_and_instance
|
from ..services.tasks import (
|
||||||
|
complete_task,
|
||||||
|
create_quick_task,
|
||||||
|
create_task_template_and_instance,
|
||||||
|
refresh_task_statuses,
|
||||||
|
update_template_and_instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("tasks", __name__, url_prefix="")
|
bp = Blueprint("tasks", __name__, url_prefix="")
|
||||||
@@ -99,6 +106,27 @@ def create():
|
|||||||
return render_template("tasks/task_form.html", form=form, mode="create", task=None)
|
return render_template("tasks/task_form.html", form=form, mode="create", task=None)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/tasks/quick", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def quick_create():
|
||||||
|
form = QuickTaskForm(prefix="quick")
|
||||||
|
config = get_quick_task_config()
|
||||||
|
form.effort.choices = [
|
||||||
|
(key, f"{values['label']} · {values['points']} Punkte")
|
||||||
|
for key, values in config.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
if not form.validate_on_submit():
|
||||||
|
for field_errors in form.errors.values():
|
||||||
|
for error in field_errors:
|
||||||
|
flash(error, "error")
|
||||||
|
return redirect(request.referrer or url_for("tasks.my_tasks"))
|
||||||
|
|
||||||
|
task = create_quick_task(form.title.data, form.effort.data, current_user)
|
||||||
|
flash(f"Schnellaufgabe „{task.title}“ wurde für dich angelegt.", "success")
|
||||||
|
return redirect(request.referrer or url_for("tasks.my_tasks"))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/tasks/<int:task_id>/edit", methods=["GET", "POST"])
|
@bp.route("/tasks/<int:task_id>/edit", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def edit(task_id: int):
|
def edit(task_id: int):
|
||||||
@@ -171,4 +199,3 @@ def calendar_view():
|
|||||||
view=view,
|
view=view,
|
||||||
tasks=tasks,
|
tasks=tasks,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
57
app/services/app_settings.py
Normal file
57
app/services/app_settings.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ..extensions import db
|
||||||
|
from ..models import AppSetting
|
||||||
|
|
||||||
|
|
||||||
|
QUICK_TASK_DEFAULTS = {
|
||||||
|
"quick_task_points_fast": 4,
|
||||||
|
"quick_task_points_normal": 8,
|
||||||
|
"quick_task_points_medium": 12,
|
||||||
|
"quick_task_points_heavy": 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
QUICK_TASK_EFFORTS = [
|
||||||
|
("fast", "Schnell", "quick_task_points_fast"),
|
||||||
|
("normal", "Normal", "quick_task_points_normal"),
|
||||||
|
("medium", "Dauert etwas", "quick_task_points_medium"),
|
||||||
|
("heavy", "Aufwendig", "quick_task_points_heavy"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_app_settings() -> None:
|
||||||
|
for key, value in QUICK_TASK_DEFAULTS.items():
|
||||||
|
setting = AppSetting.query.filter_by(key=key).first()
|
||||||
|
if not setting:
|
||||||
|
db.session.add(AppSetting(key=key, value=str(value)))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_setting_int(key: str, default: int) -> int:
|
||||||
|
setting = AppSetting.query.filter_by(key=key).first()
|
||||||
|
if not setting:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
return int(setting.value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def set_setting_int(key: str, value: int) -> None:
|
||||||
|
setting = AppSetting.query.filter_by(key=key).first()
|
||||||
|
if not setting:
|
||||||
|
setting = AppSetting(key=key, value=str(value))
|
||||||
|
db.session.add(setting)
|
||||||
|
else:
|
||||||
|
setting.value = str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def get_quick_task_config() -> dict[str, dict]:
|
||||||
|
config: dict[str, dict] = {}
|
||||||
|
for effort_key, label, setting_key in QUICK_TASK_EFFORTS:
|
||||||
|
config[effort_key] = {
|
||||||
|
"label": label,
|
||||||
|
"setting_key": setting_key,
|
||||||
|
"points": get_setting_int(setting_key, QUICK_TASK_DEFAULTS[setting_key]),
|
||||||
|
}
|
||||||
|
return config
|
||||||
@@ -6,6 +6,7 @@ from sqlalchemy import inspect, text
|
|||||||
|
|
||||||
from ..extensions import db
|
from ..extensions import db
|
||||||
from ..models import User
|
from ..models import User
|
||||||
|
from .app_settings import ensure_app_settings
|
||||||
|
|
||||||
|
|
||||||
def ensure_schema_and_admins() -> None:
|
def ensure_schema_and_admins() -> None:
|
||||||
@@ -16,6 +17,8 @@ def ensure_schema_and_admins() -> None:
|
|||||||
db.session.execute(text("ALTER TABLE user ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0"))
|
db.session.execute(text("ALTER TABLE user ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT 0"))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
ensure_app_settings()
|
||||||
|
|
||||||
admin_exists = User.query.filter_by(is_admin=True).first()
|
admin_exists = User.query.filter_by(is_admin=True).first()
|
||||||
if admin_exists:
|
if admin_exists:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from datetime import date, datetime, timedelta
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from ..extensions import db
|
from ..extensions import db
|
||||||
from ..models import TaskInstance, TaskTemplate
|
from ..models import TaskInstance, TaskTemplate, User
|
||||||
|
from .app_settings import get_quick_task_config
|
||||||
from .badges import evaluate_task_badges
|
from .badges import evaluate_task_badges
|
||||||
from .dates import add_months, today_local
|
from .dates import add_months, today_local
|
||||||
|
|
||||||
@@ -126,3 +127,33 @@ def complete_task(task: TaskInstance, completed_by_user_id: int) -> TaskInstance
|
|||||||
if task.completed_by_user:
|
if task.completed_by_user:
|
||||||
evaluate_task_badges(task.completed_by_user)
|
evaluate_task_badges(task.completed_by_user)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
|
def create_quick_task(title: str, effort: str, creator: User) -> TaskInstance:
|
||||||
|
config = get_quick_task_config()
|
||||||
|
effort_config = config[effort]
|
||||||
|
template = TaskTemplate(
|
||||||
|
title=title.strip(),
|
||||||
|
description="Schnellaufgabe",
|
||||||
|
default_points=effort_config["points"],
|
||||||
|
default_assigned_user_id=creator.id,
|
||||||
|
recurrence_interval_value=None,
|
||||||
|
recurrence_interval_unit="none",
|
||||||
|
active=False,
|
||||||
|
)
|
||||||
|
db.session.add(template)
|
||||||
|
db.session.flush()
|
||||||
|
|
||||||
|
task = TaskInstance(
|
||||||
|
task_template_id=template.id,
|
||||||
|
title=template.title,
|
||||||
|
description="Schnellaufgabe",
|
||||||
|
assigned_user_id=creator.id,
|
||||||
|
due_date=today_local(),
|
||||||
|
points_awarded=template.default_points,
|
||||||
|
status="open",
|
||||||
|
)
|
||||||
|
refresh_task_status(task, today_local())
|
||||||
|
db.session.add(task)
|
||||||
|
db.session.commit()
|
||||||
|
return task
|
||||||
|
|||||||
@@ -913,16 +913,50 @@ p {
|
|||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.complete-dialog__surface--task {
|
||||||
|
width: min(520px, calc(100vw - 24px));
|
||||||
|
}
|
||||||
|
|
||||||
.choice-grid {
|
.choice-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.text-link {
|
.text-link {
|
||||||
color: var(--primary-strong);
|
color: var(--primary-strong);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fab-quick-task {
|
||||||
|
position: fixed;
|
||||||
|
right: 18px;
|
||||||
|
bottom: calc(96px + env(safe-area-inset-bottom));
|
||||||
|
width: 62px;
|
||||||
|
height: 62px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(135deg, #2563eb, #34d399);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 24px 40px rgba(37, 99, 235, 0.28);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 45;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fab-quick-task .nav-icon,
|
||||||
|
.fab-quick-task .nav-icon svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 760px) {
|
@media (min-width: 760px) {
|
||||||
.page-shell {
|
.page-shell {
|
||||||
padding: 28px 28px 32px;
|
padding: 28px 28px 32px;
|
||||||
@@ -1017,6 +1051,11 @@ p {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fab-quick-task {
|
||||||
|
right: 28px;
|
||||||
|
bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
.task-grid,
|
.task-grid,
|
||||||
.scoreboard {
|
.scoreboard {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
const dialogChoice = document.getElementById("completeDialogChoice");
|
const dialogChoice = document.getElementById("completeDialogChoice");
|
||||||
const dialogText = document.getElementById("completeDialogText");
|
const dialogText = document.getElementById("completeDialogText");
|
||||||
const closeButton = document.getElementById("completeDialogClose");
|
const closeButton = document.getElementById("completeDialogClose");
|
||||||
|
const quickTaskDialog = document.getElementById("quickTaskDialog");
|
||||||
|
const quickTaskOpen = document.getElementById("quickTaskOpen");
|
||||||
|
const quickTaskClose = document.getElementById("quickTaskClose");
|
||||||
|
|
||||||
document.querySelectorAll("[data-complete-action]").forEach((button) => {
|
document.querySelectorAll("[data-complete-action]").forEach((button) => {
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
@@ -28,6 +31,14 @@
|
|||||||
closeButton.addEventListener("click", () => dialog.close());
|
closeButton.addEventListener("click", () => dialog.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (quickTaskOpen && quickTaskDialog) {
|
||||||
|
quickTaskOpen.addEventListener("click", () => quickTaskDialog.showModal());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quickTaskClose && quickTaskDialog) {
|
||||||
|
quickTaskClose.addEventListener("click", () => quickTaskDialog.close());
|
||||||
|
}
|
||||||
|
|
||||||
const pushButton = document.getElementById("pushToggle");
|
const pushButton = document.getElementById("pushToggle");
|
||||||
const pushHint = document.getElementById("pushHint");
|
const pushHint = document.getElementById("pushHint");
|
||||||
const vapidKey = document.body.dataset.pushKey;
|
const vapidKey = document.body.dataset.pushKey;
|
||||||
|
|||||||
@@ -97,6 +97,10 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<button type="button" class="fab-quick-task" id="quickTaskOpen" aria-label="Schnellaufgabe anlegen">
|
||||||
|
{{ nav_icon('plus') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<dialog class="complete-dialog" id="completeDialog">
|
<dialog class="complete-dialog" id="completeDialog">
|
||||||
<form method="dialog" class="complete-dialog__surface">
|
<form method="dialog" class="complete-dialog__surface">
|
||||||
<p class="eyebrow">Punkte fair verbuchen</p>
|
<p class="eyebrow">Punkte fair verbuchen</p>
|
||||||
@@ -110,6 +114,27 @@
|
|||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<dialog class="complete-dialog" id="quickTaskDialog">
|
||||||
|
<form method="post" action="{{ url_for('tasks.quick_create') }}" class="complete-dialog__surface complete-dialog__surface--task">
|
||||||
|
{{ quick_task_form.hidden_tag() }}
|
||||||
|
<p class="eyebrow">Schnellaufgabe</p>
|
||||||
|
<h2>Direkt etwas für dich anlegen</h2>
|
||||||
|
<p class="muted">Titel und Aufwand reichen. Die Aufgabe wird automatisch dir zugewiesen und auf heute gesetzt.</p>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_form.title.label }}
|
||||||
|
{{ quick_task_form.title(placeholder="Zum Beispiel: Küche kurz aufräumen") }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_form.effort.label }}
|
||||||
|
{{ quick_task_form.effort() }}
|
||||||
|
</div>
|
||||||
|
<div class="dialog-actions">
|
||||||
|
{{ quick_task_form.submit(class_='button') }}
|
||||||
|
<button type="button" class="button button--ghost" id="quickTaskClose">Abbrechen</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<form method="post" class="sr-only" id="completeDialogForm">
|
<form method="post" class="sr-only" id="completeDialogForm">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<input type="hidden" name="completed_for" value="me" id="completeDialogChoice">
|
<input type="hidden" name="completed_for" value="me" id="completeDialogChoice">
|
||||||
|
|||||||
@@ -163,5 +163,57 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<p class="eyebrow">Admin</p>
|
||||||
|
<h2>Schnellaufgabe-Punkte</h2>
|
||||||
|
<p class="muted">Diese Werte erscheinen direkt im Schnellaufgaben-Dialog hinter den Aufwand-Stufen.</p>
|
||||||
|
<form method="post" action="{{ url_for('settings.update_quick_task_config') }}" class="badge-settings">
|
||||||
|
{{ quick_task_config_form.hidden_tag() }}
|
||||||
|
<div class="badge-setting-card">
|
||||||
|
<div>
|
||||||
|
<strong>Schnell</strong>
|
||||||
|
<p class="muted">Kleine Sache für zwischendurch.</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_config_form.fast_points.label }}
|
||||||
|
{{ quick_task_config_form.fast_points(value=quick_task_config['fast']['points']) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge-setting-card">
|
||||||
|
<div>
|
||||||
|
<strong>Normal</strong>
|
||||||
|
<p class="muted">Typische Alltagssache.</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_config_form.normal_points.label }}
|
||||||
|
{{ quick_task_config_form.normal_points(value=quick_task_config['normal']['points']) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge-setting-card">
|
||||||
|
<div>
|
||||||
|
<strong>Dauert etwas</strong>
|
||||||
|
<p class="muted">Braucht etwas mehr Zeit oder Konzentration.</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_config_form.medium_points.label }}
|
||||||
|
{{ quick_task_config_form.medium_points(value=quick_task_config['medium']['points']) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="badge-setting-card">
|
||||||
|
<div>
|
||||||
|
<strong>Aufwendig</strong>
|
||||||
|
<p class="muted">Spürbarer Aufwand mit mehr Punkten.</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
{{ quick_task_config_form.heavy_points.label }}
|
||||||
|
{{ quick_task_config_form.heavy_points(value=quick_task_config['heavy']['points']) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field field--full">
|
||||||
|
{{ quick_task_config_form.submit(class_='button button--secondary') }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user