feat: Objekte-Kategorien (stern/sonne/beide) — Version 1.10.1
- Neue Spalte Kategorie (SET stern/sonne) in objekte-Tabelle - ObjektSelector zeigt je nach ArtFuehrung nur passende Objekte - SonnenFührung: Sonne fest vorausgewählt, zusätzliche Sonne-Objekte wählbar - Bestehende Objekte erhalten Kategorie automatisch beim Speichern - Admin: Kategorie editierbar (stern / sonne / stern,sonne) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-18
@@ -120,8 +120,11 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
.catch(() => {});
|
||||
}
|
||||
if (editEntry && editEntry.Objekte) {
|
||||
const names = editEntry.Objekte.split(', ').map((n) => n.trim());
|
||||
fetch('/api/objekte')
|
||||
const allNames = editEntry.Objekte.split(', ').map((n) => n.trim());
|
||||
const isSonneEntry = editEntry.ArtFuehrung === SONNE_ART;
|
||||
const names = isSonneEntry ? allNames.filter((n) => n.toLowerCase() !== 'sonne') : allNames;
|
||||
const kat = isSonneEntry ? 'sonne' : 'stern';
|
||||
fetch('/api/objekte?kategorie=' + kat)
|
||||
.then((r) => r.json())
|
||||
.then((all: { ID: number; Name: string }[]) => {
|
||||
const result: SelectedObjekt[] = names.map((name) => {
|
||||
@@ -134,12 +137,10 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
}
|
||||
}, [editEntry]);
|
||||
|
||||
// Objekte-Vorauswahl je nach Art der Führung; Besucher zurücksetzen wenn nicht relevant
|
||||
// Objekte und Besucher zurücksetzen beim Wechsel der Art der Führung
|
||||
useEffect(() => {
|
||||
if (artFuehrung === SONNE_ART) {
|
||||
setObjekte([{ ID: null, Name: 'Sonne' }]);
|
||||
} else if (NO_OBJEKTE_ARTEN.includes(artFuehrung)) {
|
||||
setObjekte([]);
|
||||
setObjekte([]);
|
||||
if (NO_OBJEKTE_ARTEN.includes(artFuehrung)) {
|
||||
setBesucher('');
|
||||
}
|
||||
}, [artFuehrung]);
|
||||
@@ -172,7 +173,7 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
Ende: ende,
|
||||
Besucher: besucher === '' ? 0 : besucher,
|
||||
beoIds: beos.map((b) => b.ID),
|
||||
objekte: showObjekte ? objekte : [],
|
||||
objekte: showObjekte ? (isSonne ? [{ ID: null, Name: 'Sonne' }, ...objekte] : objekte) : [],
|
||||
Bemerkungen: bemerkungen,
|
||||
Wetter: { ...wetter, temp: parseFloat(tempRaw) || 0 },
|
||||
};
|
||||
@@ -304,16 +305,12 @@ export default function LogbuchForm({ kuppel, currentUserBeo, editEntry, onSaved
|
||||
{showObjekte && (
|
||||
<div>
|
||||
<label className={labelCls}>Beobachtete Objekte</label>
|
||||
{isSonne ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="inline-flex items-center bg-green-100 text-green-800 text-sm px-3 py-1.5 rounded-full">
|
||||
Sonne
|
||||
</span>
|
||||
<span className="text-xs text-gray-500">(bei Sonnenführung fest vorgegeben)</span>
|
||||
</div>
|
||||
) : (
|
||||
<ObjektSelector selected={objekte} onChange={setObjekte} />
|
||||
)}
|
||||
<ObjektSelector
|
||||
selected={objekte}
|
||||
onChange={setObjekte}
|
||||
kategorie={isSonne ? 'sonne' : 'stern'}
|
||||
fixedItems={isSonne ? [{ ID: null, Name: 'Sonne' }] : []}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ import type { ObjektOption, SelectedObjekt } from '@/types/logbuch';
|
||||
interface Props {
|
||||
selected: SelectedObjekt[];
|
||||
onChange: (objekte: SelectedObjekt[]) => void;
|
||||
kategorie?: 'stern' | 'sonne';
|
||||
fixedItems?: SelectedObjekt[];
|
||||
}
|
||||
|
||||
export default function ObjektSelector({ selected, onChange }: Props) {
|
||||
export default function ObjektSelector({ selected, onChange, kategorie = 'stern', fixedItems = [] }: Props) {
|
||||
const [all, setAll] = useState<ObjektOption[]>([]);
|
||||
const [search, setSearch] = useState('');
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
@@ -16,11 +18,11 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/objekte')
|
||||
fetch('/api/objekte?kategorie=' + kategorie)
|
||||
.then((r) => { if (!r.ok) throw new Error('Fehler'); return r.json(); })
|
||||
.then(setAll)
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
}, [kategorie]);
|
||||
|
||||
useEffect(() => {
|
||||
function handleOutside(e: MouseEvent) {
|
||||
@@ -32,14 +34,17 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
||||
return () => document.removeEventListener('mousedown', handleOutside);
|
||||
}, [dropdownOpen]);
|
||||
|
||||
const fixedNamesLower = new Set(fixedItems.map((o) => o.Name.toLowerCase()));
|
||||
const selectedNames = new Set(selected.map((o) => o.Name.toLowerCase()));
|
||||
const available = all.filter((o) => !selectedNames.has(o.Name.toLowerCase()));
|
||||
const available = all.filter(
|
||||
(o) => !selectedNames.has(o.Name.toLowerCase()) && !fixedNamesLower.has(o.Name.toLowerCase())
|
||||
);
|
||||
const filtered = search
|
||||
? available.filter((o) => o.Name.toLowerCase().startsWith(search.toLowerCase()))
|
||||
: available;
|
||||
|
||||
const searchTrimmed = search.trim();
|
||||
const alreadySelected = searchTrimmed !== '' && selectedNames.has(searchTrimmed.toLowerCase());
|
||||
const alreadySelected = searchTrimmed !== '' && (selectedNames.has(searchTrimmed.toLowerCase()) || fixedNamesLower.has(searchTrimmed.toLowerCase()));
|
||||
const exactAvailableMatch = available.find((o) => o.Name.toLowerCase() === searchTrimmed.toLowerCase());
|
||||
const showAddNew = searchTrimmed !== '' && !alreadySelected && !exactAvailableMatch;
|
||||
|
||||
@@ -51,7 +56,7 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
||||
|
||||
function addNew(name: string) {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed || selectedNames.has(trimmed.toLowerCase())) return;
|
||||
if (!trimmed || selectedNames.has(trimmed.toLowerCase()) || fixedNamesLower.has(trimmed.toLowerCase())) return;
|
||||
const existing = all.find((o) => o.Name.toLowerCase() === trimmed.toLowerCase());
|
||||
if (existing) {
|
||||
onChange([...selected, { ID: existing.ID, Name: existing.Name }]);
|
||||
@@ -79,6 +84,14 @@ export default function ObjektSelector({ selected, onChange }: Props) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{fixedItems.map((o) => (
|
||||
<span
|
||||
key={'fixed-' + o.Name}
|
||||
className="inline-flex items-center gap-2 bg-blue-100 text-blue-800 text-base px-3 py-1.5 rounded-full"
|
||||
>
|
||||
{o.Name}
|
||||
</span>
|
||||
))}
|
||||
{selected.map((o) => (
|
||||
<span
|
||||
key={o.Name}
|
||||
|
||||
Reference in New Issue
Block a user