Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration
- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push - Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push - Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge, forum, wiki, walks) vollständig auf Phosphor-Icons migriert - Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos) - TheDogAPI lokal gespiegelt (169 Rassen + Fotos) - Quiz-Result-Cards horizontal (korrekte Bildproportionen) - SW by-v89
This commit is contained in:
parent
96bd57f0ad
commit
097295c628
44 changed files with 9980 additions and 300 deletions
|
|
@ -18,12 +18,12 @@ window.Page_places = (() => {
|
|||
// Typen-Konfiguration
|
||||
// ----------------------------------------------------------
|
||||
const TYPEN = {
|
||||
restaurant: { icon: '🍽️', label: 'Restaurant & Café', color: '#F97316' },
|
||||
freilauf: { icon: '🐕', label: 'Freilauffläche', color: '#22C55E' },
|
||||
shop: { icon: '🛒', label: 'Shop', color: '#3B82F6' },
|
||||
kotbeutel: { icon: '🧻', label: 'Kotbeutel-Station', color: '#6B7280' },
|
||||
tierarzt: { icon: '🩺', label: 'Tierarzt', color: '#EF4444' },
|
||||
hundeschule: { icon: '🎓', label: 'Hundeschule', color: '#8B5CF6' },
|
||||
restaurant: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#fork-knife"></use></svg>', label: 'Restaurant & Café', color: '#F97316' },
|
||||
freilauf: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>', label: 'Freilauffläche', color: '#22C55E' },
|
||||
shop: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#shopping-cart"></use></svg>', label: 'Shop', color: '#3B82F6' },
|
||||
kotbeutel: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>', label: 'Kotbeutel-Station', color: '#6B7280' },
|
||||
tierarzt: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>', label: 'Tierarzt', color: '#EF4444' },
|
||||
hundeschule: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#graduation-cap"></use></svg>', label: 'Hundeschule', color: '#8B5CF6' },
|
||||
};
|
||||
|
||||
function _esc(s) {
|
||||
|
|
@ -54,13 +54,13 @@ window.Page_places = (() => {
|
|||
<!-- Toolbar -->
|
||||
<div class="places-toolbar">
|
||||
<div class="places-filter" id="places-filter">
|
||||
<button class="places-filter-btn active" data-typ="">🗺️ Alle</button>
|
||||
<button class="places-filter-btn active" data-typ=""><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-trifold"></use></svg> Alle</button>
|
||||
${Object.entries(TYPEN).map(([k, t]) =>
|
||||
`<button class="places-filter-btn" data-typ="${k}">${t.icon} ${t.label}</button>`
|
||||
).join('')}
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm" id="places-add-btn" style="white-space:nowrap">
|
||||
+ Ort hinzufügen
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#plus"></use></svg> Ort hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ window.Page_places = (() => {
|
|||
L.Control.Locate = L.Control.extend({
|
||||
onAdd() {
|
||||
const btn = L.DomUtil.create('button', 'places-locate-btn');
|
||||
btn.innerHTML = '📍';
|
||||
btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>';
|
||||
btn.title = 'Meinen Standort';
|
||||
btn.onclick = async () => {
|
||||
try {
|
||||
|
|
@ -191,7 +191,7 @@ window.Page_places = (() => {
|
|||
_markers = [];
|
||||
|
||||
_filtered().forEach(place => {
|
||||
const t = TYPEN[place.typ] || { icon: '📍', color: '#6B7280' };
|
||||
const t = TYPEN[place.typ] || { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>', color: '#6B7280' };
|
||||
const icon = L.divIcon({
|
||||
className: '',
|
||||
html: `<div style="
|
||||
|
|
@ -242,11 +242,11 @@ window.Page_places = (() => {
|
|||
}
|
||||
|
||||
function _cardHTML(p) {
|
||||
const t = TYPEN[p.typ] || { icon: '📍', label: p.typ, color: '#6B7280' };
|
||||
const t = TYPEN[p.typ] || { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>', label: p.typ, color: '#6B7280' };
|
||||
const flags = [
|
||||
p.hund_rein === true ? '🐕 Hund rein' : null,
|
||||
p.leine_pflicht === true ? '🔗 Leinenpflicht' : null,
|
||||
p.wasser_fuer_hunde === true ? '💧 Wasser' : null,
|
||||
p.hund_rein === true ? `${UI.icon('dog')} Hund rein` : null,
|
||||
p.leine_pflicht === true ? `${UI.icon('tag')} Leinenpflicht` : null,
|
||||
p.wasser_fuer_hunde === true ? `${UI.icon('drop')} Wasser` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
return `
|
||||
|
|
@ -260,7 +260,7 @@ window.Page_places = (() => {
|
|||
</div>
|
||||
${flags.length ? `<div class="places-card-flags">${flags.map(f => `<span class="places-flag">${f}</span>`).join('')}</div>` : ''}
|
||||
</div>
|
||||
<div class="places-card-arrow">›</div>
|
||||
<div class="places-card-arrow">${UI.icon('arrow-right')}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
|
@ -268,13 +268,13 @@ window.Page_places = (() => {
|
|||
// Detail-Modal
|
||||
// ----------------------------------------------------------
|
||||
function _openDetail(place) {
|
||||
const t = TYPEN[place.typ] || { icon: '📍', label: place.typ, color: '#6B7280' };
|
||||
const t = TYPEN[place.typ] || { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>', label: place.typ, color: '#6B7280' };
|
||||
const isOwn = _appState.user?.id === place.user_id;
|
||||
|
||||
const flags = [
|
||||
place.hund_rein === true ? '🐕 Hund erlaubt' : (place.hund_rein === false ? '🚫 Kein Hund' : null),
|
||||
place.leine_pflicht === true ? '🔗 Leinenpflicht' : (place.leine_pflicht === false ? '✅ Leine optional' : null),
|
||||
place.wasser_fuer_hunde === true ? '💧 Wasser vorhanden': null,
|
||||
place.hund_rein === true ? `${UI.icon('dog')} Hund erlaubt` : (place.hund_rein === false ? `${UI.icon('x')} Kein Hund` : null),
|
||||
place.leine_pflicht === true ? `${UI.icon('tag')} Leinenpflicht` : (place.leine_pflicht === false ? `${UI.icon('check')} Leine optional` : null),
|
||||
place.wasser_fuer_hunde === true ? `${UI.icon('drop')} Wasser vorhanden`: null,
|
||||
].filter(Boolean);
|
||||
|
||||
const body = `
|
||||
|
|
@ -285,8 +285,8 @@ window.Page_places = (() => {
|
|||
<div style="color:${t.color};font-size:0.9rem">${t.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
${place.adresse ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-2)">📍 ${_esc(place.adresse)}</p>` : ''}
|
||||
${place.website ? `<p style="margin-bottom:var(--space-2)"><a href="${_esc(place.website)}" target="_blank" style="color:var(--c-primary)">🌐 ${_esc(place.website)}</a></p>` : ''}
|
||||
${place.adresse ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-2)">${UI.icon('map-pin')} ${_esc(place.adresse)}</p>` : ''}
|
||||
${place.website ? `<p style="margin-bottom:var(--space-2)"><a href="${_esc(place.website)}" target="_blank" style="color:var(--c-primary)">${UI.icon('arrow-square-out')} ${_esc(place.website)}</a></p>` : ''}
|
||||
${flags.length ? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-3)">${flags.map(f => `<span class="places-flag places-flag--detail">${f}</span>`).join('')}</div>` : ''}
|
||||
<p style="color:var(--c-text-muted);font-size:0.8rem;margin-top:var(--space-4)">
|
||||
Eingetragen von ${_esc(place.user_name || 'Unbekannt')}
|
||||
|
|
@ -301,7 +301,7 @@ window.Page_places = (() => {
|
|||
<button type="button" class="btn btn-primary flex-1" id="place-detail-close">Schließen</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: `${t.icon} ${place.name}`, body, footer });
|
||||
UI.modal.open({ title: `${t.icon} ${_esc(place.name)}`, body, footer });
|
||||
|
||||
document.getElementById('place-detail-close')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
|
|
@ -341,7 +341,7 @@ window.Page_places = (() => {
|
|||
const isEdit = !!place;
|
||||
|
||||
const typOpts = Object.entries(TYPEN)
|
||||
.map(([k, t]) => `<option value="${k}" ${place?.typ === k ? 'selected' : ''}>${t.icon} ${t.label}</option>`)
|
||||
.map(([k, t]) => `<option value="${k}" ${place?.typ === k ? 'selected' : ''}>${t.label}</option>`)
|
||||
.join('');
|
||||
|
||||
const body = `
|
||||
|
|
@ -367,12 +367,12 @@ window.Page_places = (() => {
|
|||
<input class="form-control" type="text" id="pf-lon-disp"
|
||||
placeholder="Länge" readonly style="flex:1"
|
||||
value="${place ? place.lon.toFixed(6) : ''}">
|
||||
<button type="button" class="btn btn-secondary" id="pf-gps-btn" title="GPS">📍</button>
|
||||
<button type="button" class="btn btn-secondary" id="pf-gps-btn" title="GPS"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg></button>
|
||||
</div>
|
||||
<input type="hidden" name="lat" id="pf-lat" value="${place?.lat || ''}">
|
||||
<input type="hidden" name="lon" id="pf-lon" value="${place?.lon || ''}">
|
||||
<small id="pf-gps-hint" style="color:var(--c-text-secondary)">
|
||||
${place ? '✅ Position gespeichert' : 'GPS-Button drücken oder Standort ermitteln'}
|
||||
${place ? 'Position gespeichert' : 'GPS-Button drücken oder Standort ermitteln'}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
|
@ -392,15 +392,15 @@ window.Page_places = (() => {
|
|||
<label class="form-label">Hundefreundlichkeit</label>
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="hund_rein" ${place?.hund_rein ? 'checked' : ''}>
|
||||
🐕 Hund darf rein
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg> Hund darf rein
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="leine_pflicht" ${place?.leine_pflicht ? 'checked' : ''}>
|
||||
🔗 Leinenpflicht beachten
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tag"></use></svg> Leinenpflicht beachten
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="wasser_fuer_hunde" ${place?.wasser_fuer_hunde ? 'checked' : ''}>
|
||||
💧 Wasser für Hunde vorhanden
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#drop"></use></svg> Wasser für Hunde vorhanden
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -414,7 +414,7 @@ window.Page_places = (() => {
|
|||
</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: isEdit ? `${place.name} bearbeiten` : '📍 Neuer Ort', body, footer });
|
||||
UI.modal.open({ title: isEdit ? `${_esc(place.name)} bearbeiten` : '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> Neuer Ort', body, footer });
|
||||
|
||||
document.getElementById('place-form-cancel')?.addEventListener('click', UI.modal.close);
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ window.Page_places = (() => {
|
|||
document.getElementById('pf-lon').value = pos.lon;
|
||||
document.getElementById('pf-lat-disp').value = pos.lat.toFixed(6);
|
||||
document.getElementById('pf-lon-disp').value = pos.lon.toFixed(6);
|
||||
document.getElementById('pf-gps-hint').textContent = '✅ Standort ermittelt';
|
||||
document.getElementById('pf-gps-hint').textContent = 'Standort ermittelt';
|
||||
} catch {
|
||||
UI.toast.error('GPS nicht verfügbar.');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue