diff --git a/web/pgadmin/static/js/PgTreeView/index.jsx b/web/pgadmin/static/js/PgTreeView/index.jsx index 18492448c01..529361cda2f 100644 --- a/web/pgadmin/static/js/PgTreeView/index.jsx +++ b/web/pgadmin/static/js/PgTreeView/index.jsx @@ -80,39 +80,80 @@ export default function PgTreeView({ data = [], hasCheckbox = false, const [checkedState, setCheckedState] = React.useState({}); const { ref: containerRef, width, height } = useResizeObserver(); + // Handle checkbox toggle and collect all checked nodes + // to pass complete selection state to the backup dialog const toggleCheck = (node, isChecked) => { const newState = { ...checkedState }; - const selectedChNodes = []; - // Update the node itself and all descendants + // Update the clicked node and all its descendants with the new checked value const updateDescendants = (n, val) => { newState[n.id] = val; - if (val) { - selectedChNodes.push(n); - } - n.children?.forEach(child => updateDescendants(child, val)); + n.children?.forEach(child => { updateDescendants(child, val); }); }; updateDescendants(node, isChecked); - // Update ancestors (Indeterminate logic) + // Update ancestor nodes to reflect the correct state (checked/unchecked/indeterminate) + // This ensures parent nodes show proper visual feedback based on children's state let parent = node.parent; while (parent && parent.id !== '__root__') { - const allChecked = parent.children.every(c => newState[c.id]); + // Check if ALL children are fully checked (state must be exactly true, + // not 'indeterminate') to mark parent as fully checked + const allChecked = parent.children.every(c => newState[c.id] === true); + // Check if ALL children are unchecked (falsy value: false, undefined, or null) const noneChecked = parent.children.every(c => !newState[c.id]); if (allChecked) { + // All children checked -> parent is fully checked newState[parent.id] = true; - // logic for custom indeterminate property if needed } else if (noneChecked) { + // No children checked -> parent is unchecked newState[parent.id] = false; } else { - newState[parent.id] = 'indeterminate'; // Store string for 3rd state + // Some children checked, some not -> parent shows indeterminate state + newState[parent.id] = 'indeterminate'; } parent = parent.parent; } setCheckedState(newState); - selectionChange?.(selectedChNodes); + + // Collect all checked/indeterminate nodes from the entire tree + // to provide complete selection state to selectionChange callback. + // We use wrapper objects to avoid mutating the original node data. + const allCheckedNodes = []; + const collectAllCheckedNodes = (n) => { + if (!n) return; + const state = newState[n.id]; + if (state === true || state === 'indeterminate') { + // Pass wrapper object with isIndeterminate flag to differentiate + // full schema selection from partial selection in backup dialog + allCheckedNodes.push({ + node: n, + isIndeterminate: state === 'indeterminate' + }); + } + // Recursively check all children + n.children?.forEach(child => { collectAllCheckedNodes(child); }); + }; + + // Navigate up to find the root level of the tree (parent of root nodes is '__root__') + let rootNode = node; + while (rootNode.parent && rootNode.parent.id !== '__root__') { + rootNode = rootNode.parent; + } + + // Traverse all root-level nodes to collect checked nodes from entire tree + const rootParent = rootNode.parent; + if (rootParent && rootParent.children) { + // Iterate through all sibling root nodes to collect all checked nodes + rootParent.children.forEach(root => { collectAllCheckedNodes(root); }); + } else { + // Fallback: if we can't find siblings, just traverse from the found root + collectAllCheckedNodes(rootNode); + } + + // Pass all checked nodes to callback with current selection state. + selectionChange?.(allCheckedNodes); }; return ( diff --git a/web/pgadmin/tools/backup/static/js/backup.ui.js b/web/pgadmin/tools/backup/static/js/backup.ui.js index 329f126b009..9460b5b6a82 100644 --- a/web/pgadmin/tools/backup/static/js/backup.ui.js +++ b/web/pgadmin/tools/backup/static/js/backup.ui.js @@ -758,8 +758,8 @@ export default class BackupSchema extends BaseUISchema { 'foreign table': [], 'materialized view': [], }; - state?.objects?.forEach((node)=> { - if(node.data.is_schema && !node.data?.isIndeterminate) { + state?.objects?.forEach(({ node, isIndeterminate })=> { + if(node.data.is_schema && !isIndeterminate) { selectedNodeCollection['schema'].push(node.data.name); } else if(['table', 'view', 'materialized view', 'foreign table', 'sequence'].includes(node.data.type) && !node.data.is_collection && !selectedNodeCollection['schema'].includes(node.data.schema)) {