Notizblock: Notiz-Button für 6 neue Bereiche + RUBRIKEN + Datenschutz — SW by-v425
Neue Notiz-Buttons: - Tagebuch: in der Detail-Ansicht (nicht Edit-Form) - Trainingspläne: im Plan-Header pro Plan - Freunde: in jedem Freund-Karten-Bereich - Giftköder: in jedem Meldungs-Karten (private Umstände) - Verlorener Hund: in jedem Eintrag Notizblock: - 4 neue RUBRIKEN: trainingsplan, friends, poison, lost - Datenschutz-Hinweis: "Alle Notizen sind privat" - lock-simple Icon zum Sprite hinzugefügt
This commit is contained in:
parent
b801571bf0
commit
02120bb532
9 changed files with 430 additions and 10 deletions
|
|
@ -166,4 +166,6 @@
|
|||
<symbol id="warning-circle" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z"/></symbol>
|
||||
|
||||
<symbol id="note" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H156.69A15.92,15.92,0,0,0,168,219.31L219.31,168A15.92,15.92,0,0,0,224,156.69V48A16,16,0,0,0,208,32ZM96,88h64a8,8,0,0,1,0,16H96a8,8,0,0,1,0-16Zm32,80H96a8,8,0,0,1,0-16h32a8,8,0,0,1,0,16ZM96,136a8,8,0,0,1,0-16h64a8,8,0,0,1,0,16Zm64,68.69V160h44.7Z"/></symbol>
|
||||
|
||||
<symbol id="lock-simple" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80ZM96,56a32,32,0,0,1,64,0V80H96Z"/></symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '403'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '404'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
|
||||
const App = (() => {
|
||||
|
||||
|
|
|
|||
|
|
@ -1105,11 +1105,17 @@ window.Page_diary = (() => {
|
|||
Zurück
|
||||
</button>
|
||||
<span class="diary-detail-date-center">${datumLang}</span>
|
||||
${!_appState?.activeDog?.is_guest
|
||||
? `<button id="diary-dv-edit" class="diary-detail-edit">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#pencil-simple"></use></svg>
|
||||
</button>`
|
||||
: '<div style="width:40px"></div>'}
|
||||
<div style="display:flex;align-items:center;gap:4px">
|
||||
${!_appState?.activeDog?.is_guest
|
||||
? `<button id="diary-dv-note" class="btn btn-ghost btn-xs" title="Notiz"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
|
||||
</button>
|
||||
<button id="diary-dv-edit" class="diary-detail-edit">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#pencil-simple"></use></svg>
|
||||
</button>`
|
||||
: '<div style="width:40px"></div>'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${heroSection}
|
||||
|
|
@ -1174,6 +1180,13 @@ window.Page_diary = (() => {
|
|||
};
|
||||
view.querySelector('#diary-dv-back').addEventListener('click', _closeDetail);
|
||||
|
||||
// Notiz-Button in Detailansicht
|
||||
view.querySelector('#diary-dv-note')?.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const label = entry.titel || entry.datum || String(entry.id);
|
||||
_openNoteModal('diary', entry.id, label, entry.location_name || null);
|
||||
});
|
||||
|
||||
// Bearbeiten
|
||||
view.querySelector('#diary-dv-edit')?.addEventListener('click', async () => {
|
||||
_container.querySelector('#diary-fab')?.style.removeProperty('display');
|
||||
|
|
|
|||
|
|
@ -434,6 +434,16 @@ window.Page_friends = (() => {
|
|||
</div>
|
||||
`;
|
||||
|
||||
// Notiz-Buttons
|
||||
el.querySelectorAll('.fr-note-btn').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const id = parseInt(btn.dataset.frNoteId);
|
||||
const name = btn.dataset.frNoteName || '';
|
||||
_openNoteModal('friends', id, name, null);
|
||||
});
|
||||
});
|
||||
|
||||
// Klick auf Karte → Mini-Profil
|
||||
el.querySelectorAll('.fr-card').forEach(card => {
|
||||
card.addEventListener('click', e => {
|
||||
|
|
@ -492,6 +502,13 @@ window.Page_friends = (() => {
|
|||
|
||||
<!-- Aktionen -->
|
||||
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
|
||||
<button class="btn btn-ghost btn-sm fr-note-btn"
|
||||
data-fr-note-id="${f.friend_id}"
|
||||
data-fr-note-name="${_esc(f.friend_name)}"
|
||||
title="Notiz"
|
||||
onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-sm"
|
||||
onclick="Page_friends._openChat(${f.friend_id})"
|
||||
title="Nachricht schreiben">
|
||||
|
|
@ -830,6 +847,87 @@ window.Page_friends = (() => {
|
|||
</div>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// NOTIZ-MODAL
|
||||
// ----------------------------------------------------------
|
||||
async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
|
||||
document.getElementById('by-note-modal')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'by-note-modal';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
|
||||
width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
|
||||
padding-bottom:env(safe-area-inset-bottom,0px)">
|
||||
<div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
|
||||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
|
||||
<form id="by-note-form">
|
||||
<textarea id="by-note-text" class="form-control" rows="5"
|
||||
placeholder="Notiz eingeben…"
|
||||
style="width:100%;resize:vertical"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
<div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
|
||||
display:flex;gap:var(--space-2);flex-shrink:0">
|
||||
<button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
|
||||
<button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const textarea = document.getElementById('by-note-text');
|
||||
const saveBtn = document.getElementById('by-note-save');
|
||||
const cancelBtn = document.getElementById('by-note-cancel');
|
||||
const closeBtn = document.getElementById('by-note-close');
|
||||
|
||||
let existingNoteId = null;
|
||||
|
||||
try {
|
||||
const existing = await API.notes.get(parentType, String(parentId));
|
||||
if (existing?.id) {
|
||||
existingNoteId = existing.id;
|
||||
textarea.value = existing.text || '';
|
||||
}
|
||||
} catch (_) { /* keine Notiz vorhanden — ok */ }
|
||||
|
||||
setTimeout(() => textarea.focus(), 100);
|
||||
|
||||
const _close = () => overlay.remove();
|
||||
closeBtn.addEventListener('click', _close);
|
||||
cancelBtn.addEventListener('click', _close);
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
|
||||
|
||||
document.getElementById('by-note-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const text = textarea.value.trim();
|
||||
UI.setLoading(saveBtn, true);
|
||||
try {
|
||||
const payload = { text, parent_label: parentLabel, location_name: locationName };
|
||||
if (existingNoteId) {
|
||||
await API.notes.update(existingNoteId, payload);
|
||||
} else {
|
||||
await API.notes.create(parentType, String(parentId), payload);
|
||||
}
|
||||
UI.toast.success('Notiz gespeichert.');
|
||||
_close();
|
||||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Speichern.');
|
||||
UI.setLoading(saveBtn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
return { init, refresh, onDogChange, _accept, _decline, _cancel, _removeFriend, _openChat };
|
||||
|
||||
|
|
|
|||
|
|
@ -275,6 +275,14 @@ window.Page_lost = (() => {
|
|||
if (r) _openDetail(r);
|
||||
});
|
||||
});
|
||||
listEl.querySelectorAll('.lost-note-btn').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const id = parseInt(btn.dataset.lostNoteId);
|
||||
const name = btn.dataset.lostNoteName || '';
|
||||
_openNoteModal('lost', id, name, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _reportCard(r) {
|
||||
|
|
@ -324,6 +332,14 @@ window.Page_lost = (() => {
|
|||
Gemeldet ${_fmtDate(r.created_at)}
|
||||
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
|
||||
</div>
|
||||
${_appState.user ? `<div style="margin-top:var(--space-2)">
|
||||
<button class="btn btn-ghost btn-xs lost-note-btn"
|
||||
data-lost-note-id="${r.id}"
|
||||
data-lost-note-name="${_escape(r.name)}"
|
||||
title="Notiz" onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz
|
||||
</button>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -693,6 +709,87 @@ window.Page_lost = (() => {
|
|||
</div>`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// NOTIZ-MODAL
|
||||
// ----------------------------------------------------------
|
||||
async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
|
||||
document.getElementById('by-note-modal')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'by-note-modal';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
|
||||
width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
|
||||
padding-bottom:env(safe-area-inset-bottom,0px)">
|
||||
<div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
|
||||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
|
||||
<form id="by-note-form">
|
||||
<textarea id="by-note-text" class="form-control" rows="5"
|
||||
placeholder="Notiz eingeben…"
|
||||
style="width:100%;resize:vertical"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
<div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
|
||||
display:flex;gap:var(--space-2);flex-shrink:0">
|
||||
<button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
|
||||
<button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const textarea = document.getElementById('by-note-text');
|
||||
const saveBtn = document.getElementById('by-note-save');
|
||||
const cancelBtn = document.getElementById('by-note-cancel');
|
||||
const closeBtn = document.getElementById('by-note-close');
|
||||
|
||||
let existingNoteId = null;
|
||||
|
||||
try {
|
||||
const existing = await API.notes.get(parentType, String(parentId));
|
||||
if (existing?.id) {
|
||||
existingNoteId = existing.id;
|
||||
textarea.value = existing.text || '';
|
||||
}
|
||||
} catch (_) { /* keine Notiz vorhanden — ok */ }
|
||||
|
||||
setTimeout(() => textarea.focus(), 100);
|
||||
|
||||
const _close = () => overlay.remove();
|
||||
closeBtn.addEventListener('click', _close);
|
||||
cancelBtn.addEventListener('click', _close);
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
|
||||
|
||||
document.getElementById('by-note-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const text = textarea.value.trim();
|
||||
UI.setLoading(saveBtn, true);
|
||||
try {
|
||||
const payload = { text, parent_label: parentLabel, location_name: locationName };
|
||||
if (existingNoteId) {
|
||||
await API.notes.update(existingNoteId, payload);
|
||||
} else {
|
||||
await API.notes.create(parentType, String(parentId), payload);
|
||||
}
|
||||
UI.toast.success('Notiz gespeichert.');
|
||||
_close();
|
||||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Speichern.');
|
||||
UI.setLoading(saveBtn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ window.Page_notes = (() => {
|
|||
{ type: 'walk', label: 'Gassi-Treffen',color: '#f39c12', icon: 'paw-print' },
|
||||
{ type: 'sitting', label: 'Sitting', color: '#16a085', icon: 'house-line' },
|
||||
{ type: 'erste_hilfe', label: 'Erste Hilfe', color: '#c0392b', icon: 'first-aid' },
|
||||
{ type: 'trainingsplan', label: 'Trainingsplan',color: '#059669', icon: 'clipboard-text' },
|
||||
{ type: 'friends', label: 'Freunde', color: '#7c3aed', icon: 'users' },
|
||||
{ type: 'poison', label: 'Giftköder', color: '#dc2626', icon: 'warning-octagon' },
|
||||
{ type: 'lost', label: 'Vermisste', color: '#b45309', icon: 'magnifying-glass' },
|
||||
];
|
||||
|
||||
function _rubrik(type) {
|
||||
|
|
@ -135,6 +139,15 @@ window.Page_notes = (() => {
|
|||
<span class="notes-count">${_notes.length} Notiz${_notes.length !== 1 ? 'en' : ''}</span>
|
||||
</div>
|
||||
|
||||
<!-- Datenschutz-Hinweis -->
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);background:var(--c-surface-2);
|
||||
border-radius:var(--radius-md);padding:var(--space-2) var(--space-3);
|
||||
display:flex;align-items:center;gap:var(--space-2)">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px;flex-shrink:0">
|
||||
<use href="/icons/phosphor.svg#lock-simple"></use></svg>
|
||||
Alle Notizen sind privat — nur du kannst sie lesen.
|
||||
</div>
|
||||
|
||||
<!-- KI-Panel -->
|
||||
${kiEnabled ? _kiPanelHtml() : ''}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,6 +240,13 @@ window.Page_poison = (() => {
|
|||
if (r) _openDetail(r);
|
||||
});
|
||||
});
|
||||
listEl.querySelectorAll('.poison-note-btn').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const id = parseInt(btn.dataset.poisonNoteId);
|
||||
_openNoteModal('poison', id, 'Giftköder-Meldung ' + id, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _reportCard(r) {
|
||||
|
|
@ -279,6 +286,13 @@ window.Page_poison = (() => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${_appState.user ? `<div style="margin-top:var(--space-2);text-align:right">
|
||||
<button class="btn btn-ghost btn-xs poison-note-btn"
|
||||
data-poison-note-id="${r.id}"
|
||||
title="Notiz" onclick="event.stopPropagation()">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz
|
||||
</button>
|
||||
</div>` : ''}
|
||||
${r.foto_url
|
||||
? `<img src="${r.foto_url}" alt="Foto"
|
||||
loading="lazy"
|
||||
|
|
@ -581,6 +595,87 @@ window.Page_poison = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// NOTIZ-MODAL
|
||||
// ----------------------------------------------------------
|
||||
async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
|
||||
document.getElementById('by-note-modal')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'by-note-modal';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
|
||||
width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
|
||||
padding-bottom:env(safe-area-inset-bottom,0px)">
|
||||
<div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
|
||||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${UI.escape(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
|
||||
<form id="by-note-form">
|
||||
<textarea id="by-note-text" class="form-control" rows="5"
|
||||
placeholder="Notiz eingeben…"
|
||||
style="width:100%;resize:vertical"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
<div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
|
||||
display:flex;gap:var(--space-2);flex-shrink:0">
|
||||
<button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
|
||||
<button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const textarea = document.getElementById('by-note-text');
|
||||
const saveBtn = document.getElementById('by-note-save');
|
||||
const cancelBtn = document.getElementById('by-note-cancel');
|
||||
const closeBtn = document.getElementById('by-note-close');
|
||||
|
||||
let existingNoteId = null;
|
||||
|
||||
try {
|
||||
const existing = await API.notes.get(parentType, String(parentId));
|
||||
if (existing?.id) {
|
||||
existingNoteId = existing.id;
|
||||
textarea.value = existing.text || '';
|
||||
}
|
||||
} catch (_) { /* keine Notiz vorhanden — ok */ }
|
||||
|
||||
setTimeout(() => textarea.focus(), 100);
|
||||
|
||||
const _close = () => overlay.remove();
|
||||
closeBtn.addEventListener('click', _close);
|
||||
cancelBtn.addEventListener('click', _close);
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
|
||||
|
||||
document.getElementById('by-note-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const text = textarea.value.trim();
|
||||
UI.setLoading(saveBtn, true);
|
||||
try {
|
||||
const payload = { text, parent_label: parentLabel, location_name: locationName };
|
||||
if (existingNoteId) {
|
||||
await API.notes.update(existingNoteId, payload);
|
||||
} else {
|
||||
await API.notes.create(parentType, String(parentId), payload);
|
||||
}
|
||||
UI.toast.success('Notiz gespeichert.');
|
||||
_close();
|
||||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Speichern.');
|
||||
UI.setLoading(saveBtn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -537,6 +537,16 @@ window.Page_trainingsplaene = (() => {
|
|||
// BIND EVENTS
|
||||
// ----------------------------------------------------------
|
||||
function _bindEvents() {
|
||||
// Notiz-Button
|
||||
const dogId = _dogId();
|
||||
_container.querySelector('#tp-note-btn')?.addEventListener('click', e => {
|
||||
e.stopPropagation();
|
||||
const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate'
|
||||
: _activePlan === 'junior' ? 'Junior 6–18 Monate'
|
||||
: `Erwachsener Hund – ${_activeAdultTab}`;
|
||||
_openNoteModal('trainingsplan', dogId, planLabel, null);
|
||||
});
|
||||
|
||||
// Plan selector
|
||||
_container.querySelectorAll('[data-plan]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
|
|
@ -596,11 +606,21 @@ window.Page_trainingsplaene = (() => {
|
|||
else if (_activePlan === 'junior') planContent = _renderJunior();
|
||||
else planContent = _renderErwachsen();
|
||||
|
||||
const dogId = _dogId();
|
||||
const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate'
|
||||
: _activePlan === 'junior' ? 'Junior 6–18 Monate'
|
||||
: `Erwachsener Hund – ${_activeAdultTab}`;
|
||||
|
||||
_container.innerHTML = `
|
||||
<div style="padding-bottom:var(--space-8)">
|
||||
<h2 style="font-size:var(--text-lg);font-weight:700;margin:var(--space-4) 0 var(--space-4)">
|
||||
${_icon('clipboard-text')} Trainingspläne
|
||||
</h2>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin:var(--space-4) 0 var(--space-4)">
|
||||
<h2 style="font-size:var(--text-lg);font-weight:700;margin:0">
|
||||
${_icon('clipboard-text')} Trainingspläne
|
||||
</h2>
|
||||
${dogId ? `<button class="btn btn-ghost btn-xs" id="tp-note-btn" title="Notiz zum Plan">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz
|
||||
</button>` : ''}
|
||||
</div>
|
||||
${_renderPlanSelector()}
|
||||
${planContent}
|
||||
<!-- Trainingskalender -->
|
||||
|
|
@ -750,6 +770,88 @@ window.Page_trainingsplaene = (() => {
|
|||
`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// NOTIZ-MODAL
|
||||
// ----------------------------------------------------------
|
||||
async function _openNoteModal(parentType, parentId, parentLabel, locationName) {
|
||||
// Vorhandenes Modal entfernen falls noch offen
|
||||
document.getElementById('by-note-modal')?.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'by-note-modal';
|
||||
overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center';
|
||||
|
||||
overlay.innerHTML = `
|
||||
<div style="background:var(--c-surface);border-radius:var(--radius-xl) var(--radius-xl) 0 0;
|
||||
width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;
|
||||
padding-bottom:env(safe-area-inset-bottom,0px)">
|
||||
<div style="padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--c-border);
|
||||
display:flex;align-items:center;justify-content:space-between;flex-shrink:0">
|
||||
<div>
|
||||
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base)"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz</div>
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(parentLabel)}</div>
|
||||
</div>
|
||||
<button id="by-note-close" style="background:none;border:none;cursor:pointer;font-size:1.4rem;color:var(--c-text-muted);padding:4px 8px" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div style="padding:var(--space-4) var(--space-5);flex:1;overflow-y:auto">
|
||||
<form id="by-note-form">
|
||||
<textarea id="by-note-text" class="form-control" rows="5"
|
||||
placeholder="Notiz eingeben…"
|
||||
style="width:100%;resize:vertical"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
<div style="padding:var(--space-3) var(--space-5);border-top:1px solid var(--c-border);
|
||||
display:flex;gap:var(--space-2);flex-shrink:0">
|
||||
<button type="button" id="by-note-cancel" class="btn btn-secondary flex-1">Abbrechen</button>
|
||||
<button type="submit" form="by-note-form" id="by-note-save" class="btn btn-primary flex-1">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const textarea = document.getElementById('by-note-text');
|
||||
const saveBtn = document.getElementById('by-note-save');
|
||||
const cancelBtn = document.getElementById('by-note-cancel');
|
||||
const closeBtn = document.getElementById('by-note-close');
|
||||
|
||||
let existingNoteId = null;
|
||||
|
||||
try {
|
||||
const existing = await API.notes.get(parentType, String(parentId));
|
||||
if (existing?.id) {
|
||||
existingNoteId = existing.id;
|
||||
textarea.value = existing.text || '';
|
||||
}
|
||||
} catch (_) { /* keine Notiz vorhanden — ok */ }
|
||||
|
||||
setTimeout(() => textarea.focus(), 100);
|
||||
|
||||
const _close = () => overlay.remove();
|
||||
closeBtn.addEventListener('click', _close);
|
||||
cancelBtn.addEventListener('click', _close);
|
||||
overlay.addEventListener('click', e => { if (e.target === overlay) _close(); });
|
||||
|
||||
document.getElementById('by-note-form').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const text = textarea.value.trim();
|
||||
UI.setLoading(saveBtn, true);
|
||||
try {
|
||||
const payload = { text, parent_label: parentLabel, location_name: locationName };
|
||||
if (existingNoteId) {
|
||||
await API.notes.update(existingNoteId, payload);
|
||||
} else {
|
||||
await API.notes.create(parentType, String(parentId), payload);
|
||||
}
|
||||
UI.toast.success('Notiz gespeichert.');
|
||||
_close();
|
||||
} catch (err) {
|
||||
UI.toast.error(err.message || 'Fehler beim Speichern.');
|
||||
UI.setLoading(saveBtn, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC API
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v424';
|
||||
const CACHE_VERSION = 'by-v425';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue