import React, { useState, useEffect } from 'react'; import { T } from '@/i18n'; import { Grid3X3 } from 'lucide-react'; import { useLayout } from '@/modules/layout/LayoutContext'; import { LayoutContainer } from './LayoutContainer'; import FlexibleContainerRenderer from './FlexibleContainerRenderer'; import { WidgetPalette } from './WidgetPalette'; import { uploadAndCreatePicture } from '@/lib/uploadUtils'; import { supabase } from '@/integrations/supabase/client'; import { toast } from 'sonner'; import { translate } from '@/i18n'; export interface GenericCanvasEditProps { pageId: string; pageName: string; isEditMode?: boolean; showControls?: boolean; className?: string; selectedWidgetId?: string | null; selectedWidgetIds?: Set; onSelectWidget?: (widgetId: string, pageId?: string) => void; selectedContainerId?: string | null; onSelectContainer?: (containerId: string | null, pageId?: string) => void; initialLayout?: any; editingWidgetId?: string | null; onEditWidget?: (widgetId: string | null) => void; newlyAddedWidgetId?: string | null; contextVariables?: Record; pageContext?: Record; onSave?: () => Promise; selectionBreadcrumb?: React.ReactNode; } const GenericCanvasEdit: React.FC = ({ pageId, pageName, isEditMode = false, showControls = true, className = '', selectedWidgetId, selectedWidgetIds, onSelectWidget, selectedContainerId: propSelectedContainerId, onSelectContainer: propOnSelectContainer, initialLayout, editingWidgetId, onEditWidget, newlyAddedWidgetId, contextVariables, pageContext, onSave, selectionBreadcrumb }) => { const { loadedPages, loadPageLayout, hydratePageLayout, addWidgetToPage, removeWidgetFromPage, moveWidgetInPage, updatePageContainerColumns, updatePageContainerSettings, addPageContainer, addFlexPageContainer, removePageContainer, movePageContainer, isLoading } = useLayout(); const layout = loadedPages.get(pageId); // Load the page layout on mount or hydrate from prop useEffect(() => { if (initialLayout && !layout) { console.log(`[GenericCanvasEdit HYDRATE] Initial hydration for "${pageId}"`, { widgetCount: initialLayout.containers?.reduce((sum: number, c: any) => sum + (c.widgets?.length || 0), 0), updatedAt: initialLayout.updatedAt }); hydratePageLayout(pageId, initialLayout); return; } // Re-hydrate if initialLayout was updated externally (e.g. page content reloaded) if (initialLayout && layout && initialLayout.updatedAt && layout.updatedAt && initialLayout.updatedAt > layout.updatedAt) { console.log(`[GenericCanvasEdit HYDRATE] RE-HYDRATING "${pageId}" (stale)`, { initialUpdatedAt: initialLayout.updatedAt, layoutUpdatedAt: layout.updatedAt, initialWidgets: initialLayout.containers?.reduce((sum: number, c: any) => sum + (c.widgets?.length || 0), 0), currentWidgets: layout.containers?.reduce((sum: number, c: any) => sum + (c.widgets?.length || 0), 0) }); hydratePageLayout(pageId, initialLayout); return; } if (!layout) { console.log(`[GenericCanvasEdit HYDRATE] Loading from API for "${pageId}"`); loadPageLayout(pageId, pageName); } }, [pageId, pageName, layout, loadPageLayout, hydratePageLayout, initialLayout]); const [internalSelectedContainer, setInternalSelectedContainer] = useState(null); const selectedContainer = propSelectedContainerId !== undefined ? propSelectedContainerId : internalSelectedContainer; const setSelectedContainer = (id: string | null, pageId?: string) => { if (propOnSelectContainer) { propOnSelectContainer(id, pageId); } else { setInternalSelectedContainer(id); } }; const [showWidgetPalette, setShowWidgetPalette] = useState(false); const [targetContainerId, setTargetContainerId] = useState(null); const [targetColumn, setTargetColumn] = useState(undefined); const [targetRowId, setTargetRowId] = useState(undefined); if (isLoading || !layout) { return (

Loading {pageName.toLowerCase()}...

); } const handleSelectContainer = (containerId: string, innerPageId?: string) => { setSelectedContainer(containerId, innerPageId); }; const handleAddWidget = (containerId: string, columnIndex?: number, rowId?: string) => { setTargetContainerId(containerId); setTargetColumn(columnIndex); setTargetRowId(rowId); setShowWidgetPalette(true); }; const handleWidgetAdd = async (widgetId: string) => { if (targetContainerId) { try { await addWidgetToPage(pageId, targetContainerId, widgetId, targetColumn, targetRowId); } catch (error) { console.error('Failed to add widget:', error); } } setShowWidgetPalette(false); setTargetContainerId(null); setTargetColumn(undefined); setTargetRowId(undefined); }; const handleCanvasClick = () => { if (isEditMode) { setSelectedContainer(null); } }; // Drag and Drop Handler for Image Files const handleFilesDrop = async (files: File[], targetContainerId?: string, targetColumn?: number) => { if (!targetContainerId) return; const toastId = toast.loading(translate('Uploading images...')); try { const { data: { user } } = await supabase.auth.getUser(); if (!user) { toast.error(translate('You must be logged in to upload images'), { id: toastId }); return; } for (const file of files) { // Upload and create record const picture = await uploadAndCreatePicture(file, user.id); if (picture) { // Create widget with initial props await addWidgetToPage(pageId, targetContainerId, 'photo-card', targetColumn, undefined, { pictureId: picture.id }); } } toast.success(translate('Images added to page'), { id: toastId }); } catch (error) { console.error('Failed to process dropped files:', error); toast.error(translate('Failed to upload images'), { id: toastId }); } }; const totalWidgets = layout.containers.reduce((total, container) => { const getContainerWidgetCount = (cont: any): number => { let count = cont.widgets.length; if (cont.children) { cont.children.forEach((child: any) => { count += getContainerWidgetCount(child); }); } return count; }; return total + getContainerWidgetCount(container); }, 0); if (!isEditMode && totalWidgets === 0) { return null; } return (
{/* Header with Controls */} {showControls && (
{/* Layout Info */}

Containers: {layout.containers.length} | Widgets: {totalWidgets} | Last updated: {new Date(layout.updatedAt).toLocaleString()}

{selectionBreadcrumb}
)} {/* Container Canvas */}
{layout.containers .sort((a, b) => (a.order || 0) - (b.order || 0)) .map((container, index, array) => { // Dispatch to the correct renderer based on container type if (container.type === 'flex-container') { return ( { try { await removeWidgetFromPage(pageId, widgetId); } catch (error) { console.error('Failed to remove widget:', error); } }} onRemoveContainer={async (containerId) => { try { await removePageContainer(pageId, containerId); } catch (error) { console.error('Failed to remove container:', error); } }} onMoveContainer={async (containerId, direction) => { try { await movePageContainer(pageId, containerId, direction); } catch (error) { console.error('Failed to move container:', error); } }} canMoveContainerUp={index > 0} canMoveContainerDown={index < array.length - 1} /> ); } // Default: LayoutContainer return ( { try { await removeWidgetFromPage(pageId, widgetId); } catch (error) { console.error('Failed to remove widget:', error); } }} onMoveWidget={async (widgetId, direction) => { try { await moveWidgetInPage(pageId, widgetId, direction); } catch (error) { console.error('Failed to move widget:', error); } }} onUpdateColumns={async (containerId, columns) => { try { await updatePageContainerColumns(pageId, containerId, columns); } catch (error) { console.error('Failed to update columns:', error); } }} onUpdateSettings={async (containerId, settings) => { try { await updatePageContainerSettings(pageId, containerId, settings); } catch (error) { console.error('Failed to update settings:', error); } }} onAddContainer={async (parentId) => { try { await addPageContainer(pageId, parentId); } catch (error) { console.error('Failed to add container:', error); } }} onMoveContainer={async (containerId, direction) => { try { await movePageContainer(pageId, containerId, direction); } catch (error) { console.error('Failed to move container:', error); } }} canMoveContainerUp={index > 0} canMoveContainerDown={index < array.length - 1} onRemoveContainer={async (containerId) => { try { await removePageContainer(pageId, containerId); } catch (error) { console.error('Failed to remove container:', error); } }} onFilesDrop={(files, targetColumn) => handleFilesDrop(files, container.id, targetColumn)} /> ); })} {layout.containers.length === 0 && (
{isEditMode ? ( <>

No containers yet

Add a container to start building your layout

) : ( <>

Switch to edit mode to add containers

)}
)}
{/* Widget Palette Modal */} { setShowWidgetPalette(false); setTargetContainerId(null); }} onWidgetAdd={handleWidgetAdd} />
); }; export default GenericCanvasEdit;