diff --git a/frontend/src/api/tags.ts b/frontend/src/api/tags.ts index 4b6e105..c58f3d6 100644 --- a/frontend/src/api/tags.ts +++ b/frontend/src/api/tags.ts @@ -14,7 +14,6 @@ export interface TagsResponse { export interface PhotoTagsRequest { photo_ids: number[] tag_names: string[] - linkage_type?: number // 0=single, 1=bulk } export interface PhotoTagsResponse { @@ -27,7 +26,6 @@ export interface PhotoTagsResponse { export interface PhotoTagItem { tag_id: number tag_name: string - linkage_type: number // 0=single, 1=bulk } export interface PhotoTagsListResponse { diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index 6a6b3c0..d0e2fc9 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -91,7 +91,7 @@ export default function Search() { if (searchType === 'name') { if (!personName.trim()) { - alert('Please enter a name to search.') + alert('Please enter at least one name to search.') setLoading(false) return } @@ -259,8 +259,7 @@ export default function Search() { }, [showTagModal, selectedPhotos.size]) // Get all unique tags from selected photos - // Only include tags that are single (linkage_type = 0) in ALL selected photos - // Tags that are bulk (linkage_type = 1) in ANY photo cannot be removed + // Include all tags (both single and bulk) that are present in all selected photos const allPhotoTags = useMemo(() => { if (selectedPhotos.size === 0) return [] @@ -276,18 +275,18 @@ export default function Search() { }) }) - // Filter to only include tags that are single (linkage_type = 0) in ALL photos - const removableTags = Array.from(tagMap.values()).filter(tag => { - // Check if this tag is single in all selected photos + // Filter to only include tags that exist in ALL selected photos (both single and bulk) + const commonTags = Array.from(tagMap.values()).filter(tag => { + // Check if this tag exists in all selected photos return photoIds.every(photoId => { const photoTagList = photoTags[photoId] || [] const photoTag = photoTagList.find(t => t.tag_id === tag.tag_id) - // Tag must exist in photo and be single (linkage_type = 0) - return photoTag && photoTag.linkage_type === 0 + // Tag must exist in photo + return photoTag !== undefined }) }) - return removableTags + return commonTags }, [photoTags, selectedPhotos]) const handleAddTag = async () => { @@ -314,11 +313,10 @@ export default function Search() { setNewTagName('') } - // Add tag to photos (always as single/linkage_type = 0) + // Add tag to photos await tagsApi.addToPhotos({ photo_ids: Array.from(selectedPhotos), tag_names: [tagToAdd], - linkage_type: 0, // Always single }) setSelectedTagName('') @@ -349,18 +347,8 @@ export default function Search() { setShowDeleteConfirm(false) // Get tag names from selected tag IDs - // Filter to only include tags that are single (linkage_type = 0) in all photos - // This is a defensive check - allPhotoTags should already only contain single tags - const photoIds = Array.from(selectedPhotos) + // Allow removing both single and bulk tags const tagNamesToDelete = Array.from(selectedTagIds) - .filter(tagId => { - // Check if tag is single in all selected photos - return photoIds.every(photoId => { - const photoTagList = photoTags[photoId] || [] - const photoTag = photoTagList.find(t => t.tag_id === tagId) - return photoTag && photoTag.linkage_type === 0 - }) - }) .map(tagId => { const tag = allPhotoTags.find(t => t.tag_id === tagId) return tag ? tag.tag_name : null @@ -368,7 +356,7 @@ export default function Search() { .filter(Boolean) as string[] if (tagNamesToDelete.length === 0) { - alert('No removable tags selected. Bulk tags cannot be removed from this dialog.') + alert('No tags selected to remove.') return } @@ -618,7 +606,7 @@ export default function Search() { {searchType === 'name' && (
- Note: Bulk tags cannot be removed from this dialog -
{loadingTags ? (Loading tags...
) : allPhotoTags.length === 0 ? ( diff --git a/frontend/src/pages/Tags.tsx b/frontend/src/pages/Tags.tsx index 331cf9d..91816fd 100644 --- a/frontend/src/pages/Tags.tsx +++ b/frontend/src/pages/Tags.tsx @@ -7,7 +7,6 @@ type ViewMode = 'list' | 'icons' | 'compact' interface PendingTagChange { photoId: number tagIds: number[] - linkageType: number // 0=single, 1=bulk } interface PendingTagRemoval { @@ -50,7 +49,6 @@ export default function Tags() { const [folderStates, setFolderStates] = useStateFolder: {folder.folderName} ({folder.photoCount} photos) @@ -1449,14 +1434,12 @@ function BulkTagDialog({
No bulk tags linked to this folder
+No tags linked to this folder
) : ( allTags.map(tag => { if (!folder || folder.photos.length === 0) return null const firstPhotoId = folder.photos[0].id const isPending = (pendingTagChanges[firstPhotoId] || []).includes(tag.tag_id) - const linkageType = tag.linkage_type || 1 - const canSelect = isPending || linkageType === 1 return (No common tags found
) : ( commonTags.map(tag => { - const isRemovable = tag.isRemovable !== false return (