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:
parent
f52f8c7ba0
commit
bcc93e01af
@ -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}
|
||||
|
||||
@ -1 +1,4 @@
|
||||
{}
|
||||
{
|
||||
"DSC06296.jpg": "Simon Wild",
|
||||
"DSC06416.jpg": "Simon Wild"
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user