feat: migrate remaining 5 modules to shared modal system
Migrate budget, contacts, notes, meals, calendar to use the shared openModal/closeModal from components/modal.js. Each module now gets focus-trap, escape-handler, overlay-click, focus-restore, scroll-lock. Removed ~460 lines of duplicate modal CSS (.budget-modal-overlay, .contact-modal-overlay, .note-modal-overlay, .meal-modal-overlay, .event-modal-overlay and their children). Content-specific styles (color-picker, autocomplete, ingredient-list, etc.) are preserved. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+35
-51
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
@@ -134,7 +135,7 @@ function wireNav() {
|
||||
renderBody();
|
||||
updateLabel();
|
||||
});
|
||||
const addHandler = () => openModal({ mode: 'create' });
|
||||
const addHandler = () => openBudgetModal({ mode: 'create' });
|
||||
_container.querySelector('#budget-add').addEventListener('click', addHandler);
|
||||
_container.querySelector('#fab-new-budget').addEventListener('click', addHandler);
|
||||
updateLabel();
|
||||
@@ -208,7 +209,7 @@ function renderBody() {
|
||||
const item = e.target.closest('.budget-entry[data-id]');
|
||||
if (item && !e.target.closest('[data-action]')) {
|
||||
const entry = state.entries.find((e) => e.id === parseInt(item.dataset.id, 10));
|
||||
if (entry) openModal({ mode: 'edit', entry });
|
||||
if (entry) openBudgetModal({ mode: 'edit', entry });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -276,32 +277,18 @@ function formatEntryDate(dateStr) {
|
||||
// Modal
|
||||
// --------------------------------------------------------
|
||||
|
||||
function openModal({ mode, entry = null }) {
|
||||
document.querySelector('#budget-modal-overlay')?.remove();
|
||||
|
||||
function openBudgetModal({ mode, entry = null }) {
|
||||
const isEdit = mode === 'edit';
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const isExpense = isEdit ? entry.amount < 0 : true; // Standard: Ausgabe
|
||||
const isExpense = isEdit ? entry.amount < 0 : true;
|
||||
const absAmount = isEdit ? Math.abs(entry.amount).toFixed(2) : '';
|
||||
|
||||
const catOpts = CATEGORIES.map((c) =>
|
||||
`<option value="${c}" ${isEdit && entry.category === c ? 'selected' : ''}>${c}</option>`
|
||||
).join('');
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'budget-modal-overlay';
|
||||
overlay.className = 'budget-modal-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="budget-modal" role="dialog" aria-modal="true">
|
||||
<div class="budget-modal__header">
|
||||
<h2 class="budget-modal__title">${isEdit ? 'Eintrag bearbeiten' : 'Neuer Eintrag'}</h2>
|
||||
<button class="budget-modal__close" id="bm-close" aria-label="Schließen">
|
||||
<i data-lucide="x" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="budget-modal__body">
|
||||
<!-- Einnahme / Ausgabe Toggle -->
|
||||
const content = `
|
||||
<div class="amount-type-toggle">
|
||||
<button class="amount-type-btn amount-type-btn--expenses ${isExpense ? 'amount-type-btn--active' : ''}"
|
||||
id="type-expense" type="button">Ausgabe</button>
|
||||
@@ -339,52 +326,50 @@ function openModal({ mode, entry = null }) {
|
||||
<span class="allday-toggle__label">Wiederkehrend</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="budget-modal__footer">
|
||||
|
||||
<div class="modal-panel__footer" style="border:none;padding:0;margin-top:var(--space-4)">
|
||||
${isEdit ? `<button class="btn btn--danger btn--icon" id="bm-delete" title="Löschen">
|
||||
<i data-lucide="trash-2" style="width:16px;height:16px;"></i>
|
||||
</button>` : '<div></div>'}
|
||||
<div class="budget-modal__footer-actions">
|
||||
<div style="display:flex;gap:var(--space-3)">
|
||||
<button class="btn btn--secondary" id="bm-cancel">Abbrechen</button>
|
||||
<button class="btn btn--primary" id="bm-save">${isEdit ? 'Speichern' : 'Hinzufügen'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
if (window.lucide) lucide.createIcons();
|
||||
|
||||
// Typ-Toggle
|
||||
openSharedModal({
|
||||
title: isEdit ? 'Eintrag bearbeiten' : 'Neuer Eintrag',
|
||||
content,
|
||||
size: 'sm',
|
||||
onSave(panel) {
|
||||
let currentType = isExpense ? 'expense' : 'income';
|
||||
overlay.querySelector('#type-expense').addEventListener('click', () => {
|
||||
|
||||
panel.querySelector('#type-expense').addEventListener('click', () => {
|
||||
currentType = 'expense';
|
||||
overlay.querySelector('#type-expense').classList.add('amount-type-btn--active');
|
||||
overlay.querySelector('#type-income').classList.remove('amount-type-btn--active');
|
||||
panel.querySelector('#type-expense').classList.add('amount-type-btn--active');
|
||||
panel.querySelector('#type-income').classList.remove('amount-type-btn--active');
|
||||
});
|
||||
overlay.querySelector('#type-income').addEventListener('click', () => {
|
||||
panel.querySelector('#type-income').addEventListener('click', () => {
|
||||
currentType = 'income';
|
||||
overlay.querySelector('#type-income').classList.add('amount-type-btn--active');
|
||||
overlay.querySelector('#type-expense').classList.remove('amount-type-btn--active');
|
||||
panel.querySelector('#type-income').classList.add('amount-type-btn--active');
|
||||
panel.querySelector('#type-expense').classList.remove('amount-type-btn--active');
|
||||
});
|
||||
|
||||
overlay.querySelector('#bm-close').addEventListener('click', () => overlay.remove());
|
||||
overlay.querySelector('#bm-cancel').addEventListener('click', () => overlay.remove());
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
panel.querySelector('#bm-cancel').addEventListener('click', closeModal);
|
||||
|
||||
overlay.querySelector('#bm-delete')?.addEventListener('click', async () => {
|
||||
panel.querySelector('#bm-delete')?.addEventListener('click', async () => {
|
||||
if (!confirm(`"${entry.title}" wirklich löschen?`)) return;
|
||||
overlay.remove();
|
||||
closeModal();
|
||||
await deleteEntry(entry.id);
|
||||
});
|
||||
|
||||
overlay.querySelector('#bm-save').addEventListener('click', async () => {
|
||||
const saveBtn = overlay.querySelector('#bm-save');
|
||||
const title = overlay.querySelector('#bm-title').value.trim();
|
||||
const absVal = parseFloat(overlay.querySelector('#bm-amount').value);
|
||||
const category = overlay.querySelector('#bm-category').value;
|
||||
const date = overlay.querySelector('#bm-date').value;
|
||||
const recurring = overlay.querySelector('#bm-recurring').checked ? 1 : 0;
|
||||
panel.querySelector('#bm-save').addEventListener('click', async () => {
|
||||
const saveBtn = panel.querySelector('#bm-save');
|
||||
const title = panel.querySelector('#bm-title').value.trim();
|
||||
const absVal = parseFloat(panel.querySelector('#bm-amount').value);
|
||||
const category = panel.querySelector('#bm-category').value;
|
||||
const date = panel.querySelector('#bm-date').value;
|
||||
const recurring = panel.querySelector('#bm-recurring').checked ? 1 : 0;
|
||||
|
||||
if (!title) { window.oikos?.showToast('Titel ist erforderlich', 'error'); return; }
|
||||
if (isNaN(absVal) || absVal <= 0) { window.oikos?.showToast('Gültigen Betrag eingeben', 'error'); return; }
|
||||
@@ -405,11 +390,10 @@ function openModal({ mode, entry = null }) {
|
||||
const idx = state.entries.findIndex((e) => e.id === entry.id);
|
||||
if (idx !== -1) state.entries[idx] = res.data;
|
||||
}
|
||||
// Zusammenfassung neu laden
|
||||
const sumRes = await api.get(`/budget/summary?month=${state.month}`);
|
||||
state.summary = sumRes.data;
|
||||
|
||||
overlay.remove();
|
||||
closeModal();
|
||||
renderBody();
|
||||
window.oikos?.showToast(mode === 'create' ? 'Eintrag hinzugefügt' : 'Eintrag gespeichert', 'success');
|
||||
} catch (err) {
|
||||
@@ -418,8 +402,8 @@ function openModal({ mode, entry = null }) {
|
||||
saveBtn.textContent = isEdit ? 'Speichern' : 'Hinzufügen';
|
||||
}
|
||||
});
|
||||
|
||||
overlay.querySelector('#bm-title').focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
+35
-66
@@ -6,6 +6,7 @@
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
@@ -703,61 +704,54 @@ function showEventPopup(ev, anchor) {
|
||||
// --------------------------------------------------------
|
||||
|
||||
function openEventModal({ mode, event = null, date = null }) {
|
||||
document.querySelector('#event-modal-overlay')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'event-modal-overlay';
|
||||
overlay.className = 'event-modal-overlay';
|
||||
overlay.innerHTML = buildEventModalHTML({ mode, event, date });
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
|
||||
// RRULE-Events binden
|
||||
bindRRuleEvents(overlay, 'event');
|
||||
|
||||
const isEdit = mode === 'edit';
|
||||
const content = buildEventModalContent({ mode, event, date });
|
||||
|
||||
openSharedModal({
|
||||
title: isEdit ? 'Termin bearbeiten' : 'Neuer Termin',
|
||||
content,
|
||||
size: 'md',
|
||||
onSave(panel) {
|
||||
// RRULE-Events binden
|
||||
bindRRuleEvents(panel, 'event');
|
||||
|
||||
const selectedColor = isEdit ? (event?.color || EVENT_COLORS[0]) : EVENT_COLORS[0];
|
||||
|
||||
// Farb-Auswahl
|
||||
overlay.querySelectorAll('.color-swatch').forEach((sw) => {
|
||||
panel.querySelectorAll('.color-swatch').forEach((sw) => {
|
||||
sw.addEventListener('click', () => {
|
||||
overlay.querySelectorAll('.color-swatch').forEach((s) => s.classList.remove('color-swatch--active'));
|
||||
panel.querySelectorAll('.color-swatch').forEach((s) => s.classList.remove('color-swatch--active'));
|
||||
sw.classList.add('color-swatch--active');
|
||||
});
|
||||
});
|
||||
// Initial aktive Farbe markieren
|
||||
overlay.querySelectorAll('.color-swatch').forEach((sw) => {
|
||||
panel.querySelectorAll('.color-swatch').forEach((sw) => {
|
||||
if (sw.dataset.color === selectedColor) sw.classList.add('color-swatch--active');
|
||||
});
|
||||
|
||||
// Ganztägig-Toggle
|
||||
const alldayCheck = overlay.querySelector('#modal-allday');
|
||||
const timeFields = overlay.querySelector('#time-fields');
|
||||
const alldayCheck = panel.querySelector('#modal-allday');
|
||||
const timeFields = panel.querySelector('#time-fields');
|
||||
const alldayFields = panel.querySelector('#allday-fields');
|
||||
alldayCheck.addEventListener('change', () => {
|
||||
timeFields.style.display = alldayCheck.checked ? 'none' : '';
|
||||
if (alldayCheck.checked) { timeFields.style.display = 'none'; alldayFields.style.display = ''; }
|
||||
else { timeFields.style.display = ''; alldayFields.style.display = 'none'; }
|
||||
});
|
||||
if (isEdit && event?.all_day) timeFields.style.display = 'none';
|
||||
if (isEdit && event?.all_day) { timeFields.style.display = 'none'; alldayFields.style.display = ''; }
|
||||
|
||||
// Schließen
|
||||
overlay.querySelector('#modal-close').addEventListener('click', closeEventModal);
|
||||
overlay.querySelector('#modal-cancel').addEventListener('click', closeEventModal);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeEventModal(); });
|
||||
panel.querySelector('#modal-cancel').addEventListener('click', closeModal);
|
||||
|
||||
// Löschen (nur Edit)
|
||||
overlay.querySelector('#modal-delete')?.addEventListener('click', async () => {
|
||||
panel.querySelector('#modal-delete')?.addEventListener('click', async () => {
|
||||
if (!confirm(`"${event.title}" wirklich löschen?`)) return;
|
||||
closeEventModal();
|
||||
closeModal();
|
||||
await deleteEvent(event.id);
|
||||
});
|
||||
|
||||
// Speichern
|
||||
overlay.querySelector('#modal-save').addEventListener('click', () => saveEvent(overlay, mode, event?.id));
|
||||
|
||||
overlay.querySelector('#modal-title').focus();
|
||||
panel.querySelector('#modal-save').addEventListener('click', () => saveEvent(panel, mode, event?.id));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildEventModalHTML({ mode, event, date }) {
|
||||
function buildEventModalContent({ mode, event, date }) {
|
||||
const isEdit = mode === 'edit';
|
||||
const today = date || state.today;
|
||||
|
||||
@@ -776,14 +770,6 @@ function buildEventModalHTML({ mode, event, date }) {
|
||||
].join('');
|
||||
|
||||
return `
|
||||
<div class="event-modal" role="dialog" aria-modal="true">
|
||||
<div class="event-modal__header">
|
||||
<h2 class="event-modal__title">${isEdit ? 'Termin bearbeiten' : 'Neuer Termin'}</h2>
|
||||
<button class="event-modal__close" id="modal-close" aria-label="Schließen">
|
||||
<i data-lucide="x" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="event-modal__body">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-title">Titel *</label>
|
||||
<input type="text" class="form-input" id="modal-title"
|
||||
@@ -798,7 +784,7 @@ function buildEventModalHTML({ mode, event, date }) {
|
||||
</div>
|
||||
|
||||
<div id="time-fields">
|
||||
<div class="event-modal__row">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-start-date">Startdatum</label>
|
||||
<input type="date" class="form-input" id="modal-start-date" value="${startDate}">
|
||||
@@ -808,7 +794,7 @@ function buildEventModalHTML({ mode, event, date }) {
|
||||
<input type="time" class="form-input" id="modal-start-time" value="${startTime}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-modal__row">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-end-date">Enddatum</label>
|
||||
<input type="date" class="form-input" id="modal-end-date" value="${endDate}">
|
||||
@@ -820,9 +806,8 @@ function buildEventModalHTML({ mode, event, date }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ganztägig: nur Datum -->
|
||||
<div id="allday-fields" style="display:none;">
|
||||
<div class="event-modal__row">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="modal-allday-start">Von</label>
|
||||
<input type="date" class="form-input" id="modal-allday-start" value="${startDate}">
|
||||
@@ -862,32 +847,16 @@ function buildEventModalHTML({ mode, event, date }) {
|
||||
</div>
|
||||
|
||||
${renderRRuleFields('event', isEdit ? event.recurrence_rule : null)}
|
||||
</div>
|
||||
<div class="event-modal__footer">
|
||||
|
||||
<div class="modal-panel__footer" style="border:none;padding:0;margin-top:var(--space-4)">
|
||||
${isEdit ? `<button class="btn btn--danger btn--icon" id="modal-delete" title="Löschen">
|
||||
<i data-lucide="trash-2" style="width:16px;height:16px;"></i>
|
||||
</button>` : '<div></div>'}
|
||||
<div class="event-modal__footer-actions">
|
||||
<div style="display:flex;gap:var(--space-3)">
|
||||
<button class="btn btn--secondary" id="modal-cancel">Abbrechen</button>
|
||||
<button class="btn btn--primary" id="modal-save">${isEdit ? 'Speichern' : 'Erstellen'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Allday-Toggle: Felder umschalten
|
||||
document.addEventListener('change', (e) => {
|
||||
if (e.target.id !== 'modal-allday') return;
|
||||
const tf = document.querySelector('#time-fields');
|
||||
const af = document.querySelector('#allday-fields');
|
||||
if (!tf || !af) return;
|
||||
if (e.target.checked) { tf.style.display = 'none'; af.style.display = ''; }
|
||||
else { tf.style.display = ''; af.style.display = 'none'; }
|
||||
});
|
||||
|
||||
function closeEventModal() {
|
||||
document.querySelector('#event-modal-overlay')?.remove();
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function saveEvent(overlay, mode, eventId) {
|
||||
@@ -943,7 +912,7 @@ async function saveEvent(overlay, mode, eventId) {
|
||||
if (idx !== -1) state.events[idx] = res.data;
|
||||
}
|
||||
|
||||
closeEventModal();
|
||||
closeModal();
|
||||
renderView();
|
||||
window.oikos?.showToast(mode === 'create' ? 'Termin erstellt' : 'Termin gespeichert', 'success');
|
||||
} catch (err) {
|
||||
|
||||
+27
-41
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
@@ -95,7 +96,7 @@ export async function render(container, { user }) {
|
||||
});
|
||||
|
||||
// Neu
|
||||
const addHandler = () => openModal({ mode: 'create' });
|
||||
const addHandler = () => openContactModal({ mode: 'create' });
|
||||
_container.querySelector('#contacts-add-btn').addEventListener('click', addHandler);
|
||||
_container.querySelector('#fab-new-contact').addEventListener('click', addHandler);
|
||||
}
|
||||
@@ -168,7 +169,7 @@ function renderList() {
|
||||
const item = e.target.closest('.contact-item[data-id]');
|
||||
if (item && !e.target.closest('a') && !e.target.closest('[data-action]')) {
|
||||
const c = state.contacts.find((c) => c.id === parseInt(item.dataset.id, 10));
|
||||
if (c) openModal({ mode: 'edit', contact: c });
|
||||
if (c) openContactModal({ mode: 'edit', contact: c });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -200,9 +201,7 @@ function renderContactItem(c) {
|
||||
// Modal
|
||||
// --------------------------------------------------------
|
||||
|
||||
function openModal({ mode, contact = null }) {
|
||||
document.querySelector('#contact-modal-overlay')?.remove();
|
||||
|
||||
function openContactModal({ mode, contact = null }) {
|
||||
const isEdit = mode === 'edit';
|
||||
const v = (field) => escHtml(isEdit && contact[field] ? contact[field] : '');
|
||||
|
||||
@@ -210,18 +209,7 @@ function openModal({ mode, contact = null }) {
|
||||
`<option value="${c}" ${isEdit && contact.category === c ? 'selected' : ''}>${c}</option>`
|
||||
).join('');
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'contact-modal-overlay';
|
||||
overlay.className = 'contact-modal-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="contact-modal" role="dialog" aria-modal="true">
|
||||
<div class="contact-modal__header">
|
||||
<h2 class="contact-modal__title">${isEdit ? 'Kontakt bearbeiten' : 'Neuer Kontakt'}</h2>
|
||||
<button class="contact-modal__close" id="cm-close" aria-label="Schließen">
|
||||
<i data-lucide="x" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="contact-modal__body">
|
||||
const content = `
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="cm-name">Name *</label>
|
||||
<input type="text" class="form-input" id="cm-name" placeholder="Vollständiger Name" value="${v('name')}">
|
||||
@@ -246,8 +234,8 @@ function openModal({ mode, contact = null }) {
|
||||
<label class="form-label" for="cm-notes">Notizen</label>
|
||||
<textarea class="form-input" id="cm-notes" rows="2" placeholder="Optional…">${v('notes')}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="contact-modal__footer">
|
||||
|
||||
<div class="modal-panel__footer" style="border:none;padding:0;margin-top:var(--space-4)">
|
||||
${isEdit ? `<button class="btn btn--danger btn--icon" id="cm-delete" title="Löschen">
|
||||
<i data-lucide="trash-2" style="width:16px;height:16px;"></i>
|
||||
</button>` : '<div></div>'}
|
||||
@@ -255,31 +243,29 @@ function openModal({ mode, contact = null }) {
|
||||
<button class="btn btn--secondary" id="cm-cancel">Abbrechen</button>
|
||||
<button class="btn btn--primary" id="cm-save">${isEdit ? 'Speichern' : 'Erstellen'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
if (window.lucide) lucide.createIcons();
|
||||
openSharedModal({
|
||||
title: isEdit ? 'Kontakt bearbeiten' : 'Neuer Kontakt',
|
||||
content,
|
||||
size: 'md',
|
||||
onSave(panel) {
|
||||
panel.querySelector('#cm-cancel').addEventListener('click', closeModal);
|
||||
|
||||
overlay.querySelector('#cm-close').addEventListener('click', () => overlay.remove());
|
||||
overlay.querySelector('#cm-cancel').addEventListener('click', () => overlay.remove());
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
|
||||
overlay.querySelector('#cm-delete')?.addEventListener('click', async () => {
|
||||
panel.querySelector('#cm-delete')?.addEventListener('click', async () => {
|
||||
if (!confirm(`"${contact.name}" wirklich löschen?`)) return;
|
||||
overlay.remove();
|
||||
closeModal();
|
||||
await deleteContact(contact.id);
|
||||
});
|
||||
|
||||
overlay.querySelector('#cm-save').addEventListener('click', async () => {
|
||||
const saveBtn = overlay.querySelector('#cm-save');
|
||||
const name = overlay.querySelector('#cm-name').value.trim();
|
||||
const category = overlay.querySelector('#cm-category').value;
|
||||
const phone = overlay.querySelector('#cm-phone').value.trim() || null;
|
||||
const email = overlay.querySelector('#cm-email').value.trim() || null;
|
||||
const address = overlay.querySelector('#cm-address').value.trim() || null;
|
||||
const notes = overlay.querySelector('#cm-notes').value.trim() || null;
|
||||
panel.querySelector('#cm-save').addEventListener('click', async () => {
|
||||
const saveBtn = panel.querySelector('#cm-save');
|
||||
const name = panel.querySelector('#cm-name').value.trim();
|
||||
const category = panel.querySelector('#cm-category').value;
|
||||
const phone = panel.querySelector('#cm-phone').value.trim() || null;
|
||||
const email = panel.querySelector('#cm-email').value.trim() || null;
|
||||
const address = panel.querySelector('#cm-address').value.trim() || null;
|
||||
const notes = panel.querySelector('#cm-notes').value.trim() || null;
|
||||
|
||||
if (!name) { window.oikos?.showToast('Name ist erforderlich', 'error'); return; }
|
||||
|
||||
@@ -300,7 +286,7 @@ function openModal({ mode, contact = null }) {
|
||||
const idx = state.contacts.findIndex((c) => c.id === contact.id);
|
||||
if (idx !== -1) state.contacts[idx] = res.data;
|
||||
}
|
||||
overlay.remove();
|
||||
closeModal();
|
||||
renderList();
|
||||
window.oikos?.showToast(mode === 'create' ? 'Kontakt gespeichert' : 'Kontakt aktualisiert', 'success');
|
||||
} catch (err) {
|
||||
@@ -309,8 +295,8 @@ function openModal({ mode, contact = null }) {
|
||||
saveBtn.textContent = isEdit ? 'Speichern' : 'Erstellen';
|
||||
}
|
||||
});
|
||||
|
||||
overlay.querySelector('#cm-name').focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteContact(id) {
|
||||
|
||||
+32
-46
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { openModal as openSharedModal, closeModal as closeSharedModal } from '/components/modal.js';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
@@ -248,14 +249,14 @@ function wireGrid(grid) {
|
||||
const action = btn.dataset.action;
|
||||
|
||||
if (action === 'add-meal') {
|
||||
openModal({ mode: 'create', date: btn.dataset.date, mealType: btn.dataset.type });
|
||||
openMealModal({ mode: 'create', date: btn.dataset.date, mealType: btn.dataset.type });
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'edit-meal') {
|
||||
const mealId = parseInt(btn.dataset.mealId, 10);
|
||||
const meal = state.meals.find((m) => m.id === mealId);
|
||||
if (meal) openModal({ mode: 'edit', meal, date: meal.date, mealType: meal.meal_type });
|
||||
if (meal) openMealModal({ mode: 'edit', meal, date: meal.date, mealType: meal.meal_type });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -281,21 +282,21 @@ function wireGrid(grid) {
|
||||
// Modal
|
||||
// --------------------------------------------------------
|
||||
|
||||
function openModal(opts) {
|
||||
function openMealModal(opts) {
|
||||
state.modal = opts;
|
||||
document.querySelector('#meal-modal-overlay')?.remove();
|
||||
const { mode, date, mealType, meal } = opts;
|
||||
const isEdit = mode === 'edit';
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'meal-modal-overlay';
|
||||
overlay.className = 'meal-modal-overlay';
|
||||
overlay.innerHTML = buildModalHTML(opts);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
if (window.lucide) lucide.createIcons();
|
||||
const content = buildModalContent(opts);
|
||||
|
||||
openSharedModal({
|
||||
title: isEdit ? 'Mahlzeit bearbeiten' : 'Mahlzeit hinzufügen',
|
||||
content,
|
||||
size: 'md',
|
||||
onSave(panel) {
|
||||
// Autocomplete
|
||||
const titleInput = overlay.querySelector('#modal-title');
|
||||
const acDropdown = overlay.querySelector('#modal-autocomplete');
|
||||
const titleInput = panel.querySelector('#modal-title');
|
||||
const acDropdown = panel.querySelector('#modal-autocomplete');
|
||||
let acIndex = -1;
|
||||
let acTimer;
|
||||
|
||||
@@ -331,8 +332,8 @@ function openModal(opts) {
|
||||
});
|
||||
|
||||
// Zutaten
|
||||
const ingList = overlay.querySelector('#ingredient-list');
|
||||
const addIngBtn = overlay.querySelector('#add-ingredient-btn');
|
||||
const ingList = panel.querySelector('#ingredient-list');
|
||||
const addIngBtn = panel.querySelector('#add-ingredient-btn');
|
||||
|
||||
addIngBtn.addEventListener('click', () => {
|
||||
const tmp = document.createElement('div');
|
||||
@@ -348,12 +349,12 @@ function openModal(opts) {
|
||||
if (btn) btn.closest('.ingredient-row').remove();
|
||||
});
|
||||
|
||||
// Einkaufslisten-Transfer Button im Modal
|
||||
overlay.querySelector('#transfer-btn')?.addEventListener('click', async () => {
|
||||
const selectEl = overlay.querySelector('#transfer-list-select');
|
||||
// Einkaufslisten-Transfer
|
||||
panel.querySelector('#transfer-btn')?.addEventListener('click', async () => {
|
||||
const selectEl = panel.querySelector('#transfer-list-select');
|
||||
const listId = parseInt(selectEl?.value, 10);
|
||||
if (!listId || !state.modal?.meal) return;
|
||||
const btn = overlay.querySelector('#transfer-btn');
|
||||
const btn = panel.querySelector('#transfer-btn');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
const res = await api.post(`/meals/${state.modal.meal.id}/to-shopping-list`, { listId });
|
||||
@@ -372,18 +373,13 @@ function openModal(opts) {
|
||||
}
|
||||
});
|
||||
|
||||
// Schließen
|
||||
overlay.querySelector('#modal-close').addEventListener('click', closeModal);
|
||||
overlay.querySelector('#modal-cancel').addEventListener('click', closeModal);
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); });
|
||||
|
||||
// Speichern
|
||||
overlay.querySelector('#modal-save').addEventListener('click', () => saveModal(overlay));
|
||||
|
||||
titleInput.focus();
|
||||
panel.querySelector('#modal-cancel').addEventListener('click', closeModal);
|
||||
panel.querySelector('#modal-save').addEventListener('click', () => saveModal(panel));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildModalHTML({ mode, date, mealType, meal }) {
|
||||
function buildModalContent({ mode, date, mealType, meal }) {
|
||||
const isEdit = mode === 'edit';
|
||||
const typeOpts = MEAL_TYPES.map((t) =>
|
||||
`<option value="${t.key}" ${t.key === mealType ? 'selected' : ''}>${t.label}</option>`
|
||||
@@ -400,20 +396,12 @@ function buildModalHTML({ mode, date, mealType, meal }) {
|
||||
const hasIngOpen = isEdit && meal.ingredients?.some((i) => !i.on_shopping_list);
|
||||
|
||||
return `
|
||||
<div class="meal-modal" role="dialog" aria-modal="true">
|
||||
<div class="meal-modal__header">
|
||||
<h2 class="meal-modal__title">${isEdit ? 'Mahlzeit bearbeiten' : 'Mahlzeit hinzufügen'}</h2>
|
||||
<button class="meal-modal__close" id="modal-close" aria-label="Schließen">
|
||||
<i data-lucide="x" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="meal-modal__body">
|
||||
<div class="meal-modal__row">
|
||||
<div class="form-group">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label class="form-label" for="modal-date">Datum</label>
|
||||
<input type="date" class="form-input" id="modal-date" value="${date}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="margin-bottom:0">
|
||||
<label class="form-label" for="modal-type">Mahlzeit</label>
|
||||
<select class="form-input" id="modal-type">${typeOpts}</select>
|
||||
</div>
|
||||
@@ -454,13 +442,11 @@ function buildModalHTML({ mode, date, mealType, meal }) {
|
||||
Jetzt übertragen
|
||||
</button>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<div class="meal-modal__footer">
|
||||
|
||||
<div class="modal-panel__footer" style="border:none;padding:0;margin-top:var(--space-4)">
|
||||
<button class="btn btn--secondary" id="modal-cancel">Abbrechen</button>
|
||||
<button class="btn btn--primary" id="modal-save">${isEdit ? 'Speichern' : 'Hinzufügen'}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function ingredientRowHTML(name, qty, id) {
|
||||
@@ -476,7 +462,7 @@ function ingredientRowHTML(name, qty, id) {
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.querySelector('#meal-modal-overlay')?.remove();
|
||||
closeSharedModal();
|
||||
state.modal = null;
|
||||
}
|
||||
|
||||
|
||||
+28
-44
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { api } from '/api.js';
|
||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
||||
|
||||
// --------------------------------------------------------
|
||||
// Konstanten
|
||||
@@ -71,7 +72,7 @@ export async function render(container, { user }) {
|
||||
}
|
||||
renderGrid();
|
||||
|
||||
const addHandler = () => openModal({ mode: 'create' });
|
||||
const addHandler = () => openNoteModal({ mode: 'create' });
|
||||
_container.querySelector('#notes-add-btn').addEventListener('click', addHandler);
|
||||
_container.querySelector('#fab-new-note').addEventListener('click', addHandler);
|
||||
}
|
||||
@@ -112,7 +113,7 @@ function renderGrid() {
|
||||
const card = e.target.closest('.note-card[data-id]');
|
||||
if (card) {
|
||||
const note = state.notes.find((n) => n.id === parseInt(card.dataset.id, 10));
|
||||
if (note) openModal({ mode: 'edit', note });
|
||||
if (note) openNoteModal({ mode: 'edit', note });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -152,25 +153,11 @@ function renderNoteCard(note) {
|
||||
// Modal
|
||||
// --------------------------------------------------------
|
||||
|
||||
function openModal({ mode, note = null }) {
|
||||
document.querySelector('#note-modal-overlay')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'note-modal-overlay';
|
||||
overlay.className = 'note-modal-overlay';
|
||||
|
||||
function openNoteModal({ mode, note = null }) {
|
||||
const isEdit = mode === 'edit';
|
||||
const selColor = isEdit ? note.color : NOTE_COLORS[0];
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div class="note-modal" role="dialog" aria-modal="true">
|
||||
<div class="note-modal__header">
|
||||
<h2 class="note-modal__title">${isEdit ? 'Notiz bearbeiten' : 'Neue Notiz'}</h2>
|
||||
<button class="note-modal__close" id="note-modal-close" aria-label="Schließen">
|
||||
<i data-lucide="x" style="width:16px;height:16px;"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="note-modal__body">
|
||||
const content = `
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="note-title">Titel (optional)</label>
|
||||
<input type="text" class="form-input" id="note-title"
|
||||
@@ -199,53 +186,50 @@ function openModal({ mode, note = null }) {
|
||||
<span class="allday-toggle__label">Anpinnen (erscheint auf Dashboard)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note-modal__footer">
|
||||
|
||||
<div class="modal-panel__footer" style="border:none;padding:0;margin-top:var(--space-4)">
|
||||
<button class="btn btn--secondary" id="note-modal-cancel">Abbrechen</button>
|
||||
<button class="btn btn--primary" id="note-modal-save">${isEdit ? 'Speichern' : 'Erstellen'}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
if (window.lucide) lucide.createIcons();
|
||||
</div>`;
|
||||
|
||||
openSharedModal({
|
||||
title: isEdit ? 'Notiz bearbeiten' : 'Neue Notiz',
|
||||
content,
|
||||
size: 'md',
|
||||
onSave(panel) {
|
||||
// Farb-Swatch
|
||||
overlay.querySelectorAll('.note-color-swatch').forEach((sw) => {
|
||||
panel.querySelectorAll('.note-color-swatch').forEach((sw) => {
|
||||
sw.addEventListener('click', () => {
|
||||
overlay.querySelectorAll('.note-color-swatch').forEach((s) => s.classList.remove('note-color-swatch--active'));
|
||||
panel.querySelectorAll('.note-color-swatch').forEach((s) => s.classList.remove('note-color-swatch--active'));
|
||||
sw.classList.add('note-color-swatch--active');
|
||||
});
|
||||
});
|
||||
|
||||
overlay.querySelector('#note-modal-close').addEventListener('click', () => overlay.remove());
|
||||
overlay.querySelector('#note-modal-cancel').addEventListener('click', () => overlay.remove());
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
panel.querySelector('#note-modal-cancel').addEventListener('click', closeModal);
|
||||
|
||||
overlay.querySelector('#note-modal-save').addEventListener('click', async () => {
|
||||
const saveBtn = overlay.querySelector('#note-modal-save');
|
||||
const title = overlay.querySelector('#note-title').value.trim() || null;
|
||||
const content = overlay.querySelector('#note-content').value.trim();
|
||||
const color = overlay.querySelector('.note-color-swatch--active')?.dataset.color || NOTE_COLORS[0];
|
||||
const pinned = overlay.querySelector('#note-pinned').checked ? 1 : 0;
|
||||
panel.querySelector('#note-modal-save').addEventListener('click', async () => {
|
||||
const saveBtn = panel.querySelector('#note-modal-save');
|
||||
const title = panel.querySelector('#note-title').value.trim() || null;
|
||||
const cnt = panel.querySelector('#note-content').value.trim();
|
||||
const color = panel.querySelector('.note-color-swatch--active')?.dataset.color || NOTE_COLORS[0];
|
||||
const pinned = panel.querySelector('#note-pinned').checked ? 1 : 0;
|
||||
|
||||
if (!content) { window.oikos?.showToast('Inhalt ist erforderlich', 'error'); return; }
|
||||
if (!cnt) { window.oikos?.showToast('Inhalt ist erforderlich', 'error'); return; }
|
||||
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = '…';
|
||||
|
||||
try {
|
||||
if (mode === 'create') {
|
||||
const res = await api.post('/notes', { title, content, color, pinned });
|
||||
const res = await api.post('/notes', { title, content: cnt, color, pinned });
|
||||
state.notes.unshift(res.data);
|
||||
} else {
|
||||
const res = await api.put(`/notes/${note.id}`, { title, content, color, pinned });
|
||||
const res = await api.put(`/notes/${note.id}`, { title, content: cnt, color, pinned });
|
||||
const idx = state.notes.findIndex((n) => n.id === note.id);
|
||||
if (idx !== -1) state.notes[idx] = res.data;
|
||||
// Angepinnte nach oben sortieren
|
||||
state.notes.sort((a, b) => b.pinned - a.pinned);
|
||||
}
|
||||
overlay.remove();
|
||||
closeModal();
|
||||
renderGrid();
|
||||
window.oikos?.showToast(mode === 'create' ? 'Notiz erstellt' : 'Notiz gespeichert', 'success');
|
||||
} catch (err) {
|
||||
@@ -254,8 +238,8 @@ function openModal({ mode, note = null }) {
|
||||
saveBtn.textContent = isEdit ? 'Speichern' : 'Erstellen';
|
||||
}
|
||||
});
|
||||
|
||||
overlay.querySelector('#note-content').focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
|
||||
@@ -285,81 +285,8 @@
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Modal
|
||||
* Budget-Modal Content-Styles (Overlay/Panel via shared modal.js)
|
||||
* -------------------------------------------------------- */
|
||||
.budget-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--color-overlay);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn var(--transition-fast) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.budget-modal-overlay {
|
||||
align-items: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
.budget-modal {
|
||||
background-color: var(--color-surface);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slideUp var(--transition-base) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.budget-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 460px;
|
||||
max-height: 80dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.budget-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.budget-modal__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.budget-modal__close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.budget-modal__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* Einnahme/Ausgabe-Toggle */
|
||||
.amount-type-toggle {
|
||||
@@ -394,16 +321,3 @@
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.budget-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.budget-modal__footer-actions {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
@@ -513,90 +513,8 @@
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Termin-Modal
|
||||
* Calendar-Modal Content-Styles (Overlay/Panel via shared modal.js)
|
||||
* -------------------------------------------------------- */
|
||||
.event-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--color-overlay);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn var(--transition-fast) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.event-modal-overlay {
|
||||
align-items: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
.event-modal {
|
||||
background-color: var(--color-surface);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slideUp var(--transition-base) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.event-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 560px;
|
||||
max-height: 85dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.event-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.event-modal__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.event-modal__close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: unset;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-modal__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.event-modal__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Farbauswahl */
|
||||
.color-picker {
|
||||
@@ -636,20 +554,6 @@
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.event-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-modal__footer-actions {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Termin-Detailansicht (Popup beim Klick)
|
||||
* -------------------------------------------------------- */
|
||||
|
||||
@@ -230,88 +230,3 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Modal (gleiche Struktur wie andere Module)
|
||||
* -------------------------------------------------------- */
|
||||
.contact-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--color-overlay);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn var(--transition-fast) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.contact-modal-overlay {
|
||||
align-items: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
.contact-modal {
|
||||
background-color: var(--color-surface);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slideUp var(--transition-base) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.contact-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 480px;
|
||||
max-height: 85dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.contact-modal__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.contact-modal__close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.contact-modal__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.contact-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
+1
-106
@@ -244,102 +244,8 @@
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Modal — Mahlzeit erstellen / bearbeiten
|
||||
* Meals-Modal Content-Styles (Overlay/Panel via shared modal.js)
|
||||
* -------------------------------------------------------- */
|
||||
.meal-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--color-overlay);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
animation: fadeIn var(--transition-fast) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.meal-modal-overlay {
|
||||
align-items: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
.meal-modal {
|
||||
background-color: var(--color-surface);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slideUp var(--transition-base) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.meal-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 520px;
|
||||
max-height: 80dvh;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(100%); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.meal-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.meal-modal__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.meal-modal__close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: unset;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.meal-modal__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* Formular-Zeile: Datum + Typ */
|
||||
.meal-modal__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Autocomplete im Modal */
|
||||
.meal-modal__autocomplete {
|
||||
@@ -459,17 +365,6 @@
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* Modal-Footer */
|
||||
.meal-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Mahlzeit-Typ Labels (DE)
|
||||
* -------------------------------------------------------- */
|
||||
|
||||
+1
-83
@@ -214,81 +214,8 @@
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------
|
||||
* Modal
|
||||
* Notes-Modal Content-Styles (Overlay/Panel via shared modal.js)
|
||||
* -------------------------------------------------------- */
|
||||
.note-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: var(--color-overlay);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
animation: fadeIn var(--transition-fast) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.note-modal-overlay {
|
||||
align-items: center;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
}
|
||||
|
||||
.note-modal {
|
||||
background-color: var(--color-surface);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
animation: slideUp var(--transition-base) ease;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.note-modal {
|
||||
border-radius: var(--radius-lg);
|
||||
max-width: 520px;
|
||||
max-height: 80dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.note-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-4);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-modal__title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.note-modal__close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-border);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-secondary);
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.note-modal__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--space-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
/* Farb-Auswahl */
|
||||
.note-color-picker {
|
||||
@@ -309,12 +236,3 @@
|
||||
.note-color-swatch:hover { transform: scale(1.15); }
|
||||
.note-color-swatch--active { border-color: var(--color-text-primary); transform: scale(1.1); }
|
||||
|
||||
.note-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user