refactoring node 24

This commit is contained in:
2025-11-10 10:39:56 +01:00
parent 0515d3d714
commit dff35b8218
10 changed files with 1102 additions and 1019 deletions

View File

@@ -0,0 +1,299 @@
import NotificationPanel from './NotificationPanel';
const DashboardView = ({
session,
onRefresh,
onLogout,
notificationPanelOpen,
onToggleNotificationPanel,
notificationProps,
stores,
availableCollapsed,
onToggleStores,
onStoreSelect,
configMap,
error,
onDismissError,
status,
visibleConfig,
config,
onToggleActive,
onToggleProfileCheck,
onToggleOnlyNotify,
onWeekdayChange,
weekdays,
onRangePickerRequest,
formatRangeLabel,
onSaveConfig,
onResetConfig
}) => {
const {
error: notificationError,
message: notificationMessage,
settings: notificationSettings,
capabilities: notificationCapabilities,
loading: notificationLoading,
dirty: notificationDirty,
saving: notificationSaving,
onReset: onNotificationReset,
onSave: onNotificationSave,
onFieldChange: onNotificationFieldChange,
onSendTest: onNotificationTest,
onCopyLink: onNotificationCopy,
copyFeedback,
ntfyPreviewUrl
} = notificationProps;
return (
<div className="p-4 max-w-6xl mx-auto bg-white shadow-lg rounded-lg mt-4">
<h1 className="text-2xl font-bold mb-4 text-center text-gray-800">Foodsharing Pickup Manager</h1>
<div className="bg-blue-50 border border-blue-100 rounded-lg p-4 mb-4">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<p className="text-sm uppercase text-blue-600 font-semibold">Angemeldet</p>
<p className="text-lg font-medium text-gray-800">{session.profile.name}</p>
<p className="text-gray-500 text-sm">Profil-ID: {session.profile.id}</p>
</div>
<div className="flex flex-wrap gap-2 items-center">
<button
onClick={() => onRefresh({ block: false })}
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"
>
Betriebe aktualisieren
</button>
<button
onClick={onLogout}
className="bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-gray-400 transition-colors"
>
Logout
</button>
<button
type="button"
onClick={onToggleNotificationPanel}
className={`flex items-center justify-center w-11 h-11 rounded-full border ${
notificationPanelOpen ? 'border-blue-500 text-blue-600' : 'border-gray-300 text-gray-600'
} hover:text-blue-700 hover:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-400 transition`}
title="Benachrichtigungen konfigurieren"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543-.89 3.31.877 2.42 2.42a1.724 1.724 0 0 0 1.065 2.572c1.757.426 1.757 2.924 0 3.35a1.724 1.724 0 0 0-1.066 2.573c.89 1.543-.877 3.31-2.42 2.42a1.724 1.724 0 0 0-2.572 1.065c-.426 1.757-2.924 1.757-3.35 0a1.724 1.724 0 0 0-2.573-1.066c-1.543.89-3.31-.877-2.42-2.42a1.724 1.724 0 0 0-1.065-2.572c-1.757-.426-1.757-2.924 0-3.35a1.724 1.724 0 0 0 1.066-2.573c-.89-1.543.877-3.31 2.42-2.42.996.575 2.273.155 2.573-1.065z"
/>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
</svg>
</button>
{notificationLoading && <span className="text-sm text-gray-500 ml-1">Lade</span>}
</div>
</div>
</div>
{notificationPanelOpen && (
<NotificationPanel
error={notificationError}
message={notificationMessage}
settings={notificationSettings}
capabilities={notificationCapabilities}
loading={notificationLoading}
dirty={notificationDirty}
saving={notificationSaving}
onReset={onNotificationReset}
onSave={onNotificationSave}
onFieldChange={onNotificationFieldChange}
onSendTest={onNotificationTest}
onCopyLink={onNotificationCopy}
copyFeedback={copyFeedback}
ntfyPreviewUrl={ntfyPreviewUrl}
/>
)}
<div className="mb-6 border border-gray-200 rounded-lg overflow-hidden">
<button
onClick={onToggleStores}
className="w-full flex items-center justify-between px-4 py-3 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-300"
>
<span className="font-semibold text-gray-800">Verfügbare Betriebe ({stores.length})</span>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`h-5 w-5 text-gray-600 transition-transform ${availableCollapsed ? '' : 'rotate-180'}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{!availableCollapsed && (
<div className="max-h-72 overflow-y-auto divide-y divide-gray-100">
{stores.map((store) => {
const storeId = String(store.id);
const entry = configMap.get(storeId);
const isVisible = entry && !entry.hidden;
const blockedByNoPickups = store.hasPickupSlots === false;
let statusLabel = 'Hinzufügen';
let statusClass = 'text-blue-600';
if (isVisible) {
statusLabel = 'Bereits in Konfiguration';
statusClass = 'text-gray-500';
} else if (entry) {
statusLabel = 'Ausgeblendet erneut hinzufügen';
statusClass = 'text-amber-600';
} else if (blockedByNoPickups) {
statusLabel = 'Keine Pickups automatisch verborgen';
statusClass = 'text-red-600';
}
return (
<div
key={storeId}
className="flex items-center justify-between px-4 py-3 hover:bg-gray-50 transition cursor-default"
>
<div>
<p className="font-semibold text-gray-800">{store.name}</p>
<p className="text-sm text-gray-500">
{store.zip} {store.city}
</p>
<p className={`text-xs mt-2 ${statusClass}`}>{statusLabel}</p>
</div>
<button
onClick={() => onStoreSelect(store)}
disabled={blockedByNoPickups}
className="bg-blue-50 hover:bg-blue-100 text-blue-600 border border-blue-200 px-3 py-1.5 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-300 disabled:opacity-60"
>
{isVisible ? 'Zur Liste springen' : 'Hinzufügen'}
</button>
</div>
);
})}
</div>
)}
</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">&times;</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="overflow-x-auto">
<table className="min-w-full bg-white border border-gray-200">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2">Aktiv</th>
<th className="px-4 py-2">Geschäft</th>
<th className="px-4 py-2">Profil prüfen</th>
<th className="px-4 py-2">Nur benachrichtigen</th>
<th className="px-4 py-2">Wochentag</th>
<th className="px-4 py-2">Datum / Zeitraum</th>
</tr>
</thead>
<tbody>
{visibleConfig.map((item, index) => {
const normalizedRange = item.desiredDateRange
? { ...item.desiredDateRange }
: item.desiredDate
? { start: item.desiredDate, end: item.desiredDate }
: null;
const rangeStart = normalizedRange?.start || '';
const rangeEnd = normalizedRange?.end || '';
const hasDateRange = Boolean(rangeStart || rangeEnd);
return (
<tr key={item.id || index} className={index % 2 === 0 ? 'bg-gray-50' : 'bg-white'}>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.active}
onChange={() => onToggleActive(item.id)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2">
<span className="font-medium">{item.label}</span>
{item.hidden && <span className="ml-2 text-xs text-gray-400">(ausgeblendet)</span>}
</td>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.checkProfileId}
onChange={() => onToggleProfileCheck(item.id)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2 text-center">
<input
type="checkbox"
checked={item.onlyNotify}
onChange={() => onToggleOnlyNotify(item.id)}
className="h-5 w-5"
/>
</td>
<td className="px-4 py-2">
<select
value={item.desiredWeekday || ''}
onChange={(event) => onWeekdayChange(item.id, event.target.value)}
className="border rounded p-1 w-full"
disabled={hasDateRange}
>
<option value="">Kein Wochentag</option>
{weekdays.map((day) => (
<option key={day} value={day}>
{day}
</option>
))}
</select>
</td>
<td className="px-4 py-2">
<button
type="button"
onClick={() => {
if (item.desiredWeekday) {
return;
}
onRangePickerRequest(item.id);
}}
disabled={Boolean(item.desiredWeekday)}
className={`w-full border rounded p-2 text-left transition focus:outline-none focus:ring-2 focus:ring-blue-500 ${
item.desiredWeekday
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-white hover:border-blue-400'
}`}
>
<span className="block text-sm text-gray-700">{formatRangeLabel(rangeStart, rangeEnd)}</span>
<span className="block text-xs text-gray-500">Klicke zum Auswählen</span>
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="mt-6 flex justify-between">
<button onClick={onResetConfig} className="bg-gray-500 hover:bg-gray-600 text-white py-2 px-4 rounded">
Zurücksetzen
</button>
<button onClick={onSaveConfig} className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded">
In ioBroker speichern
</button>
</div>
<div className="mt-8 p-4 border rounded bg-gray-50">
<h2 className="text-lg font-bold mb-2">Aktuelle JSON-Konfiguration:</h2>
<pre className="bg-gray-100 p-4 rounded overflow-x-auto">{JSON.stringify(config, null, 2)}</pre>
</div>
</div>
);
};
export default DashboardView;