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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user