Link am Betrieb hinzugefügt
This commit is contained in:
@@ -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 30–60 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
|
||||||
|
|||||||
Reference in New Issue
Block a user