248 lines
9.5 KiB
JavaScript
248 lines
9.5 KiB
JavaScript
const NotificationPanel = ({
|
||
error,
|
||
message,
|
||
settings,
|
||
capabilities,
|
||
loading,
|
||
dirty,
|
||
saving,
|
||
onReset,
|
||
onSave,
|
||
onFieldChange,
|
||
onSendTest,
|
||
onCopyLink,
|
||
copyFeedback,
|
||
ntfyPreviewUrl,
|
||
location,
|
||
locationLoading,
|
||
locationSaving,
|
||
locationError,
|
||
onDetectLocation,
|
||
onClearLocation
|
||
}) => {
|
||
return (
|
||
<div className="mb-6 border border-gray-200 rounded-lg p-4 bg-gray-50">
|
||
{error && (
|
||
<div className="mb-4 bg-red-100 border border-red-300 text-red-700 px-4 py-2 rounded">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
{message && (
|
||
<div className="mb-4 bg-green-100 border border-green-300 text-green-700 px-4 py-2 rounded">
|
||
{message}
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid md:grid-cols-2 gap-4">
|
||
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-gray-800">ntfy</h3>
|
||
<p className="text-xs text-gray-500">
|
||
Push aufs Handy über die ntfy-App oder Browser (Themenkanal notwendig).
|
||
</p>
|
||
</div>
|
||
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
|
||
<input
|
||
type="checkbox"
|
||
checked={settings.ntfy.enabled}
|
||
onChange={(event) => onFieldChange('ntfy', 'enabled', event.target.checked)}
|
||
disabled={!capabilities.ntfy.enabled}
|
||
className="h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
|
||
/>
|
||
<span>Aktiv</span>
|
||
</label>
|
||
</div>
|
||
|
||
{!capabilities.ntfy.enabled ? (
|
||
<p className="text-sm text-gray-500">
|
||
Diese Option wurde vom Admin deaktiviert. Bitte frage nach, wenn du ntfy nutzen möchtest.
|
||
</p>
|
||
) : (
|
||
<div className="space-y-3">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-gray-600 mb-1">Topic / Kanal</label>
|
||
<input
|
||
type="text"
|
||
value={settings.ntfy.topic}
|
||
onChange={(event) => onFieldChange('ntfy', 'topic', event.target.value)}
|
||
placeholder="z. B. mein-pickup-topic"
|
||
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
disabled={!settings.ntfy.enabled}
|
||
/>
|
||
</div>
|
||
{ntfyPreviewUrl && (
|
||
<div className="flex items-center gap-2 text-xs text-gray-600">
|
||
<a
|
||
href={ntfyPreviewUrl}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-blue-600 hover:text-blue-800 break-all"
|
||
>
|
||
{ntfyPreviewUrl}
|
||
</a>
|
||
<button
|
||
type="button"
|
||
onClick={() => onCopyLink(ntfyPreviewUrl)}
|
||
className="border border-gray-300 rounded px-2 py-0.5 text-gray-600 hover:text-blue-700 hover:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-400"
|
||
title="In Zwischenablage kopieren"
|
||
>
|
||
Kopieren
|
||
</button>
|
||
</div>
|
||
)}
|
||
{copyFeedback && <p className="text-xs text-green-600">{copyFeedback}</p>}
|
||
<button
|
||
type="button"
|
||
onClick={() => onSendTest('ntfy')}
|
||
disabled={!settings.ntfy.enabled || saving}
|
||
className="text-sm text-blue-600 hover:text-blue-800"
|
||
>
|
||
Test senden
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
|
||
<div className="flex items-center justify-between mb-3">
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-gray-800">Telegram</h3>
|
||
<p className="text-xs text-gray-500">
|
||
Nutze den Bot des Admins, um Nachrichten direkt in Telegram zu erhalten.
|
||
</p>
|
||
</div>
|
||
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
|
||
<input
|
||
type="checkbox"
|
||
checked={settings.telegram.enabled}
|
||
onChange={(event) => onFieldChange('telegram', 'enabled', event.target.checked)}
|
||
disabled={!capabilities.telegram.enabled}
|
||
className="h-4 w-4 text-blue-600 rounded focus:ring-blue-500"
|
||
/>
|
||
<span>Aktiv</span>
|
||
</label>
|
||
</div>
|
||
|
||
{!capabilities.telegram.enabled ? (
|
||
<p className="text-sm text-gray-500">
|
||
Telegram-Benachrichtigungen sind derzeit deaktiviert oder der Bot ist nicht konfiguriert.
|
||
</p>
|
||
) : (
|
||
<div className="space-y-3">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-gray-600 mb-1">Chat-ID</label>
|
||
<input
|
||
type="text"
|
||
value={settings.telegram.chatId}
|
||
onChange={(event) => onFieldChange('telegram', 'chatId', event.target.value)}
|
||
placeholder="z. B. 123456789"
|
||
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||
disabled={!settings.telegram.enabled}
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
Tipp: Schreibe dem Telegram-Bot und nutze{' '}
|
||
<a
|
||
href="https://t.me/userinfobot"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="text-blue-600 hover:text-blue-800"
|
||
>
|
||
@userinfobot
|
||
</a>{' '}
|
||
oder ein Chat-ID-Tool.
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => onSendTest('telegram')}
|
||
disabled={!settings.telegram.enabled || saving}
|
||
className="text-sm text-blue-600 hover:text-blue-800"
|
||
>
|
||
Test senden
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-6 border border-blue-100 bg-blue-50 rounded-lg p-4">
|
||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||
<div>
|
||
<h3 className="text-lg font-semibold text-blue-900">Standort für Entfernungssortierung</h3>
|
||
<p className="text-sm text-blue-800">
|
||
Der Standort wird für die Entfernungskalkulation im Monitoring genutzt. Nur für dich sichtbar.
|
||
</p>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => onDetectLocation?.()}
|
||
className="px-4 py-2 rounded-md bg-blue-600 text-white text-sm disabled:opacity-60"
|
||
disabled={!onDetectLocation || locationLoading || locationSaving}
|
||
>
|
||
{locationSaving ? 'Speichere...' : 'Standort ermitteln'}
|
||
</button>
|
||
{location && (
|
||
<button
|
||
type="button"
|
||
onClick={() => onClearLocation?.()}
|
||
className="px-4 py-2 rounded-md border border-blue-300 text-blue-700 text-sm bg-white disabled:opacity-60"
|
||
disabled={!onClearLocation || locationSaving}
|
||
>
|
||
Entfernen
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="mt-3 text-sm text-blue-900">
|
||
{locationLoading ? (
|
||
<p>Standort wird geladen...</p>
|
||
) : location ? (
|
||
<div>
|
||
<p>
|
||
Aktueller Standort: {location.lat.toFixed(4)}, {location.lon.toFixed(4)}
|
||
{location.label && (
|
||
<span className="ml-1 font-semibold text-blue-900">– {location.label}</span>
|
||
)}{' '}
|
||
(
|
||
{location.updatedAt ? new Date(location.updatedAt).toLocaleString('de-DE') : 'Zeit unbekannt'})
|
||
</p>
|
||
{location.labelDistanceKm !== undefined && (
|
||
<p className="text-xs text-blue-800 mt-1">
|
||
Nächster bekannte Betrieb ca. {location.labelDistanceKm.toFixed(1)} km entfernt.
|
||
</p>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<p>Kein Standort hinterlegt.</p>
|
||
)}
|
||
</div>
|
||
{locationError && <p className="mt-2 text-sm text-red-600">{locationError}</p>}
|
||
</div>
|
||
|
||
<div className="flex items-center justify-end mt-4 gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={onReset}
|
||
className="text-sm text-gray-600 hover:text-gray-900"
|
||
disabled={loading}
|
||
>
|
||
Zurücksetzen
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={onSave}
|
||
disabled={!dirty || saving}
|
||
className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors disabled:opacity-60"
|
||
>
|
||
{saving ? 'Speichere...' : 'Benachrichtigungen speichern'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default NotificationPanel;
|