diff --git a/admin-frontend/src/pages/Tags.tsx b/admin-frontend/src/pages/Tags.tsx
index f139608..a349730 100644
--- a/admin-frontend/src/pages/Tags.tsx
+++ b/admin-frontend/src/pages/Tags.tsx
@@ -1115,6 +1115,11 @@ export default function Tags() {
selectedPhotoIds={Array.from(selectedPhotoIds)}
photos={photos.filter(p => selectedPhotoIds.has(p.id))}
tags={tags}
+ onTagsUpdated={async () => {
+ // Reload tags when new tags are created
+ const tagsRes = await tagsApi.list()
+ setTags(tagsRes.items)
+ }}
onClose={async () => {
setShowTagSelectedDialog(false)
setSelectedPhotoIds(new Set())
@@ -1775,17 +1780,26 @@ function TagSelectedPhotosDialog({
selectedPhotoIds,
photos,
tags,
+ onTagsUpdated,
onClose,
}: {
selectedPhotoIds: number[]
photos: PhotoWithTagsItem[]
tags: TagResponse[]
+ onTagsUpdated?: () => Promise
onClose: () => void
}) {
const [selectedTagName, setSelectedTagName] = useState('')
+ const [newTagName, setNewTagName] = useState('')
const [selectedTagIds, setSelectedTagIds] = useState>(new Set())
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
const [photoTagsData, setPhotoTagsData] = useState>({})
+ const [localTags, setLocalTags] = useState(tags)
+
+ // Update local tags when tags prop changes
+ useEffect(() => {
+ setLocalTags(tags)
+ }, [tags])
// Load tag linkage information for all selected photos
useEffect(() => {
@@ -1809,28 +1823,59 @@ function TagSelectedPhotosDialog({
}, [selectedPhotoIds])
const handleAddTag = async () => {
- if (!selectedTagName.trim() || selectedPhotoIds.length === 0) return
+ if (selectedPhotoIds.length === 0) return
- // Check if tag exists, create if not
- let tag = tags.find(t => t.tag_name.toLowerCase() === selectedTagName.toLowerCase().trim())
- if (!tag) {
- try {
- tag = await tagsApi.create(selectedTagName.trim())
- // Note: We don't update the tags list here since it's passed from parent
- } catch (error) {
- console.error('Failed to create tag:', error)
- alert('Failed to create tag')
- return
- }
+ // Collect both tags: selected existing tag and new tag name
+ const tagsToAdd: string[] = []
+
+ if (selectedTagName.trim()) {
+ tagsToAdd.push(selectedTagName.trim())
+ }
+
+ if (newTagName.trim()) {
+ tagsToAdd.push(newTagName.trim())
+ }
+
+ if (tagsToAdd.length === 0) {
+ alert('Please select a tag or enter a new tag name.')
+ return
}
- // Make single batch API call for all selected photos
try {
+ // Create any new tags first
+ const newTags = tagsToAdd.filter(tag =>
+ !localTags.some(availableTag =>
+ availableTag.tag_name.toLowerCase() === tag.toLowerCase()
+ )
+ )
+
+ if (newTags.length > 0) {
+ const createdTags: TagResponse[] = []
+ for (const newTag of newTags) {
+ const createdTag = await tagsApi.create(newTag)
+ createdTags.push(createdTag)
+ }
+ // Update local tags immediately with newly created tags
+ setLocalTags(prev => {
+ const updated = [...prev, ...createdTags]
+ // Sort by tag name
+ return updated.sort((a, b) => a.tag_name.localeCompare(b.tag_name))
+ })
+ // Also reload tags list in parent to keep it in sync
+ if (onTagsUpdated) {
+ await onTagsUpdated()
+ }
+ }
+
+ // Add all tags to photos in a single API call
await tagsApi.addToPhotos({
photo_ids: selectedPhotoIds,
- tag_names: [selectedTagName.trim()],
+ tag_names: tagsToAdd,
})
+
+ // Clear inputs after successful tagging
setSelectedTagName('')
+ setNewTagName('')
// Reload photo tags data to update the common tags list
const tagsData: Record = {}
@@ -1901,7 +1946,7 @@ function TagSelectedPhotosDialog({
allPhotoTags[photoId] = photoTagsData[photoId] || []
})
- const tagIdToName = new Map(tags.map(t => [t.id, t.tag_name]))
+ const tagIdToName = new Map(localTags.map(t => [t.id, t.tag_name]))
// Get all unique tag IDs from all photos
const allTagIds = new Set()
@@ -1930,7 +1975,7 @@ function TagSelectedPhotosDialog({
}
})
.filter(Boolean) as any[]
- }, [photos, tags, selectedPhotoIds, photoTagsData])
+ }, [photos, localTags, selectedPhotoIds, photoTagsData])
// Get selected tag names for confirmation message
const selectedTagNames = useMemo(() => {
@@ -1961,11 +2006,14 @@ function TagSelectedPhotosDialog({
-
+
+
-
+
+ You can select an existing tag and enter a new tag name to add both at once.
+
+
+
+
+
setNewTagName(e.target.value)}
+ className="w-full px-3 py-2 border border-gray-300 rounded"
+ placeholder="Type new tag name..."
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleAddTag()
+ }
+ }}
+ />
+
+ New tags will be created in the database automatically.
+
@@ -2024,12 +2088,21 @@ function TagSelectedPhotosDialog({
>
Remove selected tags
-
+
+
+
+