Add week planner entry editing popups
This commit is contained in:
+10
-1
@@ -3815,6 +3815,7 @@ def planner():
|
|||||||
week_hints=build_week_hints(week_start),
|
week_hints=build_week_hints(week_start),
|
||||||
upcoming_entries=fetch_upcoming_shopping_needs(limit=8),
|
upcoming_entries=fetch_upcoming_shopping_needs(limit=8),
|
||||||
household_settings=get_household_settings(),
|
household_settings=get_household_settings(),
|
||||||
|
visibility_options=VISIBILITY_FORM_OPTIONS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -3941,6 +3942,7 @@ def planner_generated_meal():
|
|||||||
@login_required
|
@login_required
|
||||||
def planner_update(entry_id: int):
|
def planner_update(entry_id: int):
|
||||||
selected_date = parse_plan_date(request.form.get("plan_date"))
|
selected_date = parse_plan_date(request.form.get("plan_date"))
|
||||||
|
return_week = request.form.get("return_week", "").strip()
|
||||||
entry = get_db().execute(
|
entry = get_db().execute(
|
||||||
f"""
|
f"""
|
||||||
SELECT plan_entries.*,
|
SELECT plan_entries.*,
|
||||||
@@ -3960,19 +3962,24 @@ def planner_update(entry_id: int):
|
|||||||
ensure_can_edit(describe_record(dict(entry)), "Diesen Planeintrag kannst du gerade nicht bearbeiten.")
|
ensure_can_edit(describe_record(dict(entry)), "Diesen Planeintrag kannst du gerade nicht bearbeiten.")
|
||||||
except PermissionError as exc:
|
except PermissionError as exc:
|
||||||
flash(str(exc), "error")
|
flash(str(exc), "error")
|
||||||
|
if return_week:
|
||||||
|
return redirect(url_for("main.planner", week=return_week))
|
||||||
return redirect(url_for("main.planner_day", date=selected_date.isoformat()))
|
return redirect(url_for("main.planner_day", date=selected_date.isoformat()))
|
||||||
|
|
||||||
visibility = normalize_visibility(request.form.get("visibility"), entry["visibility"])
|
visibility = normalize_visibility(request.form.get("visibility"), entry["visibility"])
|
||||||
note = request.form.get("note", "").strip()
|
note = request.form.get("note", "").strip()
|
||||||
update_plan_entry(entry_id, visibility=visibility, note=note)
|
update_plan_entry(entry_id, visibility=visibility, note=note)
|
||||||
flash("Der Planeintrag wurde angepasst.", "success")
|
flash("Der Planeintrag wurde angepasst.", "success")
|
||||||
|
if return_week:
|
||||||
|
return redirect(url_for("main.planner", week=return_week))
|
||||||
return redirect(url_for("main.planner_day", date=selected_date.isoformat(), daypart_id=entry["daypart_id"]))
|
return redirect(url_for("main.planner_day", date=selected_date.isoformat(), daypart_id=entry["daypart_id"]))
|
||||||
|
|
||||||
|
|
||||||
@main_bp.post("/planner/<int:entry_id>/remove")
|
@main_bp.post("/planner/<int:entry_id>/remove")
|
||||||
@login_required
|
@login_required
|
||||||
def planner_remove(entry_id: int):
|
def planner_remove(entry_id: int):
|
||||||
selected_date = request.args.get("date", "")
|
selected_date = request.args.get("date", "") or request.form.get("plan_date", "").strip()
|
||||||
|
return_week = request.form.get("return_week", "").strip()
|
||||||
entry = get_db().execute(
|
entry = get_db().execute(
|
||||||
f"""
|
f"""
|
||||||
SELECT plan_entries.*,
|
SELECT plan_entries.*,
|
||||||
@@ -3994,6 +4001,8 @@ def planner_remove(entry_id: int):
|
|||||||
flash("Der Planeintrag wurde entfernt.", "info")
|
flash("Der Planeintrag wurde entfernt.", "info")
|
||||||
except PermissionError as exc:
|
except PermissionError as exc:
|
||||||
flash(str(exc), "error")
|
flash(str(exc), "error")
|
||||||
|
if return_week:
|
||||||
|
return redirect(url_for("main.planner", week=return_week))
|
||||||
if selected_date:
|
if selected_date:
|
||||||
return redirect(url_for("main.planner_day", date=selected_date))
|
return redirect(url_for("main.planner_day", date=selected_date))
|
||||||
return redirect(url_for("main.planner"))
|
return redirect(url_for("main.planner"))
|
||||||
|
|||||||
@@ -1583,6 +1583,17 @@ legend {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plan-chip.is-editable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-chip.is-editable:hover {
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 34%, var(--line) 66%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
||||||
|
0 10px 22px rgba(94, 68, 49, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
.plan-chip:active {
|
.plan-chip:active {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
}
|
}
|
||||||
@@ -1602,6 +1613,55 @@ legend {
|
|||||||
padding: 0.55rem 0.85rem;
|
padding: 0.55rem 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
max-width: min(34rem, calc(100vw - 2rem));
|
||||||
|
width: min(34rem, calc(100vw - 2rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog::backdrop {
|
||||||
|
background: rgba(29, 22, 19, 0.54);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog-card {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.1rem;
|
||||||
|
border-radius: 22px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
background: color-mix(in srgb, var(--surface) 98%, #fff 2%);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog-head h3 {
|
||||||
|
margin: 0 0 0.2rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog-head p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-entry-remove-form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.plan-chip small,
|
.plan-chip small,
|
||||||
.week-slot-empty {
|
.week-slot-empty {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -1687,6 +1747,13 @@ legend {
|
|||||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .plan-chip.is-editable:hover {
|
||||||
|
border-color: rgba(243, 177, 125, 0.3);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.06),
|
||||||
|
0 12px 26px rgba(0, 0, 0, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .week-slot-copy {
|
[data-theme="dark"] .week-slot-copy {
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
border-color: rgba(243, 177, 125, 0.12);
|
border-color: rgba(243, 177, 125, 0.12);
|
||||||
@@ -1701,6 +1768,12 @@ legend {
|
|||||||
border-color: rgba(243, 177, 125, 0.16);
|
border-color: rgba(243, 177, 125, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .week-entry-dialog-card {
|
||||||
|
background: rgba(43, 37, 35, 0.98);
|
||||||
|
border-color: rgba(243, 177, 125, 0.14);
|
||||||
|
box-shadow: 0 24px 50px rgba(0, 0, 0, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
.flash-stack {
|
.flash-stack {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.7rem;
|
gap: 0.7rem;
|
||||||
|
|||||||
@@ -95,12 +95,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
rememberScroll();
|
||||||
if (result.redirect_url) {
|
if (result.redirect_url) {
|
||||||
window.location.href = result.redirect_url;
|
window.location.href = result.redirect_url;
|
||||||
} else {
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
|
rememberScroll();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -196,6 +198,72 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initWeekEntryDialogs = () => {
|
||||||
|
const board = document.querySelector(".week-board");
|
||||||
|
if (!board) return;
|
||||||
|
|
||||||
|
const openDialog = (trigger) => {
|
||||||
|
const dialogId = trigger.getAttribute("data-week-entry-dialog-id");
|
||||||
|
if (!dialogId) return;
|
||||||
|
const dialog = document.getElementById(dialogId);
|
||||||
|
if (!(dialog instanceof HTMLDialogElement)) return;
|
||||||
|
if (!dialog.open) {
|
||||||
|
dialog.showModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
board.querySelectorAll("[data-week-entry-open]").forEach((entry) => {
|
||||||
|
entry.addEventListener("click", (event) => {
|
||||||
|
if (event.target instanceof Element && event.target.closest("button, a, input, select, textarea, label, form")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openDialog(entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
entry.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key !== "Enter" && event.key !== " ") return;
|
||||||
|
event.preventDefault();
|
||||||
|
openDialog(entry);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".week-entry-dialog").forEach((dialog) => {
|
||||||
|
if (!(dialog instanceof HTMLDialogElement)) return;
|
||||||
|
|
||||||
|
dialog.addEventListener("click", (event) => {
|
||||||
|
const rect = dialog.getBoundingClientRect();
|
||||||
|
const clickedInside =
|
||||||
|
rect.top <= event.clientY &&
|
||||||
|
event.clientY <= rect.top + rect.height &&
|
||||||
|
rect.left <= event.clientX &&
|
||||||
|
event.clientX <= rect.left + rect.width;
|
||||||
|
if (!clickedInside) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-week-entry-close]").forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const dialog = button.closest(".week-entry-dialog");
|
||||||
|
if (dialog instanceof HTMLDialogElement) {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".js-week-entry-submit").forEach((form) => {
|
||||||
|
form.addEventListener("submit", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
try {
|
||||||
|
await postAndRefreshInPlace(form);
|
||||||
|
} catch (_error) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const syncActionContainerVisibility = (container) => {
|
const syncActionContainerVisibility = (container) => {
|
||||||
if (!(container instanceof HTMLElement)) return;
|
if (!(container instanceof HTMLElement)) return;
|
||||||
const hasVisibleButtons = Array.from(container.querySelectorAll("button")).some((button) => {
|
const hasVisibleButtons = Array.from(container.querySelectorAll("button")).some((button) => {
|
||||||
@@ -285,6 +353,7 @@
|
|||||||
initWeekDragAndDrop();
|
initWeekDragAndDrop();
|
||||||
initWeekCopyForward();
|
initWeekCopyForward();
|
||||||
initWeekSlotPicker();
|
initWeekSlotPicker();
|
||||||
|
initWeekEntryDialogs();
|
||||||
initDaySnackReveal();
|
initDaySnackReveal();
|
||||||
initWeekSnackReveal();
|
initWeekSnackReveal();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -213,10 +213,61 @@
|
|||||||
{% if slot.entries %}
|
{% if slot.entries %}
|
||||||
<div class="week-entry-stack">
|
<div class="week-entry-stack">
|
||||||
{% for entry in slot.entries %}
|
{% for entry in slot.entries %}
|
||||||
<article class="plan-chip draggable-plan-entry" draggable="{{ 'true' if entry.can_edit else 'false' }}" data-entry-id="{{ entry.id }}" data-move-url="{{ url_for('main.planner_move', entry_id=entry.id) }}">
|
<article
|
||||||
|
class="plan-chip draggable-plan-entry{% if entry.can_edit %} is-editable{% endif %}"
|
||||||
|
draggable="{{ 'true' if entry.can_edit else 'false' }}"
|
||||||
|
data-entry-id="{{ entry.id }}"
|
||||||
|
data-move-url="{{ url_for('main.planner_move', entry_id=entry.id) }}"
|
||||||
|
{% if entry.can_edit %}
|
||||||
|
data-week-entry-open
|
||||||
|
data-week-entry-dialog-id="week-entry-dialog-{{ entry.id }}"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
aria-label="{{ entry.item_name }} bearbeiten"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
<strong>{{ entry.item_name }}</strong>
|
<strong>{{ entry.item_name }}</strong>
|
||||||
<small>{{ entry.visibility_label }} · {{ entry.for_label }}</small>
|
<small>{{ entry.visibility_label }} · {{ entry.for_label }}</small>
|
||||||
</article>
|
</article>
|
||||||
|
{% if entry.can_edit %}
|
||||||
|
<dialog class="week-entry-dialog" id="week-entry-dialog-{{ entry.id }}">
|
||||||
|
<div class="week-entry-dialog-card">
|
||||||
|
<div class="week-entry-dialog-head">
|
||||||
|
<div>
|
||||||
|
<h3>{{ entry.item_name }}</h3>
|
||||||
|
<p>{{ slot.daypart.name }} · {{ weekday_name(card.date) }}, {{ card.date.strftime('%d.%m.%Y') }}</p>
|
||||||
|
</div>
|
||||||
|
<button class="ghost-button" type="button" data-week-entry-close>Schließen</button>
|
||||||
|
</div>
|
||||||
|
<form method="post" action="{{ url_for('main.planner_update', entry_id=entry.id) }}" class="planner-entry-inline-form js-week-entry-submit">
|
||||||
|
{{ csrf_input() }}
|
||||||
|
<input type="hidden" name="plan_date" value="{{ card.date.isoformat() }}">
|
||||||
|
<input type="hidden" name="return_week" value="{{ week_start.isoformat() }}">
|
||||||
|
<label>
|
||||||
|
Für wen?
|
||||||
|
<select name="visibility">
|
||||||
|
{% for value, label in visibility_options %}
|
||||||
|
<option value="{{ value }}" {% if entry.visibility == value %}selected{% endif %}>{{ label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="wide">
|
||||||
|
Notiz
|
||||||
|
<input type="text" name="note" value="{{ entry.note or '' }}" placeholder="Optional">
|
||||||
|
</label>
|
||||||
|
<div class="week-entry-dialog-actions">
|
||||||
|
<button type="submit">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="{{ url_for('main.planner_remove', entry_id=entry.id) }}" class="week-entry-remove-form js-week-entry-submit">
|
||||||
|
{{ csrf_input() }}
|
||||||
|
<input type="hidden" name="plan_date" value="{{ card.date.isoformat() }}">
|
||||||
|
<input type="hidden" name="return_week" value="{{ week_start.isoformat() }}">
|
||||||
|
<button class="ghost-button" type="submit">Eintrag entfernen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="week-slot-actions">
|
<div class="week-slot-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user