mirror of
				https://github.com/chartdb/chartdb.git
				synced 2025-11-03 21:43:23 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			v1.17.0
			...
			jf/prevent
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e90887e9ee | ||
| 
						 | 
					ad1e59bdd2 | ||
| 
						 | 
					6282a555bb | 
@@ -1,6 +1,10 @@
 | 
				
			|||||||
import type { DBTable } from '@/lib/domain/db-table';
 | 
					import type { DBTable } from '@/lib/domain/db-table';
 | 
				
			||||||
import type { Area } from '@/lib/domain/area';
 | 
					import type { Area } from '@/lib/domain/area';
 | 
				
			||||||
import { calcTableHeight } from '@/lib/domain/db-table';
 | 
					import { calcTableHeight } from '@/lib/domain/db-table';
 | 
				
			||||||
 | 
					import type { DiagramFilter } from '@/lib/domain/diagram-filter/diagram-filter';
 | 
				
			||||||
 | 
					import { filterTable } from '@/lib/domain/diagram-filter/filter';
 | 
				
			||||||
 | 
					import { defaultSchemas } from '@/lib/data/default-schemas';
 | 
				
			||||||
 | 
					import { DatabaseType } from '@/lib/domain/database-type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Check if a table is inside an area based on their positions and dimensions
 | 
					 * Check if a table is inside an area based on their positions and dimensions
 | 
				
			||||||
