298 lines
14 KiB
JavaScript
298 lines
14 KiB
JavaScript
import { Link } from 'react-router-dom';
|
|
|
|
const AdminSettingsPanel = ({
|
|
adminSettings,
|
|
adminSettingsLoading,
|
|
status,
|
|
error,
|
|
onDismissError,
|
|
onSettingChange,
|
|
onIgnoredSlotChange,
|
|
onAddIgnoredSlot,
|
|
onRemoveIgnoredSlot,
|
|
onNotificationChange,
|
|
onSave
|
|
}) => {
|
|
return (
|
|
<div className="p-4 max-w-4xl mx-auto bg-white shadow-lg rounded-lg mt-4">
|
|
<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>
|
|
</div>
|
|
<Link
|
|
to="/"
|
|
className="inline-flex items-center justify-center px-4 py-2 text-sm font-medium border border-purple-200 rounded-md text-purple-700 hover:bg-purple-50 transition-colors"
|
|
>
|
|
Zur Konfiguration
|
|
</Link>
|
|
</div>
|
|
|
|
{error && (
|
|
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 relative">
|
|
<span className="block sm:inline">{error}</span>
|
|
<button className="absolute top-0 bottom-0 right-0 px-4 py-3" onClick={onDismissError}>
|
|
<span className="text-xl">×</span>
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{status && (
|
|
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4 relative">
|
|
<span className="block sm:inline">{status}</span>
|
|
</div>
|
|
)}
|
|
|
|
<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>}
|
|
|
|
{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>
|
|
<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 * * * *"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Initiale Verzögerung (Sek.)</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.initialDelayMinSeconds}
|
|
onChange={(event) => onSettingChange('initialDelayMinSeconds', event.target.value, true)}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
placeholder="Min"
|
|
/>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.initialDelayMaxSeconds}
|
|
onChange={(event) => onSettingChange('initialDelayMaxSeconds', event.target.value, true)}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
placeholder="Max"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Prüfverzögerung (Sek.)</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.randomDelayMinSeconds}
|
|
onChange={(event) => onSettingChange('randomDelayMinSeconds', event.target.value, true)}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
placeholder="Min"
|
|
/>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.randomDelayMaxSeconds}
|
|
onChange={(event) => onSettingChange('randomDelayMaxSeconds', event.target.value, true)}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
placeholder="Max"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Verzögerung Store-Prüfung (ms)</label>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.storePickupCheckDelayMs}
|
|
onChange={(event) => onSettingChange('storePickupCheckDelayMs', event.target.value, true)}
|
|
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>
|
|
|
|
<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>
|
|
<input
|
|
type="text"
|
|
value={adminSettings.storeWatchCron || ''}
|
|
onChange={(event) => onSettingChange('storeWatchCron', 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. */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>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.storeWatchInitialDelayMinSeconds}
|
|
onChange={(event) =>
|
|
onSettingChange('storeWatchInitialDelayMinSeconds', event.target.value, true)
|
|
}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
placeholder="Min"
|
|
/>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={adminSettings.storeWatchInitialDelayMaxSeconds}
|
|
onChange={(event) =>
|
|
onSettingChange('storeWatchInitialDelayMaxSeconds', event.target.value, true)
|
|
}
|
|
className="border rounded p-2 w-full focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
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>
|
|
|
|
<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>
|
|
{(!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">
|
|
<input
|
|
type="text"
|
|
value={slot.storeId}
|
|
onChange={(event) => onIgnoredSlotChange(index, 'storeId', event.target.value)}
|
|
placeholder="Store-ID"
|
|
className="md:col-span-2 border rounded p-2 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
/>
|
|
<input
|
|
type="text"
|
|
value={slot.description}
|
|
onChange={(event) => onIgnoredSlotChange(index, 'description', event.target.value)}
|
|
placeholder="Beschreibung (optional)"
|
|
className="md:col-span-2 border rounded p-2 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={() => onRemoveIgnoredSlot(index)}
|
|
className="text-sm text-red-600 hover:text-red-800 focus:outline-none"
|
|
>
|
|
Entfernen
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<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>
|
|
</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>
|
|
<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>
|
|
</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>
|
|
|
|
<div className="flex justify-end mt-4">
|
|
<button
|
|
type="button"
|
|
onClick={onSave}
|
|
className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded focus:outline-none focus:ring-2 focus:ring-purple-500"
|
|
>
|
|
Admin-Einstellungen speichern
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminSettingsPanel;
|