Add artist name suggestions to the admin gallery

Integrates an autocomplete input for artist names in the admin gallery, providing suggestions based on existing artist data and improving the user experience for managing artist information.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 23852c00-4779-460a-9e0c-d09fee4b6c92
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 3f1757a7-5d6b-4987-9597-190ab7c18a23
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/23852c00-4779-460a-9e0c-d09fee4b6c92/ncMMRQ9
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic 2026-03-06 11:07:58 +00:00
parent f52f8c7ba0
commit bcc93e01af
3 changed files with 76 additions and 11 deletions

View File

@ -32,6 +32,7 @@ function FocalPointEditor({
image,
currentFocalPoint,
currentArtist,
allArtistNames,
onSaveFocal,
onResetFocal,
onSaveArtist,
@ -41,6 +42,7 @@ function FocalPointEditor({
image: CloudinaryImage;
currentFocalPoint?: { x: number; y: number };
currentArtist: string;
allArtistNames: string[];
onSaveFocal: (x: number, y: number) => Promise<void>;
onResetFocal: () => Promise<void>;
onSaveArtist: (artist: string) => Promise<void>;
@ -52,7 +54,15 @@ function FocalPointEditor({
const [saving, setSaving] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [deleting, setDeleting] = useState(false);
const [showSuggestions, setShowSuggestions] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
const suggestionsRef = useRef<HTMLDivElement>(null);
const filteredSuggestions = artistName.length >= 1
? allArtistNames.filter(
(name) => name.toLowerCase().includes(artistName.toLowerCase()) && name.toLowerCase() !== artistName.toLowerCase()
).slice(0, 8)
: [];
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
const img = imgRef.current;
@ -105,14 +115,39 @@ function FocalPointEditor({
<div className="flex flex-col items-center gap-4 mt-16 w-full max-w-4xl">
<div className="flex items-center gap-3 w-full max-w-md">
<User className="w-4 h-4 text-white/50 flex-shrink-0" />
<input
type="text"
value={artistName}
onChange={(e) => setArtistName(e.target.value)}
placeholder="Interpret / Künstlername eingeben..."
className="flex-1 bg-white/10 border border-white/20 rounded px-3 py-2 text-white text-sm placeholder:text-white/30 focus:outline-none focus:border-yellow-400/60"
data-testid="input-artist-name"
/>
<div className="relative flex-1">
<input
type="text"
value={artistName}
onChange={(e) => { setArtistName(e.target.value); setShowSuggestions(true); }}
onFocus={() => setShowSuggestions(true)}
onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
placeholder="Interpret / Künstlername eingeben..."
className="w-full bg-white/10 border border-white/20 rounded px-3 py-2 text-white text-sm placeholder:text-white/30 focus:outline-none focus:border-yellow-400/60"
data-testid="input-artist-name"
/>
{showSuggestions && filteredSuggestions.length > 0 && (
<div
ref={suggestionsRef}
className="absolute top-full left-0 right-0 mt-1 bg-zinc-800 border border-white/20 rounded-lg shadow-xl overflow-hidden z-50 max-h-48 overflow-y-auto"
data-testid="artist-suggestions"
>
{filteredSuggestions.map((name) => (
<button
key={name}
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={() => { setArtistName(name); setShowSuggestions(false); }}
className="w-full text-left px-3 py-2 text-sm text-white/80 hover:bg-yellow-500/20 hover:text-white transition-colors flex items-center gap-2"
data-testid={`suggestion-${name}`}
>
<User className="w-3 h-3 text-white/30 flex-shrink-0" />
<span>{name}</span>
</button>
))}
</div>
)}
</div>
</div>
<p className="text-white/50 text-xs text-center">
@ -334,6 +369,8 @@ export default function AdminGalleryPage() {
const withFocal = Object.keys(fp).length;
const withoutArtist = (images || []).filter((img) => !img.artist).length;
const allArtistNames = [...new Set((images || []).map((img) => img.artist).filter(Boolean))].sort();
return (
<div className="min-h-screen bg-zinc-950 text-white">
<div className="border-b border-white/10 bg-zinc-900/80 sticky top-0 z-40 backdrop-blur-sm">
@ -489,6 +526,7 @@ export default function AdminGalleryPage() {
image={editingImage}
currentFocalPoint={fp[editingImage.fileName]}
currentArtist={editingImage.artist || ""}
allArtistNames={allArtistNames}
onSaveFocal={handleSaveFocalPoint}
onResetFocal={handleResetFocalPoint}
onSaveArtist={handleSaveArtist}

View File

@ -1 +1,4 @@
{}
{
"DSC06296.jpg": "Simon Wild",
"DSC06416.jpg": "Simon Wild"
}

View File

@ -1,7 +1,7 @@
{
"DSC07135.jpg": {
"x": 50,
"y": 30
"x": 42,
"y": 29
},
"Arina 1.jpg": {
"x": 70,
@ -18,5 +18,29 @@
"Aufw rts 1 1 .jpg": {
"x": 18,
"y": 43
},
"Christa Fartek 2.jpg": {
"x": 39,
"y": 60
},
"D Oimhittn Musi 2.jpg": {
"x": 43,
"y": 50
},
"DSC06296.jpg": {
"x": 50,
"y": 38
},
"DSC06416.jpg": {
"x": 46,
"y": 45
},
"DSC07594.jpg": {
"x": 46,
"y": 41
},
"DSC07438.jpg": {
"x": 57,
"y": 46
}
}