/** * Page Manager (Advanced) * Phase 7: Advanced Site Management * Features: Drag-drop reorder, bulk actions, selection */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { PlusIcon, EditIcon, TrashIcon, GripVerticalIcon, CheckSquareIcon, SquareIcon } from 'lucide-react'; import PageMeta from '../../components/common/PageMeta'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { fetchAPI } from '../../services/api'; interface Page { id: number; slug: string; title: string; type: string; status: string; order: number; blocks: any[]; } // Draggable Page Item Component const DraggablePageItem: React.FC<{ page: Page; index: number; isSelected: boolean; onSelect: (id: number) => void; onEdit: (id: number) => void; onDelete: (id: number) => void; movePage: (dragIndex: number, hoverIndex: number) => void; }> = ({ page, index, isSelected, onSelect, onEdit, onDelete, movePage }) => { const [{ isDragging }, drag] = useDrag({ type: 'page', item: { id: page.id, index }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }); const [, drop] = useDrop({ accept: 'page', hover: (draggedItem: { id: number; index: number }) => { if (draggedItem.index !== index) { movePage(draggedItem.index, index); draggedItem.index = index; } }, }); return (
drag(drop(node))} className={`flex items-center justify-between p-4 border rounded-lg transition-all ${ isDragging ? 'opacity-50 border-brand-500 bg-brand-50 dark:bg-brand-900/20' : 'border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-800' } ${isSelected ? 'bg-blue-50 dark:bg-blue-900/20 border-blue-300 dark:border-blue-700' : ''}`} >

{page.title}

/{page.slug} • {page.type} • {page.status} • Order: {page.order}

); }; export default function PageManager() { const { siteId } = useParams<{ siteId: string }>(); const navigate = useNavigate(); const toast = useToast(); const [pages, setPages] = useState([]); const [loading, setLoading] = useState(true); const [selectedPages, setSelectedPages] = useState>(new Set()); const [isReordering, setIsReordering] = useState(false); useEffect(() => { if (siteId) { loadPages(); } }, [siteId]); const loadPages = async () => { try { setLoading(true); // First, get blueprints for this site const blueprintsData = await fetchAPI(`/v1/site-builder/blueprints/?site=${siteId}`); const blueprints = Array.isArray(blueprintsData?.results) ? blueprintsData.results : Array.isArray(blueprintsData) ? blueprintsData : []; if (blueprints.length === 0) { setPages([]); return; } // Load pages from the first blueprint (or allow selection) const blueprintId = blueprints[0].id; const pagesData = await fetchAPI(`/v1/site-builder/pages/?site_blueprint=${blueprintId}`); const pagesList = Array.isArray(pagesData?.results) ? pagesData.results : Array.isArray(pagesData) ? pagesData : []; setPages(pagesList.sort((a, b) => a.order - b.order)); } catch (error: any) { toast.error(`Failed to load pages: ${error.message}`); } finally { setLoading(false); } }; const handleAddPage = () => { navigate(`/sites/${siteId}/pages/new`); }; const handleEditPage = (pageId: number) => { navigate(`/sites/${siteId}/pages/${pageId}/edit`); }; const handleDeletePage = async (pageId: number) => { if (!confirm('Are you sure you want to delete this page?')) return; try { await fetchAPI(`/v1/site-builder/pages/${pageId}/`, { method: 'DELETE', }); toast.success('Page deleted successfully'); loadPages(); } catch (error: any) { toast.error(`Failed to delete page: ${error.message}`); } }; const movePage = (dragIndex: number, hoverIndex: number) => { const draggedPage = pages[dragIndex]; const newPages = [...pages]; newPages.splice(dragIndex, 1); newPages.splice(hoverIndex, 0, draggedPage); // Update order values newPages.forEach((page, index) => { page.order = index; }); setPages(newPages); setIsReordering(true); }; const savePageOrder = async () => { try { // Update all pages' order await Promise.all( pages.map((page, index) => fetchAPI(`/v1/site-builder/pages/${page.id}/`, { method: 'PATCH', body: JSON.stringify({ order: index }), }) ) ); toast.success('Page order saved'); setIsReordering(false); } catch (error: any) { toast.error(`Failed to save page order: ${error.message}`); loadPages(); // Reload on error } }; const handleSelectPage = (pageId: number) => { const newSelected = new Set(selectedPages); if (newSelected.has(pageId)) { newSelected.delete(pageId); } else { newSelected.add(pageId); } setSelectedPages(newSelected); }; const handleSelectAll = () => { if (selectedPages.size === pages.length) { setSelectedPages(new Set()); } else { setSelectedPages(new Set(pages.map((p) => p.id))); } }; const handleBulkDelete = async () => { if (selectedPages.size === 0) { toast.error('No pages selected'); return; } if (!confirm(`Are you sure you want to delete ${selectedPages.size} page(s)?`)) return; try { await Promise.all( Array.from(selectedPages).map((id) => fetchAPI(`/v1/site-builder/pages/${id}/`, { method: 'DELETE', }) ) ); toast.success(`${selectedPages.size} page(s) deleted successfully`); setSelectedPages(new Set()); loadPages(); } catch (error: any) { toast.error(`Failed to delete pages: ${error.message}`); } }; const handleBulkStatusChange = async (newStatus: string) => { if (selectedPages.size === 0) { toast.error('No pages selected'); return; } try { await Promise.all( Array.from(selectedPages).map((id) => fetchAPI(`/v1/site-builder/pages/${id}/`, { method: 'PATCH', body: JSON.stringify({ status: newStatus }), }) ) ); toast.success(`${selectedPages.size} page(s) updated`); setSelectedPages(new Set()); loadPages(); } catch (error: any) { toast.error(`Failed to update pages: ${error.message}`); } }; if (loading) { return (
Loading pages...
); } return (

Page Manager

Manage pages for your site

{pages.length === 0 ? (

No pages created yet

) : ( <> {/* Bulk Actions Bar */} {selectedPages.size > 0 && (
{selectedPages.size} page(s) selected
)} {/* Reorder Save Button */} {isReordering && (
Page order changed. Save to apply changes.
)}

Drag and drop to reorder pages

{pages.map((page, index) => ( ))}
)}
); }