@@ -30,16 +34,54 @@ const isTableInsideArea = (table: DBTable, area: Area): boolean => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check if an area is visible based on its tables
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const isAreaVisible = (
 | 
				
			||||||
 | 
					    area: Area,
 | 
				
			||||||
 | 
					    tables: DBTable[],
 | 
				
			||||||
 | 
					    filter?: DiagramFilter,
 | 
				
			||||||
 | 
					    databaseType?: DatabaseType
 | 
				
			||||||
 | 
					): boolean => {
 | 
				
			||||||
 | 
					    const tablesInArea = tables.filter((t) => t.parentAreaId === area.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // If area has no tables, consider it visible
 | 
				
			||||||
 | 
					    if (tablesInArea.length === 0) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Area is visible if at least one table in it is visible
 | 
				
			||||||
 | 
					    return tablesInArea.some((table) =>
 | 
				
			||||||
 | 
					        filterTable({
 | 
				
			||||||
 | 
					            table: { id: table.id, schema: table.schema },
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
 | 
					            options: {
 | 
				
			||||||
 | 
					                defaultSchema:
 | 
				
			||||||
 | 
					                    defaultSchemas[databaseType || DatabaseType.GENERIC],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Find which area contains a table
 | 
					 * Find which area contains a table
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const findContainingArea = (table: DBTable, areas: Area[]): Area | null => {
 | 
					const findContainingArea = (
 | 
				
			||||||
 | 
					    table: DBTable,
 | 
				
			||||||
 | 
					    areas: Area[],
 | 
				
			||||||
 | 
					    tables: DBTable[],
 | 
				
			||||||
 | 
					    filter?: DiagramFilter,
 | 
				
			||||||
 | 
					    databaseType?: DatabaseType
 | 
				
			||||||
 | 
					): Area | null => {
 | 
				
			||||||
    // Sort areas by order (if available) to prioritize top-most areas
 | 
					    // Sort areas by order (if available) to prioritize top-most areas
 | 
				
			||||||
    const sortedAreas = [...areas].sort(
 | 
					    const sortedAreas = [...areas].sort(
 | 
				
			||||||
        (a, b) => (b.order ?? 0) - (a.order ?? 0)
 | 
					        (a, b) => (b.order ?? 0) - (a.order ?? 0)
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const area of sortedAreas) {
 | 
					    for (const area of sortedAreas) {
 | 
				
			||||||
 | 
					        // Skip hidden areas - they shouldn't capture tables
 | 
				
			||||||
 | 
					        if (!isAreaVisible(area, tables, filter, databaseType)) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (isTableInsideArea(table, area)) {
 | 
					        if (isTableInsideArea(table, area)) {
 | 
				
			||||||
            return area;
 | 
					            return area;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -53,10 +95,33 @@ const findContainingArea = (table: DBTable, areas: Area[]): Area | null => {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export const updateTablesParentAreas = (
 | 
					export const updateTablesParentAreas = (
 | 
				
			||||||
    tables: DBTable[],
 | 
					    tables: DBTable[],
 | 
				
			||||||
    areas: Area[]
 | 
					    areas: Area[],
 | 
				
			||||||
 | 
					    filter?: DiagramFilter,
 | 
				
			||||||
 | 
					    databaseType?: DatabaseType
 | 
				
			||||||
): DBTable[] => {
 | 
					): DBTable[] => {
 | 
				
			||||||
    return tables.map((table) => {
 | 
					    return tables.map((table) => {
 | 
				
			||||||
        const containingArea = findContainingArea(table, areas);
 | 
					        // Skip hidden tables - they shouldn't be assigned to areas
 | 
				
			||||||
 | 
					        const isTableVisible = filterTable({
 | 
				
			||||||
 | 
					            table: { id: table.id, schema: table.schema },
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
 | 
					            options: {
 | 
				
			||||||
 | 
					                defaultSchema:
 | 
				
			||||||
 | 
					                    defaultSchemas[databaseType || DatabaseType.GENERIC],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isTableVisible) {
 | 
				
			||||||
 | 
					            // Hidden tables keep their current parent area (don't change)
 | 
				
			||||||
 | 
					            return table;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const containingArea = findContainingArea(
 | 
				
			||||||
 | 
					            table,
 | 
				
			||||||
 | 
					            areas,
 | 
				
			||||||
 | 
					            tables,
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
 | 
					            databaseType
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        const newParentAreaId = containingArea?.id || null;
 | 
					        const newParentAreaId = containingArea?.id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Only update if parentAreaId has changed
 | 
					        // Only update if parentAreaId has changed
 | 
				
			||||||
@@ -80,3 +145,26 @@ export const getTablesInArea = (
 | 
				
			|||||||
): DBTable[] => {
 | 
					): DBTable[] => {
 | 
				
			||||||
    return tables.filter((table) => table.parentAreaId === areaId);
 | 
					    return tables.filter((table) => table.parentAreaId === areaId);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get visible tables that are inside a specific area
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const getVisibleTablesInArea = (
 | 
				
			||||||
 | 
					    areaId: string,
 | 
				
			||||||
 | 
					    tables: DBTable[],
 | 
				
			||||||
 | 
					    filter?: DiagramFilter,
 | 
				
			||||||
 | 
					    databaseType?: DatabaseType
 | 
				
			||||||
 | 
					): DBTable[] => {
 | 
				
			||||||
 | 
					    return tables.filter((table) => {
 | 
				
			||||||
 | 
					        if (table.parentAreaId !== areaId) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return filterTable({
 | 
				
			||||||
 | 
					            table: { id: table.id, schema: table.schema },
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
 | 
					            options: {
 | 
				
			||||||
 | 
					                defaultSchema:
 | 
				
			||||||
 | 
					                    defaultSchemas[databaseType || DatabaseType.GENERIC],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,19 @@ import React, {
 | 
				
			|||||||
    useEffect,
 | 
					    useEffect,
 | 
				
			||||||
    useRef,
 | 
					    useRef,
 | 
				
			||||||
} from 'react';
 | 
					} from 'react';
 | 
				
			||||||
import { X, Search, Eye, EyeOff, Database, Table, Funnel } from 'lucide-react';
 | 
					import {
 | 
				
			||||||
 | 
					    X,
 | 
				
			||||||
 | 
					    Search,
 | 
				
			||||||
 | 
					    Eye,
 | 
				
			||||||
 | 
					    EyeOff,
 | 
				
			||||||
 | 
					    Database,
 | 
				
			||||||
 | 
					    Table,
 | 
				
			||||||
 | 
					    Funnel,
 | 
				
			||||||
 | 
					    Layers,
 | 
				
			||||||
 | 
					    Box,
 | 
				
			||||||
 | 
					} from 'lucide-react';
 | 
				
			||||||
import { useChartDB } from '@/hooks/use-chartdb';
 | 
					import { useChartDB } from '@/hooks/use-chartdb';
 | 
				
			||||||
 | 
					import type { DBTable } from '@/lib/domain/db-table';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
import { Button } from '@/components/button/button';
 | 
					import { Button } from '@/components/button/button';
 | 
				
			||||||
import { Input } from '@/components/input/input';
 | 
					import { Input } from '@/components/input/input';
 | 
				
			||||||
@@ -18,14 +29,17 @@ import type { TreeNode } from '@/components/tree-view/tree';
 | 
				
			|||||||
import { ScrollArea } from '@/components/scroll-area/scroll-area';
 | 
					import { ScrollArea } from '@/components/scroll-area/scroll-area';
 | 
				
			||||||
import { filterSchema, filterTable } from '@/lib/domain/diagram-filter/filter';
 | 
					import { filterSchema, filterTable } from '@/lib/domain/diagram-filter/filter';
 | 
				
			||||||
import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
 | 
					import { useDiagramFilter } from '@/context/diagram-filter-context/use-diagram-filter';
 | 
				
			||||||
 | 
					import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CanvasFilterProps {
 | 
					export interface CanvasFilterProps {
 | 
				
			||||||
    onClose: () => void;
 | 
					    onClose: () => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type NodeType = 'schema' | 'table';
 | 
					type NodeType = 'schema' | 'area' | 'table';
 | 
				
			||||||
 | 
					type GroupingMode = 'schema' | 'area';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SchemaContext = { name: string; visible: boolean };
 | 
					type SchemaContext = { name: string; visible: boolean };
 | 
				
			||||||
 | 
					type AreaContext = { id: string; name: string; visible: boolean };
 | 
				
			||||||
type TableContext = {
 | 
					type TableContext = {
 | 
				
			||||||
    tableSchema?: string | null;
 | 
					    tableSchema?: string | null;
 | 
				
			||||||
    visible: boolean;
 | 
					    visible: boolean;
 | 
				
			||||||
@@ -33,6 +47,7 @@ type TableContext = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type NodeContext = {
 | 
					type NodeContext = {
 | 
				
			||||||
    schema: SchemaContext;
 | 
					    schema: SchemaContext;
 | 
				
			||||||
 | 
					    area: AreaContext;
 | 
				
			||||||
    table: TableContext;
 | 
					    table: TableContext;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,7 +59,7 @@ type RelevantTableData = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
					export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			||||||
    const { t } = useTranslation();
 | 
					    const { t } = useTranslation();
 | 
				
			||||||
    const { tables, databaseType } = useChartDB();
 | 
					    const { tables, databaseType, areas } = useChartDB();
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
        filter,
 | 
					        filter,
 | 
				
			||||||
        toggleSchemaFilter,
 | 
					        toggleSchemaFilter,
 | 
				
			||||||
@@ -56,6 +71,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
    const [searchQuery, setSearchQuery] = useState('');
 | 
					    const [searchQuery, setSearchQuery] = useState('');
 | 
				
			||||||
    const [expanded, setExpanded] = useState<Record<string, boolean>>({});
 | 
					    const [expanded, setExpanded] = useState<Record<string, boolean>>({});
 | 
				
			||||||
    const [isFilterVisible, setIsFilterVisible] = useState(false);
 | 
					    const [isFilterVisible, setIsFilterVisible] = useState(false);
 | 
				
			||||||
 | 
					    const [groupingMode, setGroupingMode] = useState<GroupingMode>('schema');
 | 
				
			||||||
    const searchInputRef = useRef<HTMLInputElement>(null);
 | 
					    const searchInputRef = useRef<HTMLInputElement>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Extract only the properties needed for tree data
 | 
					    // Extract only the properties needed for tree data
 | 
				
			||||||
@@ -76,40 +92,99 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Convert tables to tree nodes
 | 
					    // Convert tables to tree nodes
 | 
				
			||||||
    const treeData = useMemo(() => {
 | 
					    const treeData = useMemo(() => {
 | 
				
			||||||
        // Group tables by schema
 | 
					 | 
				
			||||||
        const tablesBySchema = new Map<string, RelevantTableData[]>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        relevantTableData.forEach((table) => {
 | 
					 | 
				
			||||||
            const schema = !databaseWithSchemas
 | 
					 | 
				
			||||||
                ? 'All Tables'
 | 
					 | 
				
			||||||
                : (table.schema ?? defaultSchemas[databaseType] ?? 'default');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!tablesBySchema.has(schema)) {
 | 
					 | 
				
			||||||
                tablesBySchema.set(schema, []);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            tablesBySchema.get(schema)!.push(table);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Sort tables within each schema
 | 
					 | 
				
			||||||
        tablesBySchema.forEach((tables) => {
 | 
					 | 
				
			||||||
            tables.sort((a, b) => a.name.localeCompare(b.name));
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Convert to tree nodes
 | 
					 | 
				
			||||||
        const nodes: TreeNode<NodeType, NodeContext>[] = [];
 | 
					        const nodes: TreeNode<NodeType, NodeContext>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tablesBySchema.forEach((schemaTables, schemaName) => {
 | 
					        if (groupingMode === 'area') {
 | 
				
			||||||
            let schemaVisible;
 | 
					            // Group tables by area
 | 
				
			||||||
 | 
					            const tablesByArea = new Map<string | null, DBTable[]>();
 | 
				
			||||||
 | 
					            const tablesWithoutArea: DBTable[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (databaseWithSchemas) {
 | 
					            tables.forEach((table) => {
 | 
				
			||||||
                const schemaId = schemaNameToSchemaId(schemaName);
 | 
					                if (table.parentAreaId) {
 | 
				
			||||||
                schemaVisible = filterSchema({
 | 
					                    if (!tablesByArea.has(table.parentAreaId)) {
 | 
				
			||||||
                    schemaId,
 | 
					                        tablesByArea.set(table.parentAreaId, []);
 | 
				
			||||||
                    schemaIdsFilter: filter?.schemaIds,
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                    tablesByArea.get(table.parentAreaId)!.push(table);
 | 
				
			||||||
            } else {
 | 
					                } else {
 | 
				
			||||||
                // if at least one table is visible, the schema is considered visible
 | 
					                    tablesWithoutArea.push(table);
 | 
				
			||||||
                schemaVisible = schemaTables.some((table) =>
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sort tables within each area
 | 
				
			||||||
 | 
					            tablesByArea.forEach((areaTables) => {
 | 
				
			||||||
 | 
					                areaTables.sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            tablesWithoutArea.sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create nodes for areas
 | 
				
			||||||
 | 
					            areas.forEach((area) => {
 | 
				
			||||||
 | 
					                const areaTables = tablesByArea.get(area.id) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Check if at least one table in the area is visible
 | 
				
			||||||
 | 
					                const areaVisible =
 | 
				
			||||||
 | 
					                    areaTables.length === 0 ||
 | 
				
			||||||
 | 
					                    areaTables.some((table) =>
 | 
				
			||||||
 | 
					                        filterTable({
 | 
				
			||||||
 | 
					                            table: {
 | 
				
			||||||
 | 
					                                id: table.id,
 | 
				
			||||||
 | 
					                                schema: table.schema,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            filter,
 | 
				
			||||||
 | 
					                            options: {
 | 
				
			||||||
 | 
					                                defaultSchema: defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const areaNode: TreeNode<NodeType, NodeContext> = {
 | 
				
			||||||
 | 
					                    id: `area-${area.id}`,
 | 
				
			||||||
 | 
					                    name: `${area.name} (${areaTables.length})`,
 | 
				
			||||||
 | 
					                    type: 'area',
 | 
				
			||||||
 | 
					                    isFolder: true,
 | 
				
			||||||
 | 
					                    icon: Box,
 | 
				
			||||||
 | 
					                    context: {
 | 
				
			||||||
 | 
					                        id: area.id,
 | 
				
			||||||
 | 
					                        name: area.name,
 | 
				
			||||||
 | 
					                        visible: areaVisible,
 | 
				
			||||||
 | 
					                    } as AreaContext,
 | 
				
			||||||
 | 
					                    className: !areaVisible ? 'opacity-50' : '',
 | 
				
			||||||
 | 
					                    children: areaTables.map(
 | 
				
			||||||
 | 
					                        (table): TreeNode<NodeType, NodeContext> => {
 | 
				
			||||||
 | 
					                            const tableVisible = filterTable({
 | 
				
			||||||
 | 
					                                table: {
 | 
				
			||||||
 | 
					                                    id: table.id,
 | 
				
			||||||
 | 
					                                    schema: table.schema,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                filter,
 | 
				
			||||||
 | 
					                                options: {
 | 
				
			||||||
 | 
					                                    defaultSchema: defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return {
 | 
				
			||||||
 | 
					                                id: table.id,
 | 
				
			||||||
 | 
					                                name: table.name,
 | 
				
			||||||
 | 
					                                type: 'table',
 | 
				
			||||||
 | 
					                                isFolder: false,
 | 
				
			||||||
 | 
					                                icon: Table,
 | 
				
			||||||
 | 
					                                context: {
 | 
				
			||||||
 | 
					                                    tableSchema: table.schema,
 | 
				
			||||||
 | 
					                                    visible: tableVisible,
 | 
				
			||||||
 | 
					                                } as TableContext,
 | 
				
			||||||
 | 
					                                className: !tableVisible ? 'opacity-50' : '',
 | 
				
			||||||
 | 
					                            };
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (areaTables.length > 0) {
 | 
				
			||||||
 | 
					                    nodes.push(areaNode);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add ungrouped tables
 | 
				
			||||||
 | 
					            if (tablesWithoutArea.length > 0) {
 | 
				
			||||||
 | 
					                const ungroupedVisible = tablesWithoutArea.some((table) =>
 | 
				
			||||||
                    filterTable({
 | 
					                    filterTable({
 | 
				
			||||||
                        table: {
 | 
					                        table: {
 | 
				
			||||||
                            id: table.id,
 | 
					                            id: table.id,
 | 
				
			||||||
@@ -121,19 +196,84 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const schemaNode: TreeNode<NodeType, NodeContext> = {
 | 
					                const ungroupedNode: TreeNode<NodeType, NodeContext> = {
 | 
				
			||||||
                id: `schema-${schemaName}`,
 | 
					                    id: 'ungrouped',
 | 
				
			||||||
                name: `${schemaName} (${schemaTables.length})`,
 | 
					                    name: `Ungrouped (${tablesWithoutArea.length})`,
 | 
				
			||||||
                type: 'schema',
 | 
					                    type: 'area',
 | 
				
			||||||
                isFolder: true,
 | 
					                    isFolder: true,
 | 
				
			||||||
                icon: Database,
 | 
					                    icon: Layers,
 | 
				
			||||||
                context: { name: schemaName, visible: schemaVisible },
 | 
					                    context: {
 | 
				
			||||||
                className: !schemaVisible ? 'opacity-50' : '',
 | 
					                        id: 'ungrouped',
 | 
				
			||||||
                children: schemaTables.map(
 | 
					                        name: 'Ungrouped',
 | 
				
			||||||
                    (table): TreeNode<NodeType, NodeContext> => {
 | 
					                        visible: ungroupedVisible,
 | 
				
			||||||
                        const tableVisible = filterTable({
 | 
					                    } as AreaContext,
 | 
				
			||||||
 | 
					                    className: !ungroupedVisible ? 'opacity-50' : '',
 | 
				
			||||||
 | 
					                    children: tablesWithoutArea.map(
 | 
				
			||||||
 | 
					                        (table): TreeNode<NodeType, NodeContext> => {
 | 
				
			||||||
 | 
					                            const tableVisible = filterTable({
 | 
				
			||||||
 | 
					                                table: {
 | 
				
			||||||
 | 
					                                    id: table.id,
 | 
				
			||||||
 | 
					                                    schema: table.schema,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                filter,
 | 
				
			||||||
 | 
					                                options: {
 | 
				
			||||||
 | 
					                                    defaultSchema: defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return {
 | 
				
			||||||
 | 
					                                id: table.id,
 | 
				
			||||||
 | 
					                                name: table.name,
 | 
				
			||||||
 | 
					                                type: 'table',
 | 
				
			||||||
 | 
					                                isFolder: false,
 | 
				
			||||||
 | 
					                                icon: Table,
 | 
				
			||||||
 | 
					                                context: {
 | 
				
			||||||
 | 
					                                    tableSchema: table.schema,
 | 
				
			||||||
 | 
					                                    visible: tableVisible,
 | 
				
			||||||
 | 
					                                } as TableContext,
 | 
				
			||||||
 | 
					                                className: !tableVisible ? 'opacity-50' : '',
 | 
				
			||||||
 | 
					                            };
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                nodes.push(ungroupedNode);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Group tables by schema (existing logic)
 | 
				
			||||||
 | 
					            const tablesBySchema = new Map<string, RelevantTableData[]>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            relevantTableData.forEach((table) => {
 | 
				
			||||||
 | 
					                const schema = !databaseWithSchemas
 | 
				
			||||||
 | 
					                    ? 'All Tables'
 | 
				
			||||||
 | 
					                    : (table.schema ??
 | 
				
			||||||
 | 
					                      defaultSchemas[databaseType] ??
 | 
				
			||||||
 | 
					                      'default');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!tablesBySchema.has(schema)) {
 | 
				
			||||||
 | 
					                    tablesBySchema.set(schema, []);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                tablesBySchema.get(schema)!.push(table);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Sort tables within each schema
 | 
				
			||||||
 | 
					            tablesBySchema.forEach((tables) => {
 | 
				
			||||||
 | 
					                tables.sort((a, b) => a.name.localeCompare(b.name));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            tablesBySchema.forEach((schemaTables, schemaName) => {
 | 
				
			||||||
 | 
					                let schemaVisible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (databaseWithSchemas) {
 | 
				
			||||||
 | 
					                    const schemaId = schemaNameToSchemaId(schemaName);
 | 
				
			||||||
 | 
					                    schemaVisible = filterSchema({
 | 
				
			||||||
 | 
					                        schemaId,
 | 
				
			||||||
 | 
					                        schemaIdsFilter: filter?.schemaIds,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // if at least one table is visible, the schema is considered visible
 | 
				
			||||||
 | 
					                    schemaVisible = schemaTables.some((table) =>
 | 
				
			||||||
 | 
					                        filterTable({
 | 
				
			||||||
                            table: {
 | 
					                            table: {
 | 
				
			||||||
                                id: table.id,
 | 
					                                id: table.id,
 | 
				
			||||||
                                schema: table.schema,
 | 
					                                schema: table.schema,
 | 
				
			||||||
@@ -142,30 +282,65 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
                            options: {
 | 
					                            options: {
 | 
				
			||||||
                                defaultSchema: defaultSchemas[databaseType],
 | 
					                                defaultSchema: defaultSchemas[databaseType],
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
                        });
 | 
					                        })
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        const hidden = !tableVisible;
 | 
					                const schemaNode: TreeNode<NodeType, NodeContext> = {
 | 
				
			||||||
 | 
					                    id: `schema-${schemaName}`,
 | 
				
			||||||
 | 
					                    name: `${schemaName} (${schemaTables.length})`,
 | 
				
			||||||
 | 
					                    type: 'schema',
 | 
				
			||||||
 | 
					                    isFolder: true,
 | 
				
			||||||
 | 
					                    icon: Database,
 | 
				
			||||||
 | 
					                    context: {
 | 
				
			||||||
 | 
					                        name: schemaName,
 | 
				
			||||||
 | 
					                        visible: schemaVisible,
 | 
				
			||||||
 | 
					                    } as SchemaContext,
 | 
				
			||||||
 | 
					                    className: !schemaVisible ? 'opacity-50' : '',
 | 
				
			||||||
 | 
					                    children: schemaTables.map(
 | 
				
			||||||
 | 
					                        (table): TreeNode<NodeType, NodeContext> => {
 | 
				
			||||||
 | 
					                            const tableVisible = filterTable({
 | 
				
			||||||
 | 
					                                table: {
 | 
				
			||||||
 | 
					                                    id: table.id,
 | 
				
			||||||
 | 
					                                    schema: table.schema,
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                filter,
 | 
				
			||||||
 | 
					                                options: {
 | 
				
			||||||
 | 
					                                    defaultSchema: defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return {
 | 
					                            const hidden = !tableVisible;
 | 
				
			||||||
                            id: table.id,
 | 
					
 | 
				
			||||||
                            name: table.name,
 | 
					                            return {
 | 
				
			||||||
                            type: 'table',
 | 
					                                id: table.id,
 | 
				
			||||||
                            isFolder: false,
 | 
					                                name: table.name,
 | 
				
			||||||
                            icon: Table,
 | 
					                                type: 'table',
 | 
				
			||||||
                            context: {
 | 
					                                isFolder: false,
 | 
				
			||||||
                                tableSchema: table.schema,
 | 
					                                icon: Table,
 | 
				
			||||||
                                visible: tableVisible,
 | 
					                                context: {
 | 
				
			||||||
                            },
 | 
					                                    tableSchema: table.schema,
 | 
				
			||||||
                            className: hidden ? 'opacity-50' : '',
 | 
					                                    visible: tableVisible,
 | 
				
			||||||
                        };
 | 
					                                } as TableContext,
 | 
				
			||||||
                    }
 | 
					                                className: hidden ? 'opacity-50' : '',
 | 
				
			||||||
                ),
 | 
					                            };
 | 
				
			||||||
            };
 | 
					                        }
 | 
				
			||||||
            nodes.push(schemaNode);
 | 
					                    ),
 | 
				
			||||||
        });
 | 
					                };
 | 
				
			||||||
 | 
					                nodes.push(schemaNode);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return nodes;
 | 
					        return nodes;
 | 
				
			||||||
    }, [relevantTableData, databaseType, filter, databaseWithSchemas]);
 | 
					    }, [
 | 
				
			||||||
 | 
					        relevantTableData,
 | 
				
			||||||
 | 
					        tables,
 | 
				
			||||||
 | 
					        databaseType,
 | 
				
			||||||
 | 
					        filter,
 | 
				
			||||||
 | 
					        databaseWithSchemas,
 | 
				
			||||||
 | 
					        groupingMode,
 | 
				
			||||||
 | 
					        areas,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize expanded state with all schemas expanded
 | 
					    // Initialize expanded state with all schemas expanded
 | 
				
			||||||
    useMemo(() => {
 | 
					    useMemo(() => {
 | 
				
			||||||
@@ -240,9 +415,12 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
    const renderActions = useCallback(
 | 
					    const renderActions = useCallback(
 | 
				
			||||||
        (node: TreeNode<NodeType, NodeContext>) => {
 | 
					        (node: TreeNode<NodeType, NodeContext>) => {
 | 
				
			||||||
            if (node.type === 'schema') {
 | 
					            if (node.type === 'schema') {
 | 
				
			||||||
                const schemaContext = node.context as SchemaContext;
 | 
					                const context = node.context as SchemaContext;
 | 
				
			||||||
                const schemaId = schemaNameToSchemaId(schemaContext.name);
 | 
					                const schemaVisible = context.visible;
 | 
				
			||||||
                const schemaVisible = node.context.visible;
 | 
					                const schemaName = context.name;
 | 
				
			||||||
 | 
					                if (!schemaName) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const schemaId = schemaNameToSchemaId(schemaName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return (
 | 
					                return (
 | 
				
			||||||
                    <Button
 | 
					                    <Button
 | 
				
			||||||
@@ -256,7 +434,7 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
                                toggleSchemaFilter(schemaId);
 | 
					                                toggleSchemaFilter(schemaId);
 | 
				
			||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                // Toggle visibility of all tables in this schema
 | 
					                                // Toggle visibility of all tables in this schema
 | 
				
			||||||
                                if (node.context.visible) {
 | 
					                                if (schemaVisible) {
 | 
				
			||||||
                                    setTableIdsFilterEmpty();
 | 
					                                    setTableIdsFilterEmpty();
 | 
				
			||||||
                                } else {
 | 
					                                } else {
 | 
				
			||||||
                                    clearTableIdsFilter();
 | 
					                                    clearTableIdsFilter();
 | 
				
			||||||
@@ -273,10 +451,83 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (node.type === 'area') {
 | 
				
			||||||
 | 
					                const context = node.context as AreaContext;
 | 
				
			||||||
 | 
					                const areaVisible = context.visible;
 | 
				
			||||||
 | 
					                const areaId = context.id;
 | 
				
			||||||
 | 
					                if (!areaId) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Get all tables in this area
 | 
				
			||||||
 | 
					                const areaTables =
 | 
				
			||||||
 | 
					                    areaId === 'ungrouped'
 | 
				
			||||||
 | 
					                        ? tables.filter((t) => !t.parentAreaId)
 | 
				
			||||||
 | 
					                        : tables.filter((t) => t.parentAreaId === areaId);
 | 
				
			||||||
 | 
					                const tableIds = areaTables.map((t) => t.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return (
 | 
				
			||||||
 | 
					                    <Button
 | 
				
			||||||
 | 
					                        variant="ghost"
 | 
				
			||||||
 | 
					                        size="sm"
 | 
				
			||||||
 | 
					                        className="size-7 h-fit p-0"
 | 
				
			||||||
 | 
					                        onClick={(e) => {
 | 
				
			||||||
 | 
					                            e.stopPropagation();
 | 
				
			||||||
 | 
					                            // Toggle all tables in this area
 | 
				
			||||||
 | 
					                            if (areaVisible) {
 | 
				
			||||||
 | 
					                                // Hide all tables in this area
 | 
				
			||||||
 | 
					                                tableIds.forEach((id) => {
 | 
				
			||||||
 | 
					                                    const isVisible = filterTable({
 | 
				
			||||||
 | 
					                                        table: {
 | 
				
			||||||
 | 
					                                            id,
 | 
				
			||||||
 | 
					                                            schema: tables.find(
 | 
				
			||||||
 | 
					                                                (t) => t.id === id
 | 
				
			||||||
 | 
					                                            )?.schema,
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                        filter,
 | 
				
			||||||
 | 
					                                        options: {
 | 
				
			||||||
 | 
					                                            defaultSchema:
 | 
				
			||||||
 | 
					                                                defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                    if (isVisible) {
 | 
				
			||||||
 | 
					                                        toggleTableFilter(id);
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                // Show all tables in this area
 | 
				
			||||||
 | 
					                                tableIds.forEach((id) => {
 | 
				
			||||||
 | 
					                                    const isVisible = filterTable({
 | 
				
			||||||
 | 
					                                        table: {
 | 
				
			||||||
 | 
					                                            id,
 | 
				
			||||||
 | 
					                                            schema: tables.find(
 | 
				
			||||||
 | 
					                                                (t) => t.id === id
 | 
				
			||||||
 | 
					                                            )?.schema,
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                        filter,
 | 
				
			||||||
 | 
					                                        options: {
 | 
				
			||||||
 | 
					                                            defaultSchema:
 | 
				
			||||||
 | 
					                                                defaultSchemas[databaseType],
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                    if (!isVisible) {
 | 
				
			||||||
 | 
					                                        toggleTableFilter(id);
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        {!areaVisible ? (
 | 
				
			||||||
 | 
					                            <EyeOff className="size-3.5 text-muted-foreground" />
 | 
				
			||||||
 | 
					                        ) : (
 | 
				
			||||||
 | 
					                            <Eye className="size-3.5" />
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (node.type === 'table') {
 | 
					            if (node.type === 'table') {
 | 
				
			||||||
                const tableId = node.id;
 | 
					                const tableId = node.id;
 | 
				
			||||||
                const tableContext = node.context as TableContext;
 | 
					                const context = node.context as TableContext;
 | 
				
			||||||
                const tableVisible = tableContext.visible;
 | 
					                const tableVisible = context.visible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return (
 | 
					                return (
 | 
				
			||||||
                    <Button
 | 
					                    <Button
 | 
				
			||||||
@@ -305,6 +556,9 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
            clearTableIdsFilter,
 | 
					            clearTableIdsFilter,
 | 
				
			||||||
            setTableIdsFilterEmpty,
 | 
					            setTableIdsFilterEmpty,
 | 
				
			||||||
            databaseWithSchemas,
 | 
					            databaseWithSchemas,
 | 
				
			||||||
 | 
					            tables,
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
 | 
					            databaseType,
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -312,10 +566,10 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
    const handleNodeClick = useCallback(
 | 
					    const handleNodeClick = useCallback(
 | 
				
			||||||
        (node: TreeNode<NodeType, NodeContext>) => {
 | 
					        (node: TreeNode<NodeType, NodeContext>) => {
 | 
				
			||||||
            if (node.type === 'table') {
 | 
					            if (node.type === 'table') {
 | 
				
			||||||
                const tableContext = node.context as TableContext;
 | 
					                const context = node.context as TableContext;
 | 
				
			||||||
                const isTableVisible = tableContext.visible;
 | 
					                const isTableVisible = context.visible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Only focus if neither table is hidden nor filtered by schema
 | 
					                // Only focus if table is visible
 | 
				
			||||||
                if (isTableVisible) {
 | 
					                if (isTableVisible) {
 | 
				
			||||||
                    focusOnTable(node.id);
 | 
					                    focusOnTable(node.id);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -376,13 +630,34 @@ export const CanvasFilter: React.FC<CanvasFilterProps> = ({ onClose }) => {
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            {/* Grouping Toggle */}
 | 
				
			||||||
 | 
					            <div className="border-b p-2">
 | 
				
			||||||
 | 
					                <ToggleGroup
 | 
				
			||||||
 | 
					                    type="single"
 | 
				
			||||||
 | 
					                    value={groupingMode}
 | 
				
			||||||
 | 
					                    onValueChange={(value) => {
 | 
				
			||||||
 | 
					                        if (value) setGroupingMode(value as GroupingMode);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    className="w-full"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <ToggleGroupItem value="schema" className="flex-1 text-xs">
 | 
				
			||||||
 | 
					                        <Database className="mr-1.5 size-3.5" />
 | 
				
			||||||
 | 
					                        {t('canvas_filter.group_by_schema', 'Group by Schema')}
 | 
				
			||||||
 | 
					                    </ToggleGroupItem>
 | 
				
			||||||
 | 
					                    <ToggleGroupItem value="area" className="flex-1 text-xs">
 | 
				
			||||||
 | 
					                        <Box className="mr-1.5 size-3.5" />
 | 
				
			||||||
 | 
					                        {t('canvas_filter.group_by_area', 'Group by Area')}
 | 
				
			||||||
 | 
					                    </ToggleGroupItem>
 | 
				
			||||||
 | 
					                </ToggleGroup>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {/* Table Tree */}
 | 
					            {/* Table Tree */}
 | 
				
			||||||
            <ScrollArea className="flex-1 rounded-b-lg" type="auto">
 | 
					            <ScrollArea className="flex-1 rounded-b-lg" type="auto">
 | 
				
			||||||
                <TreeView
 | 
					                <TreeView
 | 
				
			||||||
                    data={filteredTreeData}
 | 
					                    data={filteredTreeData}
 | 
				
			||||||
                    onNodeClick={handleNodeClick}
 | 
					                    onNodeClick={handleNodeClick}
 | 
				
			||||||
                    renderActionsComponent={renderActions}
 | 
					                    renderActionsComponent={renderActions}
 | 
				
			||||||
                    defaultFolderIcon={Database}
 | 
					                    defaultFolderIcon={groupingMode === 'area' ? Box : Database}
 | 
				
			||||||
                    defaultIcon={Table}
 | 
					                    defaultIcon={Table}
 | 
				
			||||||
                    expanded={expanded}
 | 
					                    expanded={expanded}
 | 
				
			||||||
                    setExpanded={setExpanded}
 | 
					                    setExpanded={setExpanded}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -86,7 +86,7 @@ import { useCanvas } from '@/hooks/use-canvas';
 | 
				
			|||||||
import type { AreaNodeType } from './area-node/area-node';
 | 
					import type { AreaNodeType } from './area-node/area-node';
 | 
				
			||||||
import { AreaNode } from './area-node/area-node';
 | 
					import { AreaNode } from './area-node/area-node';
 | 
				
			||||||
import type { Area } from '@/lib/domain/area';
 | 
					import type { Area } from '@/lib/domain/area';
 | 
				
			||||||
import { updateTablesParentAreas, getTablesInArea } from './area-utils';
 | 
					import { updateTablesParentAreas, getVisibleTablesInArea } from './area-utils';
 | 
				
			||||||
import { CanvasFilter } from './canvas-filter/canvas-filter';
 | 
					import { CanvasFilter } from './canvas-filter/canvas-filter';
 | 
				
			||||||
import { useHotkeys } from 'react-hotkeys-hook';
 | 
					import { useHotkeys } from 'react-hotkeys-hook';
 | 
				
			||||||
import { ShowAllButton } from './show-all-button';
 | 
					import { ShowAllButton } from './show-all-button';
 | 
				
			||||||
@@ -142,15 +142,40 @@ const tableToTableNode = (
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const areaToAreaNode = (area: Area): AreaNodeType => ({
 | 
					const areaToAreaNode = (
 | 
				
			||||||
    id: area.id,
 | 
					    area: Area,
 | 
				
			||||||
    type: 'area',
 | 
					    tables: DBTable[],
 | 
				
			||||||
    position: { x: area.x, y: area.y },
 | 
					    filter?: DiagramFilter,
 | 
				
			||||||
    data: { area },
 | 
					    databaseType?: DatabaseType
 | 
				
			||||||
    width: area.width,
 | 
					): AreaNodeType => {
 | 
				
			||||||
    height: area.height,
 | 
					    // Get all tables in this area
 | 
				
			||||||
    zIndex: -10,
 | 
					    const tablesInArea = tables.filter((t) => t.parentAreaId === area.id);
 | 
				
			||||||
});
 | 
					
 | 
				
			||||||
 | 
					    // Check if at least one table in the area is visible
 | 
				
			||||||
 | 
					    const hasVisibleTable =
 | 
				
			||||||
 | 
					        tablesInArea.length === 0 ||
 | 
				
			||||||
 | 
					        tablesInArea.some((table) =>
 | 
				
			||||||
 | 
					            filterTable({
 | 
				
			||||||
 | 
					                table: { id: table.id, schema: table.schema },
 | 
				
			||||||
 | 
					                filter,
 | 
				
			||||||
 | 
					                options: {
 | 
				
			||||||
 | 
					                    defaultSchema:
 | 
				
			||||||
 | 
					                        defaultSchemas[databaseType || DatabaseType.GENERIC],
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        id: area.id,
 | 
				
			||||||
 | 
					        type: 'area',
 | 
				
			||||||
 | 
					        position: { x: area.x, y: area.y },
 | 
				
			||||||
 | 
					        data: { area },
 | 
				
			||||||
 | 
					        width: area.width,
 | 
				
			||||||
 | 
					        height: area.height,
 | 
				
			||||||
 | 
					        zIndex: -10,
 | 
				
			||||||
 | 
					        hidden: !hasVisibleTable,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CanvasProps {
 | 
					export interface CanvasProps {
 | 
				
			||||||
    initialTables: DBTable[];
 | 
					    initialTables: DBTable[];
 | 
				
			||||||
@@ -409,7 +434,9 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
 | 
				
			|||||||
                        },
 | 
					                        },
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                ...areas.map(areaToAreaNode),
 | 
					                ...areas.map((area) =>
 | 
				
			||||||
 | 
					                    areaToAreaNode(area, tables, filter, databaseType)
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Check if nodes actually changed
 | 
					            // Check if nodes actually changed
 | 
				
			||||||
@@ -468,7 +495,12 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        const checkParentAreas = debounce(() => {
 | 
					        const checkParentAreas = debounce(() => {
 | 
				
			||||||
            const updatedTables = updateTablesParentAreas(tables, areas);
 | 
					            const updatedTables = updateTablesParentAreas(
 | 
				
			||||||
 | 
					                tables,
 | 
				
			||||||
 | 
					                areas,
 | 
				
			||||||
 | 
					                filter,
 | 
				
			||||||
 | 
					                databaseType
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            const needsUpdate: Array<{
 | 
					            const needsUpdate: Array<{
 | 
				
			||||||
                id: string;
 | 
					                id: string;
 | 
				
			||||||
                parentAreaId: string | null;
 | 
					                parentAreaId: string | null;
 | 
				
			||||||
@@ -509,7 +541,14 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
 | 
				
			|||||||
        }, 300);
 | 
					        }, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        checkParentAreas();
 | 
					        checkParentAreas();
 | 
				
			||||||
    }, [tablePositions, areas, updateTablesState, tables]);
 | 
					    }, [
 | 
				
			||||||
 | 
					        tablePositions,
 | 
				
			||||||
 | 
					        areas,
 | 
				
			||||||
 | 
					        updateTablesState,
 | 
				
			||||||
 | 
					        tables,
 | 
				
			||||||
 | 
					        filter,
 | 
				
			||||||
 | 
					        databaseType,
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const onConnectHandler = useCallback(
 | 
					    const onConnectHandler = useCallback(
 | 
				
			||||||
        async (params: AddEdgeParams) => {
 | 
					        async (params: AddEdgeParams) => {
 | 
				
			||||||
@@ -888,16 +927,37 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
 | 
				
			|||||||
                            const deltaX = change.position.x - currentArea.x;
 | 
					                            const deltaX = change.position.x - currentArea.x;
 | 
				
			||||||
                            const deltaY = change.position.y - currentArea.y;
 | 
					                            const deltaY = change.position.y - currentArea.y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            const childTables = getTablesInArea(
 | 
					                            // Only move visible child tables
 | 
				
			||||||
 | 
					                            const childTables = getVisibleTablesInArea(
 | 
				
			||||||
                                change.id,
 | 
					                                change.id,
 | 
				
			||||||
                                tables
 | 
					                                tables,
 | 
				
			||||||
 | 
					                                filter,
 | 
				
			||||||
 | 
					                                databaseType
 | 
				
			||||||
                            );
 | 
					                            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            // Update child table positions in storage
 | 
					                            // Update child table positions in storage
 | 
				
			||||||
                            if (childTables.length > 0) {
 | 
					                            if (childTables.length > 0) {
 | 
				
			||||||
                                updateTablesState((currentTables) =>
 | 
					                                updateTablesState((currentTables) =>
 | 
				
			||||||
                                    currentTables.map((table) => {
 | 
					                                    currentTables.map((table) => {
 | 
				
			||||||
                                        if (table.parentAreaId === change.id) {
 | 
					                                        // Only move visible tables that are in this area
 | 
				
			||||||
 | 
					                                        const isVisible = filterTable({
 | 
				
			||||||
 | 
					                                            table: {
 | 
				
			||||||
 | 
					                                                id: table.id,
 | 
				
			||||||
 | 
					                                                schema: table.schema,
 | 
				
			||||||
 | 
					                                            },
 | 
				
			||||||
 | 
					                                            filter,
 | 
				
			||||||
 | 
					                                            options: {
 | 
				
			||||||
 | 
					                                                defaultSchema:
 | 
				
			||||||
 | 
					                                                    defaultSchemas[
 | 
				
			||||||
 | 
					                                                        databaseType
 | 
				
			||||||
 | 
					                                                    ],
 | 
				
			||||||
 | 
					                                            },
 | 
				
			||||||
 | 
					                                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        if (
 | 
				
			||||||
 | 
					                                            table.parentAreaId === change.id &&
 | 
				
			||||||
 | 
					                                            isVisible
 | 
				
			||||||
 | 
					                                        ) {
 | 
				
			||||||
                                            return {
 | 
					                                            return {
 | 
				
			||||||
                                                id: table.id,
 | 
					                                                id: table.id,
 | 
				
			||||||
                                                x: table.x + deltaX,
 | 
					                                                x: table.x + deltaX,
 | 
				
			||||||
@@ -961,6 +1021,8 @@ export const Canvas: React.FC<CanvasProps> = ({ initialTables }) => {
 | 
				
			|||||||
            tables,
 | 
					            tables,
 | 
				
			||||||
            areas,
 | 
					            areas,
 | 
				
			||||||
            getNode,
 | 
					            getNode,
 | 
				
			||||||
 | 
					            databaseType,
 | 
				
			||||||
 | 
					            filter,
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user