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'; 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 = ({ const AdminSettingsPanel = ({
adminSettings, adminSettings,
adminSettingsLoading, 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 className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4">
<div> <div>
<h1 className="text-2xl font-bold text-purple-900">Admin-Einstellungen</h1> <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> </div>
<Link <Link
to="/" to="/"
@@ -45,24 +80,33 @@ const AdminSettingsPanel = ({
<div className="border border-purple-200 rounded-lg p-4 bg-purple-50"> <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 && <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 && ( {adminSettings && (
<> <>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <SettingSection
<div> title="Scheduler für Slot-Suche"
<label className="block text-sm font-medium text-gray-700 mb-1">Cron-Ausdruck</label> 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 <input
type="text" type="text"
value={adminSettings.scheduleCron} value={adminSettings.scheduleCron}
onChange={(event) => onSettingChange('scheduleCron', event.target.value)} 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" 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> <SettingField
<label className="block text-sm font-medium text-gray-700 mb-1">Initiale Verzögerung (Sek.)</label> 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"> <div className="grid grid-cols-2 gap-2">
<input <input
type="number" type="number"
@@ -81,10 +125,12 @@ const AdminSettingsPanel = ({
placeholder="Max" placeholder="Max"
/> />
</div> </div>
</div> </SettingField>
<div> <SettingField
<label className="block text-sm font-medium text-gray-700 mb-1">Prüfverzögerung (Sek.)</label> 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"> <div className="grid grid-cols-2 gap-2">
<input <input
type="number" type="number"
@@ -103,10 +149,12 @@ const AdminSettingsPanel = ({
placeholder="Max" placeholder="Max"
/> />
</div> </div>
</div> </SettingField>
<div> <SettingField
<label className="block text-sm font-medium text-gray-700 mb-1">Verzögerung Store-Prüfung (ms)</label> 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 <input
type="number" type="number"
min="0" 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" 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" placeholder="z. B. 3000"
/> />
</div> </SettingField>
</div> </SettingSection>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <SettingSection
<div> title="Store-Watch Monitoring"
<label className="block text-sm font-medium text-gray-700 mb-1">Store-Watch Cron</label> 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 <input
type="text" type="text"
value={adminSettings.storeWatchCron || ''} 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" 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 * * * *" placeholder="z. B. */30 * * * *"
/> />
<p className="text-xs text-gray-500 mt-1"> </SettingField>
Legt fest, wie häufig der Team-Status der überwachten Betriebe geprüft wird.
</p> <SettingField
</div> label="Store-Watch Startverzögerung (Sek.)"
<div> description="Randomisierte Wartezeit, bevor nach dem Cron-Signal die Statusprüfungen beginnen. Verhindert Traffic-Spitzen."
<label className="block text-sm font-medium text-gray-700 mb-1">Store-Watch Startverzögerung (Sek.)</label> >
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<input <input
type="number" type="number"
@@ -156,28 +209,21 @@ const AdminSettingsPanel = ({
placeholder="Max" placeholder="Max"
/> />
</div> </div>
<p className="text-xs text-gray-500 mt-1"> </SettingField>
Wird genutzt, um die ersten Prüfungen leicht zu verteilen. </SettingSection>
</p>
</div>
</div>
<div className="mb-6"> <SettingSection
<div className="flex items-center justify-between mb-2"> title="Ignorierte Slots"
<h2 className="text-lg font-semibold text-purple-900">Ignorierte Slots</h2> subtitle="Diese Einträge überspringt der Scheduler dauerhaft (z. B. TVS-Zeiten oder Test-Slots)."
<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>
{(!adminSettings.ignoredSlots || adminSettings.ignoredSlots.length === 0) && ( {(!adminSettings.ignoredSlots || adminSettings.ignoredSlots.length === 0) && (
<p className="text-sm text-gray-500">Keine Regeln definiert.</p> <p className="text-sm text-gray-500">Keine Regeln definiert.</p>
)} )}
{adminSettings.ignoredSlots?.map((slot, index) => ( {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 <input
type="text" type="text"
value={slot.storeId} value={slot.storeId}
@@ -201,14 +247,25 @@ const AdminSettingsPanel = ({
</button> </button>
</div> </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>
<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="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="bg-white border border-gray-200 rounded-lg p-4 shadow-sm">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div> <div>
<h3 className="text-lg font-semibold text-gray-800">ntfy</h3> <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> <p className="text-xs text-gray-500">Push-Nachrichten via ntfy-Server.</p>
</div> </div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700"> <label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<input <input
@@ -240,14 +297,14 @@ const AdminSettingsPanel = ({
value={adminSettings.notifications?.ntfy?.username || ''} value={adminSettings.notifications?.ntfy?.username || ''}
onChange={(event) => onNotificationChange('ntfy', 'username', event.target.value)} 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" className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Benutzername" placeholder="Benutzername (optional)"
/> />
<input <input
type="password" type="password"
value={adminSettings.notifications?.ntfy?.password || ''} value={adminSettings.notifications?.ntfy?.password || ''}
onChange={(event) => onNotificationChange('ntfy', 'password', event.target.value)} 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" className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
placeholder="Passwort" placeholder="Passwort (optional)"
/> />
</div> </div>
</div> </div>
@@ -256,7 +313,7 @@ const AdminSettingsPanel = ({
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div> <div>
<h3 className="text-lg font-semibold text-gray-800">Telegram</h3> <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> <p className="text-xs text-gray-500">Bot-Einstellungen für Telegram-Nachrichten.</p>
</div> </div>
<label className="inline-flex items-center space-x-2 text-sm text-gray-700"> <label className="inline-flex items-center space-x-2 text-sm text-gray-700">
<input <input
@@ -268,6 +325,7 @@ const AdminSettingsPanel = ({
<span>Aktiv</span> <span>Aktiv</span>
</label> </label>
</div> </div>
<div className="space-y-3">
<input <input
type="text" type="text"
value={adminSettings.notifications?.telegram?.botToken || ''} value={adminSettings.notifications?.telegram?.botToken || ''}
@@ -277,6 +335,8 @@ const AdminSettingsPanel = ({
/> />
</div> </div>
</div> </div>
</div>
</SettingSection>
<div className="flex justify-end mt-4"> <div className="flex justify-end mt-4">
<button <button