Link am Betrieb hinzugefügt

This commit is contained in:
2025-11-10 19:05:15 +01:00
parent 1bb1066de4
commit 449f7e2daa

View File

@@ -1,5 +1,40 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
const SettingField = ({ label, description, children }) => {
const [showHelp, setShowHelp] = useState(false);
return (
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<label className="block text-sm font-medium text-gray-700">{label}</label>
{description && (
<button
type="button"
onClick={() => setShowHelp((prev) => !prev)}
className="text-xs text-purple-600 hover:text-purple-900 focus:outline-none"
>
{showHelp ? 'Hilfe ausblenden' : 'Hilfe anzeigen'}
</button>
)}
</div>
{children}
{showHelp && description && (
<p className="text-xs text-gray-600 bg-white border border-purple-100 rounded p-2">{description}</p>
)}
</div>
);
};
const SettingSection = ({ title, subtitle, children }) => (
<section className="mb-6">
<div className="mb-3">
<h2 className="text-xl font-semibold text-purple-900">{title}</h2>
{subtitle && <p className="text-sm text-gray-600">{subtitle}</p>}
</div>
<div className="space-y-4 bg-white border border-purple-100 rounded-lg p-4 shadow-sm">{children}</div>
</section>
);
const AdminSettingsPanel = ({
adminSettings,
adminSettingsLoading,
@@ -18,7 +53,7 @@ const AdminSettingsPanel = ({
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4">
<div>
<h1 className="text-2xl font-bold text-purple-900">Admin-Einstellungen</h1>
<p className="text-sm text-gray-600">Globale Abläufe und Verzögerungen für die Abhol-Automation verwalten.</p>
<p className="text-sm text-gray-600">Globale Abläufe und Benachrichtigungen feinjustieren.</p>
</div>
<Link
to="/"
@@ -45,24 +80,33 @@ const AdminSettingsPanel = ({
<div className="border border-purple-200 rounded-lg p-4 bg-purple-50">
{adminSettingsLoading && <p className="text-sm text-purple-700">Lade Admin-Einstellungen...</p>}
{!adminSettingsLoading && !adminSettings && <p className="text-sm text-purple-700">Keine Admin-Einstellungen verfügbar.</p>}
{!adminSettingsLoading && !adminSettings && (
<p className="text-sm text-purple-700">Keine Admin-Einstellungen verfügbar.</p>
)}
{adminSettings && (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Cron-Ausdruck</label>
<SettingSection
title="Scheduler für Slot-Suche"
subtitle="Bestimmt, wann der Bot freie Slots sucht und wie stark er Anfragen verteilt."
>
<SettingField
label="Cron-Ausdruck"
description="Legt fest, zu welchen Zeiten die reine Slot-Suche läuft. Nutzt die klassische Cron-Syntax (Serverzeit)."
>
<input
type="text"
value={adminSettings.scheduleCron}
onChange={(event) => onSettingChange('scheduleCron', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="z. B. 0 * * * *"
placeholder="z. B. */10 7-22 * * *"
/>
</div>
</SettingField>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Initiale Verzögerung (Sek.)</label>
<SettingField
label="Initiale Verzögerung (Sek.)"
description="Zufällige Wartezeit beim Start einer Session. Hilft, die Last des ersten Laufs auf mehrere Sekunden zu verteilen."
>
<div className="grid grid-cols-2 gap-2">
<input
type="number"
@@ -81,10 +125,12 @@ const AdminSettingsPanel = ({
placeholder="Max"
/>
</div>
</div>
</SettingField>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Prüfverzögerung (Sek.)</label>
<SettingField
label="Verzögerung zwischen Jobs (Sek.)"
description="Pause zwischen Cron-Trigger und tatsächlicher Anfrage. Größere Intervalle = mehr Streuung zwischen Sessions."
>
<div className="grid grid-cols-2 gap-2">
<input
type="number"
@@ -103,10 +149,12 @@ const AdminSettingsPanel = ({
placeholder="Max"
/>
</div>
</div>
</SettingField>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Verzögerung Store-Prüfung (ms)</label>
<SettingField
label="Verzögerung zwischen Store-Abfragen (ms)"
description="Drosselt den Sync der Foodsharing-Stores. Erhöhe den Wert bei Rate-Limit-Fehlern oder wenn der Server langsam reagiert."
>
<input
type="number"
min="0"
@@ -115,12 +163,17 @@ const AdminSettingsPanel = ({
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="z. B. 3000"
/>
</div>
</div>
</SettingField>
</SettingSection>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Store-Watch Cron</label>
<SettingSection
title="Store-Watch Monitoring"
subtitle="Steuert die Team-Status-Abfragen für geschlossene Betriebe (teamSearchStatus)."
>
<SettingField
label="Store-Watch Cron"
description="Cron-Ausdruck, wann Team-Status-Prüfungen laufen. Bei sehr vielen Stores Werte etwas streuen (z. B. alle 3060 Minuten)."
>
<input
type="text"
value={adminSettings.storeWatchCron || ''}
@@ -128,12 +181,12 @@ const AdminSettingsPanel = ({
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="z. B. */30 * * * *"
/>
<p className="text-xs text-gray-500 mt-1">
Legt fest, wie häufig der Team-Status der überwachten Betriebe geprüft wird.
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Store-Watch Startverzögerung (Sek.)</label>
</SettingField>
<SettingField
label="Store-Watch Startverzögerung (Sek.)"
description="Randomisierte Wartezeit, bevor nach dem Cron-Signal die Statusprüfungen beginnen. Verhindert Traffic-Spitzen."
>
<div className="grid grid-cols-2 gap-2">
<input
type="number"
@@ -156,28 +209,21 @@ const AdminSettingsPanel = ({
placeholder="Max"
/>
</div>
<p className="text-xs text-gray-500 mt-1">
Wird genutzt, um die ersten Prüfungen leicht zu verteilen.
</p>
</div>
</div>
</SettingField>
</SettingSection>
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h2 className="text-lg font-semibold text-purple-900">Ignorierte Slots</h2>
<button
type="button"
onClick={onAddIgnoredSlot}
className="text-sm text-purple-700 hover:text-purple-900 flex items-center gap-1"
>
+ Slot hinzufügen
</button>
</div>
<SettingSection
title="Ignorierte Slots"
subtitle="Diese Einträge überspringt der Scheduler dauerhaft (z. B. TVS-Zeiten oder Test-Slots)."
>
{(!adminSettings.ignoredSlots || adminSettings.ignoredSlots.length === 0) && (
<p className="text-sm text-gray-500">Keine Regeln definiert.</p>
)}
{adminSettings.ignoredSlots?.map((slot, index) => (
<div key={`${index}-${slot.storeId}`} className="grid grid-cols-1 md:grid-cols-5 gap-2 mb-2 items-center">
<div
key={`${index}-${slot.storeId}`}
className="grid grid-cols-1 md:grid-cols-5 gap-2 items-center border border-purple-100 rounded p-3"
>
<input
type="text"
value={slot.storeId}
@@ -201,82 +247,96 @@ const AdminSettingsPanel = ({
</button>
</div>
))}
</div>
<button
type="button"
onClick={onAddIgnoredSlot}
className="text-sm text-purple-700 hover:text-purple-900 flex items-center gap-1"
>
+ Slot hinzufügen
</button>
</SettingSection>
<div className="grid grid-cols-1 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">Konfiguration für Systembenachrichtigungen via ntfy.</p>
<SettingSection
title="System-Benachrichtigungen"
subtitle="Aktiviere hier die Kanäle, über die Nutzer später personalisierte Benachrichtigungen empfangen können."
>
<div className="grid grid-cols-1 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-Nachrichten via ntfy-Server.</p>
</div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<input
type="checkbox"
checked={adminSettings.notifications?.ntfy?.enabled || false}
onChange={(event) => onNotificationChange('ntfy', 'enabled', event.target.checked)}
className="h-4 w-4 text-purple-600 rounded focus:ring-purple-500"
/>
<span>Aktiv</span>
</label>
</div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<div className="space-y-3">
<input
type="checkbox"
checked={adminSettings.notifications?.ntfy?.enabled || false}
onChange={(event) => onNotificationChange('ntfy', 'enabled', event.target.checked)}
className="h-4 w-4 text-purple-600 rounded focus:ring-purple-500"
type="text"
value={adminSettings.notifications?.ntfy?.serverUrl || ''}
onChange={(event) => onNotificationChange('ntfy', 'serverUrl', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Server-URL"
/>
<span>Aktiv</span>
</label>
<input
type="text"
value={adminSettings.notifications?.ntfy?.topicPrefix || ''}
onChange={(event) => onNotificationChange('ntfy', 'topicPrefix', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Themenpräfix"
/>
<input
type="text"
value={adminSettings.notifications?.ntfy?.username || ''}
onChange={(event) => onNotificationChange('ntfy', 'username', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Benutzername (optional)"
/>
<input
type="password"
value={adminSettings.notifications?.ntfy?.password || ''}
onChange={(event) => onNotificationChange('ntfy', 'password', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Passwort (optional)"
/>
</div>
</div>
<div className="space-y-3">
<input
type="text"
value={adminSettings.notifications?.ntfy?.serverUrl || ''}
onChange={(event) => onNotificationChange('ntfy', 'serverUrl', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Server-URL"
/>
<input
type="text"
value={adminSettings.notifications?.ntfy?.topicPrefix || ''}
onChange={(event) => onNotificationChange('ntfy', 'topicPrefix', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Themenpräfix"
/>
<input
type="text"
value={adminSettings.notifications?.ntfy?.username || ''}
onChange={(event) => onNotificationChange('ntfy', 'username', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Benutzername"
/>
<input
type="password"
value={adminSettings.notifications?.ntfy?.password || ''}
onChange={(event) => onNotificationChange('ntfy', 'password', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Passwort"
/>
<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">Bot-Einstellungen für Telegram-Nachrichten.</p>
</div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<input
type="checkbox"
checked={adminSettings.notifications?.telegram?.enabled || false}
onChange={(event) => onNotificationChange('telegram', 'enabled', event.target.checked)}
className="h-4 w-4 text-purple-600 rounded focus:ring-purple-500"
/>
<span>Aktiv</span>
</label>
</div>
<div className="space-y-3">
<input
type="text"
value={adminSettings.notifications?.telegram?.botToken || ''}
onChange={(event) => onNotificationChange('telegram', 'botToken', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Bot-Token"
/>
</div>
</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">Bot-Konfiguration für Telegram-Benachrichtigungen.</p>
</div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<input
type="checkbox"
checked={adminSettings.notifications?.telegram?.enabled || false}
onChange={(event) => onNotificationChange('telegram', 'enabled', event.target.checked)}
className="h-4 w-4 text-purple-600 rounded focus:ring-purple-500"
/>
<span>Aktiv</span>
</label>
</div>
<input
type="text"
value={adminSettings.notifications?.telegram?.botToken || ''}
onChange={(event) => onNotificationChange('telegram', 'botToken', event.target.value)}
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Bot-Token"
/>
</div>
</div>
</SettingSection>
<div className="flex justify-end mt-4">
<button