import React, { useState, useEffect } from 'react'; import './BulkActionCategoryModal.scss'; import { translateString } from '../utils/helpers/'; interface Category { title: string; uid: string; } interface BulkActionCategoryModalProps { isOpen: boolean; selectedMediaIds: string[]; onCancel: () => void; onSuccess: (message: string) => void; onError: (message: string) => void; csrfToken: string; } export const BulkActionCategoryModal: React.FC = ({ isOpen, selectedMediaIds, onCancel, onSuccess, onError, csrfToken, }) => { const [existingCategories, setExistingCategories] = useState([]); const [allCategories, setAllCategories] = useState([]); const [categoriesToAdd, setCategoriesToAdd] = useState([]); const [categoriesToRemove, setCategoriesToRemove] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { if (isOpen && selectedMediaIds.length > 0) { fetchData(); } else { // Reset state when modal closes setExistingCategories([]); setAllCategories([]); setCategoriesToAdd([]); setCategoriesToRemove([]); } }, [isOpen, selectedMediaIds]); const fetchData = async () => { setIsLoading(true); try { // Fetch existing categories (intersection - categories all selected media belong to) const existingResponse = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'category_membership', media_ids: selectedMediaIds, }), }); if (!existingResponse.ok) { throw new Error(translateString('Failed to fetch existing categories')); } const existingData = await existingResponse.json(); const existing = existingData.results || []; // Fetch all categories const allResponse = await fetch('/api/v1/categories'); if (!allResponse.ok) { throw new Error(translateString('Failed to fetch all categories')); } const allData = await allResponse.json(); const all = allData.results || allData; setExistingCategories(existing); setAllCategories(all); } catch (error) { console.error('Error fetching categories:', error); onError(translateString('Failed to load categories')); } finally { setIsLoading(false); } }; const addCategoryToList = (category: Category) => { if (!categoriesToAdd.some((c) => c.uid === category.uid)) { setCategoriesToAdd([...categoriesToAdd, category]); } }; const removeCategoryFromAddList = (category: Category) => { setCategoriesToAdd(categoriesToAdd.filter((c) => c.uid !== category.uid)); }; const markCategoryForRemoval = (category: Category) => { if (!categoriesToRemove.some((c) => c.uid === category.uid)) { setCategoriesToRemove([...categoriesToRemove, category]); } }; const unmarkCategoryForRemoval = (category: Category) => { setCategoriesToRemove(categoriesToRemove.filter((c) => c.uid !== category.uid)); }; const handleProceed = async () => { setIsProcessing(true); try { // First, add categories if any if (categoriesToAdd.length > 0) { const uidsToAdd = categoriesToAdd.map((c) => c.uid); const addResponse = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'add_to_category', media_ids: selectedMediaIds, category_uids: uidsToAdd, }), }); if (!addResponse.ok) { throw new Error(translateString('Failed to add categories')); } } // Then, remove categories if any if (categoriesToRemove.length > 0) { const uidsToRemove = categoriesToRemove.map((c) => c.uid); const removeResponse = await fetch('/api/v1/media/user/bulk_actions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken, }, body: JSON.stringify({ action: 'remove_from_category', media_ids: selectedMediaIds, category_uids: uidsToRemove, }), }); if (!removeResponse.ok) { throw new Error(translateString('Failed to remove categories')); } } onSuccess(translateString('Successfully updated categories')); onCancel(); } catch (error) { console.error('Error processing categories:', error); onError(translateString('Failed to update categories. Please try again.')); } finally { setIsProcessing(false); } }; // Get categories for left panel (all categories minus those already existing) const getLeftPanelCategories = () => { return allCategories.filter( (cat) => !existingCategories.some((existing) => existing.uid === cat.uid) ); }; // Get categories for right panel ("Add to" - existing + newly added) const getRightPanelCategories = () => { // Combine existing categories with newly added ones const combined = [...existingCategories, ...categoriesToAdd]; return combined; }; if (!isOpen) return null; const leftPanelCategories = getLeftPanelCategories(); const rightPanelCategories = getRightPanelCategories(); return (

{translateString('Add / Remove from Categories')}

{translateString('Categories')}

{isLoading ? (
{translateString('Loading categories...')}
) : (
{leftPanelCategories.length === 0 ? (
{translateString('All categories already added')}
) : ( leftPanelCategories.map((category) => (
addCategoryToList(category)} > {category.title}
)) )}
)}

{translateString('Add to')} {selectedMediaIds.length > 1 && ( ? )}

{isLoading ? (
{translateString('Loading categories...')}
) : (
{rightPanelCategories.length === 0 ? (
{translateString('No categories')}
) : ( rightPanelCategories.map((category) => { const isExisting = existingCategories.some((c) => c.uid === category.uid); const isMarkedForRemoval = categoriesToRemove.some((c) => c.uid === category.uid); return (
{category.title}
); }) )}
)}
); };