🔍 Code Extractor

class DocumentDetail_v1

Maturity: 27

Document detail view component

File:
/tf/active/vicechatdev/document_detail_old.py
Lines:
69 - 3891
Complexity:
moderate

Purpose

Document detail view component

Source Code

class DocumentDetail(param.Parameterized):
    """Document detail view component"""
    
    document_uid = param.String(default='')
    doc_number = param.String(default='')
    current_tab = param.String(default='overview')
    
    def __init__(self, parent_app=None, **params):
        super().__init__(**params)
        self.parent_app = parent_app
        self.template = None  # No template needed when embedded
        self.session_manager = SessionManager()
        self.user = None
        self.document = None
        self.current_version = None
        self.notification_area = pn.pane.Markdown("")
        self.main_content = pn.Column(sizing_mode='stretch_width')
        
        # Document info and actions areas
        self.doc_info_area = pn.Column(sizing_mode='stretch_width')
        self.doc_actions_area = pn.Column(sizing_mode='stretch_width')
        
        # Create tabs for different sections
        self.tabs = pn.layout.Tabs(sizing_mode='stretch_width')
    
    def set_user(self, user):
        """Set the current user."""
        self.user = user
        return True

    def load_document(self, document_uid=None, doc_number=None):
        """Load document by UID or document number."""
        if document_uid:
            self.document_uid = document_uid
        
        if doc_number:
            self.doc_number = doc_number
            
        return self._load_document()
    
    def get_document_view(self):
        """Get the document view for embedding in other panels."""
        container = pn.Column(
            self.notification_area,
            self.main_content,
            sizing_mode='stretch_width'
        )
        return container
    
    def _get_current_user(self) -> DocUser:
        """Get the current user from session"""
        user_id = self.session_manager.get_user_id()
        if user_id:
            return DocUser(uid=user_id)
        return None
    
    def _setup_header(self):
        """Set up the header with title and actions"""
        # Create back button
        back_btn = Button(
            name='Back to Dashboard',
            button_type='default',
            width=150
        )
        back_btn.on_click(self._navigate_back)
        
        # Create refresh button
        refresh_btn = Button(
            name='Refresh',
            button_type='default',
            width=100
        )
        refresh_btn.on_click(self._load_document)
        
        # Header with buttons
        header = Row(
            pn.pane.Markdown("# Document Details"),
            refresh_btn,
            back_btn,
            sizing_mode='stretch_width',
            align='end'
        )
        
        self.template.header.append(header)
    
    def _setup_sidebar(self):
        """Set up the sidebar with document actions"""
        # Document info area
        self.doc_info_area = Column(
            sizing_mode='stretch_width'
        )
        
        # Document actions area
        self.doc_actions_area = Column(
            sizing_mode='stretch_width'
        )
        
        # Add to sidebar
        self.template.sidebar.append(self.doc_info_area)
        self.template.sidebar.append(self.doc_actions_area)
    
    def _setup_main_area(self):
        """Set up the main area with document content tabs"""
        # Create notification area
        self.template.main.append(self.notification_area)
        
        # Create tabs for different sections
        self.tabs = Tabs(
            sizing_mode='stretch_width'
        )
        
        # Add tabs container to main area
        self.template.main.append(self.tabs)
    
    def _load_document(self, event=None):
        """Load document data and update the UI."""
        try:
            # Guard against recursive calls
            if hasattr(self, '_loading_document') and self._loading_document:
                logger.warning("Recursive call to _load_document avoided")
                return False
                
            self._loading_document = True
            
            try:
                # Clear notification
                self.notification_area.object = ""
                
                # Clear existing UI elements
                self.main_content.clear()
                self.doc_info_area.clear()
                self.doc_actions_area.clear()
                self.tabs.clear()
                
                # Get document details using document_uid directly
                if self.document_uid:
                    # Only fetch document if we don't already have it
                    if not self.document:
                        document_data = get_document(document_uid=self.document_uid)
                        
                        if not document_data:
                            self.notification_area.object = "**Error:** Document not found"
                            return False
                            
                        # Store the document
                        self.document = document_data
                        
                        # Extract properties from document data
                        self._extract_document_properties()
                    
                    # Create header
                    doc_header = pn.Column(
                        pn.pane.Markdown(f"# {self.doc_title or 'Untitled Document'}"),
                        pn.pane.Markdown(f"**Document Number:** {self.doc_number or 'No number'} | " +
                                        f"**Revision:** {self.doc_revision or 'None'} | " +
                                        f"**Status:** {self.doc_status or 'Unknown'}"),
                        sizing_mode='stretch_width'
                    )
                    
                    # Add header to main content
                    self.main_content.append(doc_header)
                    
                    # Set up document info
                    self._setup_document_info()
                    
                    # Set up document actions
                    self._setup_document_actions()
                    
                    # Create and add tabs for document content
                    self._create_document_tabs()
                    
                    # Add document info and actions to main content (in a row to mimic sidebar)
                    info_actions = pn.Row(
                        self.doc_info_area,
                        pn.layout.HSpacer(width=20),  # Spacing
                        self.doc_actions_area
                    )
                    
                    # Create layout
                    layout = pn.Column(
                        doc_header,
                        info_actions,
                        self.tabs,
                        sizing_mode='stretch_width'
                    )
                    
                    # Update main content
                    self.main_content.clear()
                    self.main_content.append(layout)
                    
                    return True
                else:
                    self.notification_area.object = "**Error:** No document UID provided"
                    return False
                    
            finally:
                # Always clear the loading flag
                self._loading_document = False
                
        except Exception as e:
            self._loading_document = False  # Ensure flag is cleared
            self.notification_area.object = f"**Error:** {str(e)}"
            logger.error(f"Error loading document: {e}")
            import traceback
            logger.error(f"Traceback: {traceback.format_exc()}")
            return False
    
    def _extract_document_properties(self):
        """Extract document properties from document data."""
        if not self.document:
            return
            
        # Case-insensitive property extraction
        def get_property(data, possible_keys, default=''):
            if not data:
                return default
            
            for key in possible_keys:
                if hasattr(data, 'get'):
                    value = data.get(key)
                    if value:
                        return value
            return default
        
        # Extract document metadata with case-insensitive lookup
        self.document_uid = get_property(self.document, ['UID', 'uid'])
        self.doc_title = get_property(self.document, ['title', 'Title'])
        self.doc_number = get_property(self.document, ['docNumber', 'doc_number', 'documentNumber'])
        self.doc_revision = get_property(self.document, ['revision', 'Revision'])
        self.doc_status = get_property(self.document, ['status', 'Status'])
        self.doc_type = get_property(self.document, ['docType', 'doc_type', 'documentType'])
        self.doc_department = get_property(self.document, ['department', 'Department'])
        self.doc_owner = get_property(self.document, ['ownerName', 'owner_name', 'owner'])
        self.doc_owner_uid = get_property(self.document, ['ownerUID', 'owner_uid'])
        self.doc_creator = get_property(self.document, ['creatorName', 'creator_name', 'creator'])
        self.doc_creator_uid = get_property(self.document, ['creatorUID', 'creator_uid'])
        self.doc_created_date = get_property(self.document, ['createdDate', 'created_date', 'created'])
        self.doc_modified_date = get_property(self.document, ['modifiedDate', 'modified_date', 'modified'])
        
        # Get document content if available directly or fetch it if needed
        self.doc_content = get_property(self.document, ['content', 'text', 'doc_text'])
    
    def load_document_data(self, document_data):
        """
        Load document directly from document data.
        
        Parameters:
        -----------
        document_data : dict
            The document data to load
        """
        logger.debug(f"Loading document from data: {type(document_data)}")
        
        try:
            # Debug the document data keys
            if hasattr(document_data, 'keys'):
                logger.debug(f"Document data keys: {document_data.keys()}")
            
            # Store the document data
            self.document = document_data
            
            # Case-insensitive property extraction
            def get_property(data, possible_keys, default=''):
                if not data:
                    return default
                
                for key in possible_keys:
                    if hasattr(data, 'get'):
                        value = data.get(key)
                        if value:
                            return value
                return default
            
            # Extract document metadata with case-insensitive lookup
            self.document_uid = get_property(document_data, ['UID', 'uid'])  # This is correct
            self.doc_title = get_property(document_data, ['title', 'Title'])
            self.doc_number = get_property(document_data, ['docNumber', 'doc_number', 'documentNumber'])
            self.doc_revision = get_property(document_data, ['revision', 'Revision'])
            self.doc_status = get_property(document_data, ['status', 'Status'])
            self.doc_type = get_property(document_data, ['docType', 'doc_type', 'documentType'])
            self.doc_department = get_property(document_data, ['department', 'Department'])
            self.doc_owner = get_property(document_data, ['ownerName', 'owner_name', 'owner'])
            self.doc_owner_uid = get_property(document_data, ['ownerUID', 'owner_uid'])
            self.doc_creator = get_property(document_data, ['creatorName', 'creator_name', 'creator'])
            self.doc_creator_uid = get_property(document_data, ['creatorUID', 'creator_uid'])
            self.doc_created_date = get_property(document_data, ['createdDate', 'created_date', 'created'])
            self.doc_modified_date = get_property(document_data, ['modifiedDate', 'modified_date', 'modified'])
            
            # Get document content if available directly or fetch it if needed
            self.doc_content = get_property(document_data, ['content', 'text', 'doc_text'])
            
            # FIX: Use self.document_uid instead of self.doc_uid
            if not self.doc_content and self.document_uid:
                # Fetch content if not included in document data
                from CDocs.controllers.document_controller import get_document_content
                content_result = get_document_content(self.document_uid)
                if content_result and isinstance(content_result, dict):
                    self.doc_content = content_result.get('content', '')
            
            # Just return True - don't try to call _load_document() which will cause infinite recursion
            logger.debug("Document data loaded successfully")
            return True
            
        except Exception as e:
            logger.error(f"Error loading document data: {e}")
            import traceback
            logger.error(f"Traceback: {traceback.format_exc()}")
            
            # Display error message in UI
            if hasattr(self, 'notification_area'):
                self.notification_area.object = f"**Error:** {str(e)}"
            
            return False
    
    def _setup_document_info(self):
        """Set up document info panel in sidebar"""
        # Document status with appropriate styling
        status_code = self.doc_status or 'DRAFT'
        status_name = settings.get_document_status_name(status_code)
        status_color = settings.get_status_color(status_code)
        
        # Basic document info
        doc_info = pn.Column(
            pn.pane.Markdown(f"## {self.doc_number or 'No document number'}"),
            pn.pane.Markdown(f"**Title:** {self.doc_title or 'Untitled'}"),
            pn.pane.Markdown(f"**Type:** {settings.get_document_type_name(self.doc_type)}"),
            pn.pane.Markdown(f"**Department:** {settings.get_department_name(self.doc_department)}"),
            pn.pane.Markdown(f"**Status:** <span style='color:{status_color};font-weight:bold;'>{status_name}</span>"),
            pn.pane.Markdown(f"**Owner:** {self.doc_owner or 'Unassigned'}"),
            pn.pane.Markdown(f"**Created:** {self._format_date(self.doc_created_date)}"),
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        
        self.doc_info_area.append(doc_info)
        
        # Include version info code from the original method
        # Get current version
        self.current_version = self.document.get('current_version')
        
        # Version info if available
        if self.current_version:
            # Determine which file path to show based on document status
            primary_file_path = None
            secondary_file_path = None
            
            # Get file paths from current version
            word_file_path = self.current_version.get('word_file_path') or self.current_version.get('fileCloudWordPath')
            pdf_file_path = self.current_version.get('pdf_file_path') or self.current_version.get('fileCloudPdfPath')
            
            # Set file paths based on document status
            if is_published_status(status_code):
                primary_file_path = pdf_file_path
                secondary_file_path = word_file_path
                primary_label = "PDF File"
                secondary_label = "Editable File"
            else:
                primary_file_path = word_file_path
                secondary_file_path = pdf_file_path
                primary_label = "Editable File"
                secondary_label = "PDF File"
            
            # Build version info panel
            version_info_items = [
                pn.pane.Markdown(f"### Current Version"),
                pn.pane.Markdown(f"**Version:** {self.current_version.get('version_number', '')}"),
                pn.pane.Markdown(f"**Created by:** {self.current_version.get('created_by_name', '')}"),
                pn.pane.Markdown(f"**Date:** {self._format_date(self.current_version.get('created_date'))}")
            ]
            
            # Add file path information if available
            if primary_file_path:
                # Truncate path for display if it's too long
                display_path = primary_file_path
                if len(display_path) > 40:
                    display_path = "..." + display_path[-40:]
                
                version_info_items.append(pn.pane.Markdown(f"**{primary_label}:** {display_path}"))
                
            if secondary_file_path:
                # Truncate path for display if it's too long
                display_path = secondary_file_path
                if len(display_path) > 40:
                    display_path = "..." + display_path[-40:]
                    
                version_info_items.append(pn.pane.Markdown(f"**{secondary_label}:** {display_path}"))
            
            version_info = pn.Column(
                *version_info_items,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'mt-3']
            )
            
            self.doc_info_area.append(version_info)
        else:
            self.doc_info_area.append(doc_info)
            self.doc_info_area.append(pn.pane.Markdown("*No versions available*"))
    
    def _setup_document_actions(self):
        """Set up document action buttons in sidebar"""
        # Create action button container
        self.doc_actions_area.append(Markdown("## Actions"))
        
        # Different actions based on document status and user permissions
        status = self.document.get('status', '')
        
        # View/download button always available
        if self.current_version:
            view_btn = pn.widgets.Button(name="View Document", button_type="primary", width=200)
            view_btn.on_click(self._view_document)
            self.doc_actions_area.append(view_btn)
            
            # Add EDIT button for documents in editable states
            if is_editable_status(status) and permissions.user_has_permission(self.user, "EDIT_DOCUMENT"):
                edit_btn = pn.widgets.Button(name="Edit Online", button_type="warning", width=200)
                edit_btn.on_click(self._edit_document_online)
                self.doc_actions_area.append(edit_btn)
         
        # Edit metadata button - available if user has edit permission
        if permissions.user_has_permission(self.user, "EDIT_DOCUMENT"):
            edit_btn = pn.widgets.Button(name="Edit Metadata", button_type="default", width=200)
            edit_btn.on_click(self._show_edit_form)
            self.doc_actions_area.append(edit_btn)
        
        # Upload new version - available if user has create version permission
        if permissions.user_has_permission(self.user, "CREATE_VERSION"):
            upload_btn = pn.widgets.Button(name="Upload New Version", button_type="default", width=200)
            upload_btn.on_click(self._show_upload_form)
            self.doc_actions_area.append(upload_btn)
        
        # Add PDF convert button for editable documents
        if is_editable_status(status) and permissions.user_has_permission(self.user, "CONVERT_DOCUMENT"):
            convert_btn = pn.widgets.Button(name="Convert to PDF", button_type="default", width=200)
            convert_btn.on_click(self._convert_to_pdf)
            self.doc_actions_area.append(convert_btn)
        
        # Review button - available for draft documents if user has review initiation permission
        if status in ['DRAFT'] and permissions.user_has_permission(self.user, "INITIATE_REVIEW"):
            review_btn = Button(name="Start Review", button_type="default", width=200)
            review_btn.on_click(self._show_review_form)
            self.doc_actions_area.append(review_btn)
        
        # Approval button - available for approved documents if user has approval initiation permission
        if status in ['DRAFT', 'IN_REVIEW'] and permissions.user_has_permission(self.user, "INITIATE_APPROVAL"):
            approval_btn = Button(name="Start Approval", button_type="default", width=200)
            approval_btn.on_click(self._show_approval_form)
            self.doc_actions_area.append(approval_btn)
        
        # Publish button - available for approved documents if user has publish permission
        if status in ['APPROVED'] and permissions.user_has_permission(self.user, "PUBLISH_DOCUMENT"):
            publish_btn = Button(name="Publish Document", button_type="success", width=200)
            publish_btn.on_click(self._show_publish_form)
            self.doc_actions_area.append(publish_btn)
        
        # Archive button - available for published documents if user has archive permission
        if status in ['PUBLISHED', 'EFFECTIVE'] and permissions.user_has_permission(self.user, "ARCHIVE_DOCUMENT"):
            archive_btn = Button(name="Archive Document", button_type="danger", width=200)
            archive_btn.on_click(self._show_archive_form)
            self.doc_actions_area.append(archive_btn)
        
        # Clone button - always available if user has create document permission
        if permissions.user_has_permission(self.user, "CREATE_DOCUMENT"):
            clone_btn = Button(name="Clone Document", button_type="default", width=200)
            clone_btn.on_click(self._show_clone_form)
            self.doc_actions_area.append(clone_btn)
    
    def _create_document_tabs(self):
        """Create tabs for different document content sections"""
        # Overview tab
        overview_tab = self._create_overview_tab()
        
        # Versions tab
        versions_tab = self._create_versions_tab()
        
        # Reviews tab
        reviews_tab = self._create_reviews_tab()
        
        # Approvals tab
        approvals_tab = self._create_approvals_tab()
        
        # Audit trail tab
        audit_tab = self._create_audit_tab()
        
        # Add tabs to the tabs container
        self.tabs.extend([
            ('Overview', overview_tab),
            ('Versions', versions_tab),
            ('Reviews', reviews_tab),
            ('Approvals', approvals_tab),
            ('Audit Trail', audit_tab)
        ])
    
    def _create_overview_tab(self):
        """Create the overview tab content"""
        # Basic overview information
        description = self.document.get('description', 'No description available')
        
        # Key metadata from document
        created_date = self._format_date(self.document.get('createdDate'))
        modified_date = self._format_date(self.document.get('modifiedDate'))
        effective_date = self._format_date(self.document.get('effective_date'))
        expiry_date = self._format_date(self.document.get('expiry_date'))
        
        # Current version preview if available
        preview_pane = Column(
            sizing_mode='stretch_width',
            height=600
        )
        
        if self.current_version:
            try:
                # Add document viewer
                doc_viewer = self._create_document_viewer()
                preview_pane.append(doc_viewer)
            except Exception as e:
                logger.error(f"Error creating document preview: {e}")
                preview_pane.append(Markdown("*Error loading document preview*"))
        else:
            preview_pane.append(Markdown("*No document version available for preview*"))
        
        # Document lifecycle dates
        dates_section = Column(
            Markdown("### Document Timeline"),
            Markdown(f"**Created:** {created_date}"),
            Markdown(f"**Last Modified:** {modified_date}"),
            Markdown(f"**Effective Date:** {effective_date or 'Not set'}"),
            Markdown(f"**Expiry Date:** {expiry_date or 'Not set'}"),
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        
        # Create layout
        overview_layout = Column(
            Markdown(f"# {self.document.get('title')}"),
            Markdown(f"## Description"),
            Markdown(description),
            Row(
                dates_section,
                sizing_mode='stretch_width'
            ),
            Markdown("## Document Preview"),
            preview_pane,
            sizing_mode='stretch_width'
        )
        
        return overview_layout
    
    def _create_versions_tab(self):
        """Create the versions tab content"""
        logger = logging.getLogger('CDocs.ui.document_detail')
        logger.debug("Creating versions tab")
        
        # Get versions from document with error handling
        versions = []
        
        try:
            # Debug the document structure
            logger.debug(f"Document keys: {list(self.document.keys() if isinstance(self.document, dict) else [])}")
            
            # Try accessing versions from the document
            if isinstance(self.document, dict):
                if 'versions' in self.document and isinstance(self.document['versions'], list):
                    versions = self.document['versions']
                    logger.debug(f"Found {len(versions)} versions in document['versions']")
                elif 'document_versions' in self.document and isinstance(self.document['document_versions'], list):
                    versions = self.document['document_versions']
                    logger.debug(f"Found {len(versions)} versions in document['document_versions']")
                
            # If no versions found in document, fetch them directly
            if not versions and hasattr(self, 'document_uid') and self.document_uid:
                logger.debug(f"No versions found in document, fetching directly for {self.document_uid}")
                try:
                    from CDocs.controllers.document_controller import get_document_versions
                    version_result = get_document_versions(self.document_uid)
                    logger.debug(f"get_document_versions result: {version_result}")
                    
                    if version_result and version_result.get('success') and 'versions' in version_result:
                        versions = version_result['versions']
                        logger.debug(f"Loaded {len(versions)} versions from direct API call")
                except Exception as version_err:
                    logger.error(f"Error fetching versions: {version_err}")
                    import traceback
                    logger.error(traceback.format_exc())
        except Exception as e:
            logger.error(f"Error accessing document versions: {e}")
            import traceback
            logger.error(traceback.format_exc())
            versions = []
        
        # Debug the versions we found
        logger.debug(f"Final versions count: {len(versions)}")
        if versions and len(versions) > 0:
            logger.debug(f"First version keys: {list(versions[0].keys() if isinstance(versions[0], dict) else [])}")
        
        if not versions:
            # Create upload new version button if user has permission
            upload_btn = Button(
                name="Upload New Version", 
                button_type="primary", 
                width=150,
                disabled=not permissions.user_has_permission(self.user, "CREATE_VERSION")
            )
            upload_btn.on_click(self._show_upload_form)
            
            return pn.Column(
                pn.pane.Markdown("# Document Versions"),
                pn.pane.Markdown("*No versions available for this document*"),
                upload_btn,
                sizing_mode='stretch_width'
            )
        #logger.info("versions found, creating DataFrame", versions)
        # Convert versions to DataFrame with flexible field names
        current_version_uid=self.document['current_version'].get('UID')
        version_rows = []
        for version in versions:
            # Skip if not a dictionary
            if not isinstance(version, dict):
                continue
                
            # Helper function to get a field with multiple possible names
            def get_field(names):
                for name in names:
                    if name in version and version[name] is not None:
                        return version[name]
                return ""
            
            # Extract data with fallbacks for different field names
            version_row = {
                'UID': get_field(['UID', 'uid', 'version_uid']),
                'version_number': get_field(['version_number', 'versionNumber', 'number', 'revision']),
                'is_current': current_version_uid==get_field(['UID', 'uid', 'version_uid']),
                'created_date': get_field(['created_date', 'createdDate', 'date']),
                'created_by_name': get_field(['created_by_name', 'createdByName', 'creatorName', 'creator']),
                'file_name': get_field(['file_name', 'fileName', 'name']),
                'comment': get_field(['comment', 'versionComment', 'notes'])
            }
            version_rows.append(version_row)
        
        # If no valid rows, show message
        if not version_rows:
            return pn.Column(
                pn.pane.Markdown("# Document Versions"),
                pn.pane.Markdown(f"*No valid version data found*"),
                sizing_mode='stretch_width'
            )
        
        # Create DataFrame
        versions_df = pd.DataFrame(version_rows)
        logger.debug(f"Created DataFrame with columns: {versions_df.columns.tolist()}")
        
        # Format dates
        if 'created_date' in versions_df.columns:
            # Convert dates safely
            try:
                versions_df['created_date'] = pd.to_datetime(versions_df['created_date']).dt.strftime('%Y-%m-%d %H:%M')
            except Exception as date_err:
                logger.warning(f"Error formatting dates: {date_err}")
        
        # Sort by version number if possible
        try:
            versions_df = versions_df.sort_values('version_number', ascending=False)
        except Exception as sort_err:
            logger.warning(f"Error sorting versions: {sort_err}")
        
        # Add hidden UID column for reference
        if 'UID' in versions_df.columns:
            versions_df['_uid'] = versions_df['UID']
        
        # Select columns for display
        display_columns = []
        column_names = {}
        
        # Include columns that exist in the dataframe
        if 'version_number' in versions_df.columns:
            display_columns.append('version_number')
            column_names['version_number'] = 'Version'
            
        if 'is_current' in versions_df.columns:
            display_columns.append('is_current')
            column_names['is_current'] = 'Current'
            
        if 'created_date' in versions_df.columns:
            display_columns.append('created_date')
            column_names['created_date'] = 'Created'
            
        if 'created_by_name' in versions_df.columns:
            display_columns.append('created_by_name')
            column_names['created_by_name'] = 'Created By'
            
        if 'file_name' in versions_df.columns:
            display_columns.append('file_name')
            column_names['file_name'] = 'File Name'
            
        if 'comment' in versions_df.columns:
            display_columns.append('comment')
            column_names['comment'] = 'Comment'
        
        # Add action column with button formatter
        versions_df['_action'] = 'Download'
        display_columns.append('_action')
        column_names['_action'] = 'Action'
        
        # Filter and rename columns
        filtered_cols = [col for col in display_columns if col in versions_df.columns]
        
        # Make sure to always include the UID columns even if they're not displayed
        display_df = versions_df[filtered_cols]

        # Create formatters for tabulator
        formatters = {
            'Current': {'type': 'tickCross'},
            'Action': {
                'type': 'button',
                'buttonTitle': 'Download',
                'buttonLabel': 'Download'
            }
        }

        # Make sure there's a hidden UID column for reference
        if 'uid' in versions_df.columns:
            display_df['__uid'] = versions_df['UID']  # Double underscore to avoid conflicts

        if '_uid' in versions_df.columns:
            display_df['__uid'] = versions_df['_uid']  # Double underscore to avoid conflicts

        # Create versions table
        versions_table = Tabulator(
            display_df,
            pagination='local',
            page_size=10,
            sizing_mode='stretch_width',
            selectable=1,
            height=400,
            formatters=formatters
        )
        
        # Add version selection handler
        versions_table.on_click(self._version_selected)
        
        # Create upload new version button if user has permission
        upload_btn = Button(
            name="Upload New Version", 
            button_type="primary", 
            width=150,
            disabled=not permissions.user_has_permission(self.user, "CREATE_VERSION")
        )
        upload_btn.on_click(self._show_upload_form)
        
        # Create version action area
        version_action_area = pn.Column(
            pn.pane.Markdown("## Select a version to view"),
            sizing_mode='stretch_width'
        )
        
        # Store this for later reference
        self._version_action_area = version_action_area
        
        # Layout
        versions_layout = pn.Column(
            pn.pane.Markdown("# Document Versions"),
            pn.pane.Markdown("The table below shows all versions of this document. Click on a version to view or download it."),
            pn.Row(
                pn.layout.HSpacer(),
                upload_btn,
                sizing_mode='stretch_width',
                align='end'
            ),
            versions_table,
            version_action_area,
            sizing_mode='stretch_width'
        )
        
        return versions_layout

    def _edit_document_online(self, event=None):
        """Get edit URL from FileCloud and open it"""
        # Show loading message
        self.notification_area.object = "**Getting edit URL...**"
        
        try:
            # Import the document controller function
            from CDocs.controllers.document_controller import get_document_edit_url
            
            # Call API to get edit URL
            result = get_document_edit_url(
                user=self.user,
                document_uid=self.document_uid
            )
            
            if result.get('success'):
                edit_url = result.get('edit_url')
                # Create a clickable link to open the edit URL
                self.notification_area.object = f"""
                **Document ready for editing!**
                
                Click this link to edit the document online: 
                [Open in FileCloud Editor]({edit_url})
                
                """
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Unable to get edit URL')}"
        
        except Exception as e:
            import traceback
            logger.error(f"Error getting edit URL: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error getting edit URL:** {str(e)}"

    def _convert_to_pdf(self, event=None):
        """Convert the current document version to PDF"""
        # Show loading message
        self.notification_area.object = "**Converting document to PDF...**"
        
        try:
            # Import the document controller function
            from CDocs.controllers.document_controller import convert_document_to_pdf
            
            # Call API to convert document - FIX: Pass the user object
            result = convert_document_to_pdf(
                user=self.user,  # Pass the user object
                document_uid=self.document_uid
            )
            
            if result.get('success'):
                # Show success message with PDF path
                message = f"""
                **Document converted to PDF successfully!**
                
                PDF path: {result.get('pdf_path')}
                
                Reload this page to see the updated document.
                """
                
                # Create a new notification with both message and button
                notification_column = pn.Column(
                    pn.pane.Markdown(message),
                    pn.widgets.Button(name="Reload Document", button_type="primary", on_click=self._load_document)
                )
                
                # Replace the notification area with our column
                for i, item in enumerate(self.main_content):
                    if item is self.notification_area:
                        self.main_content[i] = notification_column
                        break
                
                if not any(item is notification_column for item in self.main_content):
                    # If we couldn't find and replace, just update the message
                    self.notification_area.object = message
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to convert document')}"
        
        except Exception as e:
            import traceback
            logger.error(f"Error converting document to PDF: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error converting document:** {str(e)}"
    
    def _create_reviews_tab(self):
        """Create the reviews tab content"""
        # Get review cycles from document
        review_cycles = []
        
        try:
            # Call controller to get review cycles
            from CDocs.controllers.review_controller import get_document_review_cycles
            review_result = get_document_review_cycles(document_uid=self.document_uid)
            review_cycles = review_result.get('review_cycles', [])
            
            logger.debug(f"Loaded {len(review_cycles)} review cycles")
        except Exception as e:
            logger.error(f"Error loading review cycles: {e}")
            return pn.Column(
                pn.pane.Markdown("# Document Reviews"),
                pn.pane.Markdown(f"**Error loading review data:** {str(e)}"),
                sizing_mode='stretch_width'
            )
        
        if not review_cycles:
            # Create button to start review if appropriate
            if self.document.get('status') == 'DRAFT' and permissions.user_has_permission(self.user, "INITIATE_REVIEW"):
                start_review_btn = pn.widgets.Button(
                    name="Start Review", 
                    button_type="primary", 
                    width=150
                )
                start_review_btn.on_click(self._show_review_form)
                
                return pn.Column(
                    pn.pane.Markdown("# Document Reviews"),
                    pn.pane.Markdown("*No review cycles found for this document*"),
                    start_review_btn,
                    sizing_mode='stretch_width'
                )
            else:
                return pn.Column(
                    pn.pane.Markdown("# Document Reviews"),
                    pn.pane.Markdown("*No review cycles found for this document*"),
                    sizing_mode='stretch_width'
                )
        
        # Convert to DataFrame for tabulator
        try:
            # Create a clean list of dictionaries for the DataFrame
            reviews_data = []
            for cycle in review_cycles:
                # Convert Neo4j DateTime objects to Python datetime objects or strings
                cycle_dict = {}
                for key, value in cycle.items():
                    # Convert Neo4j DateTime objects to strings
                    if hasattr(value, '__class__') and value.__class__.__name__ == 'DateTime':
                        # Convert Neo4j DateTime to string in ISO format
                        try:
                            cycle_dict[key] = value.iso_format()[:10]  # Just get the YYYY-MM-DD part
                        except (AttributeError, TypeError):
                            cycle_dict[key] = str(value)
                    else:
                        cycle_dict[key] = value
                
                reviews_data.append(cycle_dict)
            
            # Create DataFrame (safely handle empty data)
            if not reviews_data:
                return pn.Column(
                    pn.pane.Markdown("# Document Reviews"),
                    pn.pane.Markdown("*Error: Review cycle data is in unexpected format*"),
                    sizing_mode='stretch_width'
                )
            
            reviews_df = pd.DataFrame(reviews_data)
        except Exception as df_error:
            logger.error(f"Error creating reviews DataFrame: {df_error}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            return pn.Column(
                pn.pane.Markdown("# Document Reviews"),
                pn.pane.Markdown(f"*Error formatting review cycle data: {str(df_error)}*"),
                sizing_mode='stretch_width'
            )
        
        # Format dates - no need to use pd.to_datetime since we already formatted them
        date_columns = ['start_date', 'startDate', 'due_date', 'dueDate', 'completed_date', 'completionDate']
        for col in date_columns:
            if col in reviews_df.columns:
                # Instead of using pd.to_datetime, ensure the column contains strings
                reviews_df[col] = reviews_df[col].astype(str)
        
        # Select and rename columns for display
        display_columns = ['UID', 'cycle_number', 'status', 'initiated_by_name', 'startDate', 'dueDate', 'completionDate']
        column_names = {
            'cycle_number': 'Cycle #',
            'status': 'Status',
            'initiated_by_name': 'Initiated By',
            'startDate': 'Started',
            'dueDate': 'Due',
            'completionDate': 'Completed'
        }
        
        # Filter columns that exist in the DataFrame
        exist_columns = [col for col in display_columns if col in reviews_df.columns]
        reviews_df = reviews_df[exist_columns]
        
        # Rename columns
        rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
        reviews_df = reviews_df.rename(columns=rename_dict)
        
        # Add action column
        reviews_df['Action'] = 'View'
        
        # Create reviews table
        reviews_table = Tabulator(
            reviews_df,
            pagination='local',
            page_size=5,
            sizing_mode='stretch_width',
            selectable=1,
            height=300
        )
        
        # Add review selection handler
        reviews_table.on_click(self._review_selected)
        
        # Create review details area
        review_details_area = Column(
            Markdown("## Review Details"),
            Markdown("*Select a review cycle to see details*"),
            sizing_mode='stretch_width',
            styles={'background':'#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        
        # Create start review button if appropriate
        buttons = []
        if self.document.get('status') == 'DRAFT' and permissions.user_has_permission(self.user, "INITIATE_REVIEW"):
            start_review_btn = Button(
                name="Start New Review Cycle", 
                button_type="primary", 
                width=180
            )
            start_review_btn.on_click(self._show_review_form)
            buttons.append(start_review_btn)
        
        # Layout
        reviews_layout = Column(
            Markdown("# Document Reviews"),
            Row(*buttons, sizing_mode='stretch_width', align='end') if buttons else None,
            reviews_table,
            review_details_area,
            sizing_mode='stretch_width'
        )
        
        return reviews_layout
    
    def _review_selected(self, event):
        """Handle review selection from table with support for both selection and cell click events"""
        try:
            # Handle different event types
            row_index = None
            row_data = None
            
            # Check if this is a CellClickEvent (from clicking on a table cell)
            if hasattr(event, 'row') and event.row is not None:
                # This is a CellClickEvent
                row_index = event.row
                
                # For CellClickEvent, extract data from event.model.source
                if hasattr(event, 'model') and hasattr(event.model, 'source') and hasattr(event.model.source, 'data'):
                    source_data = event.model.source.data
                    
                    # Create a dictionary with column name -> value for this row
                    row_data = {col: values[row_index] for col, values in source_data.items() if len(values) > row_index}
                    
                    # Look for UID in source data
                    for col in source_data.keys():
                        if col in ['UID', 'uid', '_uid'] and len(source_data[col]) > row_index:
                            review_uid = source_data[col][row_index]
                            logger.debug(f"Found review UID from cell click: {review_uid}")
                            break
            
            # Handle TabSelector selection event (event.new)
            elif hasattr(event, 'new') and event.new:
                row_index = event.new[0] if isinstance(event.new, list) else event.new
                
                # Get data from the DataFrame for TabSelector events
                if hasattr(event, 'obj') and hasattr(event.obj, 'value'):
                    df = event.obj.value
                    
                    if row_index < len(df):
                        row_data = df.iloc[row_index].to_dict()
                        
                        # Get the review UID - check different possible column names
                        for col in ['UID', 'uid', '_uid']:
                            if col in df.columns:
                                review_uid = df.iloc[row_index][col]
                                logger.debug(f"Found review UID from selection: {review_uid}")
                                break
            
            # If we couldn't get row index, exit
            if row_index is None:
                logger.warning("No row index found in review selection event")
                return
            
            # If we don't have review_uid yet, try to extract it from row_data
            if not locals().get('review_uid') and row_data:
                for key in ['UID', 'uid', '_uid']:
                    if key in row_data:
                        review_uid = row_data[key]
                        logger.debug(f"Found review UID from row data with key {key}: {review_uid}")
                        break
            
            # Exit if we still couldn't find the review UID
            if not locals().get('review_uid'):
                self.notification_area.object = "**Error:** Could not determine review UID"
                return
            
            # Store for later access
            self._selected_review_uid = locals().get('review_uid')
            
            # Get detailed review data from backend
            from CDocs.controllers.review_controller import get_review_cycle
        
            review_data = get_review_cycle(
                review_uid=self._selected_review_uid,
                include_comments=True,
                include_document=True
            )
            
            if not review_data:
                self.notification_area.object = "**Error:** Could not retrieve review details"
                return

            # Recursively convert all Neo4j DateTime objects to Python datetime objects
            review_data = self._convert_neo4j_datetimes(review_data)
                
            # Rest of the method remains the same...
            # Extract reviewer assignments
            reviewer_assignments = review_data.get('reviewer_assignments', [])
            
            # Create DataFrame for reviewers
            reviewer_data = []
            for assignment in reviewer_assignments:
                status = assignment.get('status', '')
                decision = assignment.get('decision', '')
                
                reviewer_data.append({
                    'reviewer_name': assignment.get('reviewer_name', ''),
                    'role': assignment.get('role', ''),
                    'status': status,
                    'decision': decision if status == 'COMPLETED' else '',
                    'assigned_date': self._format_date(assignment.get('assigned_date')),
                    'decision_date': self._format_date(assignment.get('decision_date')) if status == 'COMPLETED' else ''
                })
                
            reviewers_df = pd.DataFrame(reviewer_data)
            
            # Create reviewers table
            reviewer_table = pn.widgets.Tabulator(
                reviewers_df,
                pagination='local',
                page_size=10,
                sizing_mode='stretch_width',
                height=200
            )
            
            # Extract review comments
            comments = review_data.get('comments', [])
            
            # Create review details
            status = review_data.get('status', '')
            status_color = '#28a745' if status == 'COMPLETED' else '#ffc107' if status == 'IN_PROGRESS' else '#dc3545' if status == 'CANCELED' else '#6c757d'
            
            # Get the details of the review cycle
            started_date = self._format_date(review_data.get('startDate', ''))
            due_date = self._format_date(review_data.get('dueDate', ''))
            completed_date = self._format_date(review_data.get('completionDate', ''))
            initiated_by = review_data.get('initiated_by_name', 'Unknown')
            review_type = review_data.get('review_type', 'STANDARD')
            sequential = review_data.get('sequential', False)
            instructions = review_data.get('instructions', '')
            on_version = review_data.get('on_version', 'Unknown')
            
            # Create content sections
            review_header = pn.Column(
                pn.pane.Markdown(f"## Review Cycle Details"),
                pn.pane.Markdown(f"**Version:** {on_version}"),
                pn.pane.Markdown(f"**Status:** <span style='color:{status_color};font-weight:bold;'>{status}</span>"),
                pn.pane.Markdown(f"**Started:** {started_date}"),
                pn.pane.Markdown(f"**Due Date:** {due_date}"),
                pn.pane.Markdown(f"**Completed:** {completed_date if completed_date else 'Not completed'}"),
                pn.pane.Markdown(f"**Initiated By:** {initiated_by}"),
                pn.pane.Markdown(f"**Review Type:** {review_type}"),
                pn.pane.Markdown(f"**Review Mode:** {'Sequential' if sequential else 'Parallel'}"),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded']
            )
            
            # Instructions section if they exist
            instructions_section = None
            if instructions:
                instructions_section = pn.Column(
                    pn.pane.Markdown("### Instructions"),
                    pn.pane.Markdown(instructions),
                    styles={'background':'#f8f9fa'},
                    css_classes=['p-3', 'border', 'rounded', 'mt-3']
                )
            
            # Reviewers section
            reviewers_section = pn.Column(
                pn.pane.Markdown("### Reviewers"),
                reviewer_table,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'mt-3']
            )
            
            # Comments section if there are comments
            comments_section = None
            if comments:
                comments_md = ["### Comments"]
                for comment in comments:
                    user_name = comment.get('user_name', 'Unknown')
                    timestamp = self._format_date(comment.get('timestamp', ''))
                    text = comment.get('text', '')
                    comments_md.append(f"**{user_name}** ({timestamp}):<br>{text}<hr>")
                
                comments_section = pn.Column(
                    *[pn.pane.Markdown(md) for md in comments_md],
                    styles={'background':'#f8f9fa'},
                    css_classes=['p-3', 'border', 'rounded', 'mt-3']
                )
            
            # Action buttons section
            action_buttons = []
            
            # Button to extend deadline if user has permission and review is active
            if permissions.user_has_permission(self.user, "MANAGE_REVIEWS") and status in ['PENDING', 'IN_PROGRESS']:
                extend_btn = pn.widgets.Button(
                    name="Extend Deadline",
                    button_type="default",
                    width=150
                )
                extend_btn.on_click(lambda event: self._show_extend_review_deadline_form(self._selected_review_uid))
                action_buttons.append(extend_btn)
            
            # Button to add reviewer if user has permission and review is active
            if permissions.user_has_permission(self.user, "MANAGE_REVIEWS") and status in ['PENDING', 'IN_PROGRESS']:
                add_reviewer_btn = pn.widgets.Button(
                    name="Add Reviewer",
                    button_type="default",
                    width=150
                )
                add_reviewer_btn.on_click(lambda event: self._show_add_reviewer_form(self._selected_review_uid))
                action_buttons.append(add_reviewer_btn)
            
            # Button to cancel review if user has permission and review is active
            if permissions.user_has_permission(self.user, "MANAGE_REVIEWS") and status in ['PENDING', 'IN_PROGRESS']:
                cancel_btn = pn.widgets.Button(
                    name="Cancel Review",
                    button_type="danger",
                    width=150
                )
                cancel_btn.on_click(lambda event: self._show_cancel_review_form(self._selected_review_uid))
                action_buttons.append(cancel_btn)
            
            # Create action buttons row if any buttons
            actions_section = None
            if action_buttons:
                actions_section = pn.Row(
                    *action_buttons,
                    styles={'background':'#f8f9fa'},
                    css_classes=['p-3', 'border', 'rounded', 'mt-3']
                )
            
            # Compile all sections that exist
            sections = [review_header, reviewers_section]
            if instructions_section:
                sections.append(instructions_section)
            if comments_section:
                sections.append(comments_section)
            if actions_section:
                sections.append(actions_section)
            
            # Create final layout
            review_details = pn.Column(
                *sections,
                sizing_mode='stretch_width'
            )
            
            # Find and update the review details area safely
            reviews_tab_found = False
            
            # Check if self.tabs is iterable before attempting to iterate
            if hasattr(self.tabs, '__iter__'):
                for item in self.tabs:
                    # Check if item is a tuple or list with at least 2 elements
                    if isinstance(item, (tuple, list)) and len(item) >= 2:
                        tab_name, tab_content = item[0], item[1]
                        
                        if tab_name == "Reviews":
                            reviews_tab_found = True
                            
                            # Found the Reviews tab
                            if isinstance(tab_content, pn.Column):
                                # Look for the review details area - it's typically the last item
                                for j, component in enumerate(tab_content):
                                    if (isinstance(component, pn.Column) and 
                                        len(component) > 0 and 
                                        isinstance(component[0], pn.pane.Markdown) and
                                        "Review Details" in component[0].object):
                                        # Found the review details area, replace it with our new content
                                        tab_content[j] = review_details
                                        return
                                
                                # If we didn't find a specific review details section,
                                # just append the new details to the column
                                tab_content.append(review_details)
                                return
            
            # If we couldn't find the reviews tab through iteration,
            # try directly accessing using the index if it's a Panel Tabs object
            if not reviews_tab_found and hasattr(self.tabs, '__getitem__'):
                try:
                    # Panel's Tabs typically use integer indices
                    review_tab_index = None
                    
                    # Try to find the Reviews tab index
                    for i, name in enumerate(self.tabs._names):
                        if name == "Reviews":
                            review_tab_index = i
                            break
                    
                    if review_tab_index is not None:
                        tab_content = self.tabs[review_tab_index]
                        
                        if isinstance(tab_content, pn.Column):
                            # Look for review details area at the end
                            if len(tab_content) > 0:
                                last_item = tab_content[-1]
                                if (isinstance(last_item, pn.Column) and 
                                    len(last_item) > 0 and 
                                    isinstance(last_item[0], pn.pane.Markdown) and
                                    "Review Details" in last_item[0].object):
                                    # Replace last item
                                    tab_content[-1] = review_details
                                    return
                            
                            # Append if not found
                            tab_content.append(review_details)
                            return
                except Exception as tab_err:
                    logger.error(f"Error accessing tabs by index: {tab_err}")
            
            # As a last resort, just show the details in the notification area
            review_details_str = "**Review details:**\n\n"
            review_details_str += f"**Status:** {review_data.get('status', 'Unknown')}\n"
            review_details_str += f"**Started:** {self._format_date(review_data.get('start_date', ''))}\n"
            review_details_str += f"**Due Date:** {self._format_date(review_data.get('due_date', ''))}\n"
            review_details_str += f"**Reviewers:** {len(review_data.get('reviewers', []))}\n"
            
            # Update notification area with string instead of Column object
            # Update notification area with string
            self.notification_area.object = review_details_str
        
            # Create a safe shallow copy of review_details for the container
            # This avoids potential circular references
            review_details_md = pn.pane.Markdown("# Review Details")
            
            # Then create a new Column in a separate row of main_content
            # Use only components that we're sure can be serialized
            review_details_container = pn.Column(
                review_details_md,
                *[pn.pane.Markdown(f"**{section}:** {value}") for section, value in {
                    "Status": review_data.get('status', 'Unknown'),
                    "Started": self._format_date(review_data.get('startDate', '')),
                    "Due Date": self._format_date(review_data.get('dueDate', '')),
                    "Instructions": review_data.get('instructions', 'None provided')
                }.items()],
                sizing_mode='stretch_width'
            )
            
            # Clear main_content and add both notification and simplified details container
            self.main_content.clear()
            self.main_content.append(self.notification_area)
            self.main_content.append(review_details_container)
            
        except Exception as e:
            logger.error(f"Error selecting review: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"

    def _show_extend_review_deadline_form(self, review_uid):
        """Show form to extend review deadline"""
        try:
            # Store the review UID for later use
            self._selected_review_uid = review_uid
            # Get current review cycle to show current deadline
            from CDocs.controllers.review_controller import get_review_cycle
            review_data = get_review_cycle(review_uid)
            
            if not review_data:
                self.notification_area.object = "**Error:** Could not retrieve review cycle details"
                return
                
            # Extract current deadline for display
            current_deadline = None
            if 'dueDate' in review_data:
                try:
                    current_deadline = self._convert_neo4j_datetimes(review_data['dueDate'])
                    current_deadline_str = current_deadline.strftime("%Y-%m-%d") if current_deadline else "Not set"
                except Exception:
                    current_deadline_str = str(review_data['dueDate'])
            else:
                current_deadline_str = "Not set"
    
            # Create form elements
            date_picker = pn.widgets.DatePicker(
                name="New Due Date",
                value=None,
                start=datetime.now().date() + timedelta(days=1),  # Must be at least tomorrow
                end=datetime.now().date() + timedelta(days=90),   # Maximum 90 days in future
                width=200
            )
            
            reason_input = pn.widgets.TextAreaInput(
                name="Reason for Extension",
                placeholder="Enter reason for deadline extension",
                rows=3,
                width=300
            )
            
            submit_btn = pn.widgets.Button(
                name="Extend Deadline",
                button_type="primary",
                width=150
            )
            
            cancel_btn = pn.widgets.Button(
                name="Cancel",
                button_type="default",
                width=100
            )
            
            # Create form container
            form = pn.Column(
                pn.pane.Markdown("## Extend Review Deadline"),
                pn.pane.Markdown(f"Current deadline: **{current_deadline_str}**"),
                pn.Row(date_picker, sizing_mode='stretch_width'),
                pn.Row(reason_input, sizing_mode='stretch_width'),
                pn.Row(
                    pn.layout.HSpacer(),
                    cancel_btn,
                    submit_btn,
                    sizing_mode='stretch_width'
                ),
                width=400,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'shadow']
            )
            
            # Define submission handler
            def submit_extension(event):
                # Validate inputs
                if not date_picker.value:
                    self.notification_area.object = "**Error:** Please select a new deadline date"
                    return
                    
                # Convert to datetime with end of day
                new_deadline = datetime.combine(date_picker.value, datetime.max.time())
                
                # Call controller function
                from CDocs.controllers.review_controller import extend_review_deadline
                try:
                    result = extend_review_deadline(
                        user=self.user,
                        review_uid=review_uid,
                        new_due_date=new_deadline,
                        reason=reason_input.value
                    )
                    
                    if result.get('success', False):
                        self.notification_area.object = "**Success:** Review deadline extended"
                        # Close form and refresh review details
                        # Close form and refresh document details
                        self._load_document()
                    else:
                        self.notification_area.object = f"**Error:** {result.get('message', 'Unknown error')}"
                except Exception as e:
                    self.notification_area.object = f"**Error:** {str(e)}"
            
            # Define cancel handler
            def cancel_action(event):
                self.notification_area.object = "Deadline extension canceled"
                # Remove the form from view
                if hasattr(self, 'main_content'):
                    for i, item in enumerate(self.main_content):
                        if item is form:
                            self.main_content.pop(i)
                            break
            
            # Add handlers
            submit_btn.on_click(submit_extension)
            cancel_btn.on_click(cancel_action)
            
            # Clear display area and show form
            # FIX: Check if template exists, otherwise use main_content
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(form)
                
        except Exception as e:
            logger.error(f"Error showing extend deadline form: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _show_add_reviewer_form(self, review_uid):
        """Show form to add a reviewer to an active review cycle"""
        try:
            # Store the review UID for later use
            self._selected_review_uid = review_uid
            # Get current review cycle
            from CDocs.controllers.review_controller import get_review_cycle
            review_data = get_review_cycle(review_uid)
            
            if not review_data:
                self.notification_area.object = "**Error:** Could not retrieve review cycle details"
                return
                
            # Get potential reviewers (exclude current reviewers)
            from CDocs.models.user_extensions import DocUser
            try:
                potential_reviewers = DocUser.get_users_by_role(role="REVIEWER")
                

                # Get current reviewer UIDs to exclude
                current_reviewer_uids = set()
                for reviewer in review_data.get('reviewers', []):
                    current_reviewer_uids.add(reviewer.get('UID'))
                
                # Filter out current reviewers
                potential_reviewers = [u for u in potential_reviewers if u.uid not in current_reviewer_uids]
                
                # Create options dictionary for select widget
                user_options = {f"{u.name} ({u.username})" : u.uid for u in potential_reviewers}
                
                # If no potential reviewers, show message
                if not user_options:
                    self.notification_area.object = "**Info:** No additional reviewers available"
                    return
                    
            except Exception as users_err:
                # Fallback - get all users
                logger.error(f"Error getting potential reviewers: {users_err}")
                user_options = {"user1": "User 1", "user2": "User 2"}  # Placeholder
            
            # Create form elements
            reviewer_select = pn.widgets.Select(
                name="Select Reviewer",
                options=user_options,
                width=300
            )
            
            # For sequential reviews, add sequence selector
            sequence_input = None
            if review_data.get('sequential', False):
                sequence_input = pn.widgets.IntInput(
                    name="Review Sequence Order",
                    value=len(review_data.get('reviewers', [])) + 1,  # Default to next in sequence
                    start=1,
                    width=150
                )
            
            submit_btn = pn.widgets.Button(
                name="Add Reviewer",
                button_type="primary",
                width=150
            )
            
            cancel_btn = pn.widgets.Button(
                name="Cancel",
                button_type="default",
                width=100
            )
            
            # Create form container
            form_components = [
                pn.pane.Markdown("## Add Reviewer to Review Cycle"),
                pn.pane.Markdown(f"Review: {review_data.get('status', 'Unknown')}"),
                pn.Row(reviewer_select, sizing_mode='stretch_width')
            ]
            
            if sequence_input:
                form_components.append(pn.Row(sequence_input, sizing_mode='stretch_width'))
            
            form_components.append(
                pn.Row(
                    pn.layout.HSpacer(),
                    cancel_btn,
                    submit_btn,
                    sizing_mode='stretch_width'
                )
            )
            
            form = pn.Column(
                *form_components,
                width=400,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'shadow']
            )
            
            # Define submission handler
            def submit_add_reviewer(event):
                # Validate inputs
                if not reviewer_select.value:
                    self.notification_area.object = "**Error:** Please select a reviewer"
                    return
                    
                # Call controller function
                from CDocs.controllers.review_controller import add_reviewer_to_active_review
                try:
                    result = add_reviewer_to_active_review(
                        user=self.user,
                        review_uid=review_uid,
                        reviewer_uid=reviewer_select.value,
                        sequence_order=sequence_input.value if sequence_input else None
                    )
                    
                    if result.get('success', False):
                        self.notification_area.object = "**Success:** Reviewer added to review cycle"
                        # Close form and refresh review details
                        # Close form and refresh document details
                        self._load_document()
                    else:
                        self.notification_area.object = f"**Error:** {result.get('message', 'Unknown error')}"
                except Exception as e:
                    self.notification_area.object = f"**Error:** {str(e)}"
            
            # Define cancel handler
            def cancel_action(event):
                self.notification_area.object = "Reviewer addition canceled"
                # Remove the form from view
                if hasattr(self, 'main_content'):
                    for i, item in enumerate(self.main_content):
                        if item is form:
                            self.main_content.pop(i)
                            break
            
            # Add handlers
            submit_btn.on_click(submit_add_reviewer)
            cancel_btn.on_click(cancel_action)
            
            # Clear display area and show form
            # FIX: Check if template exists, otherwise use main_content
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(form)
                
        except Exception as e:
            logger.error(f"Error showing add reviewer form: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _show_cancel_review_form(self, review_uid):
        """Show form to cancel an active review cycle"""
        try:
            # Store the review UID for later use
            self._selected_review_uid = review_uid
            # Get current review cycle
            from CDocs.controllers.review_controller import get_review_cycle
            review_data = get_review_cycle(review_uid, include_document=True)
            
            if not review_data:
                self.notification_area.object = "**Error:** Could not retrieve review cycle details"
                return
                
            # Create form elements
            reason_input = pn.widgets.TextAreaInput(
                name="Reason for Cancellation",
                placeholder="Enter reason for canceling the review cycle",
                rows=3,
                width=300
            )
            
            confirm_checkbox = pn.widgets.Checkbox(
                name="I understand this action cannot be undone",
                value=False
            )
            
            submit_btn = pn.widgets.Button(
                name="Cancel Review",
                button_type="danger",
                width=150,
                disabled=True  # Disabled until checkbox is checked
            )
            
            cancel_btn = pn.widgets.Button(
                name="Go Back",
                button_type="default",
                width=100
            )
            
            # Create warning with document info
            document_info = review_data.get('document', {})
            doc_number = document_info.get('doc_number', 'Unknown')
            doc_title = document_info.get('title', 'Unknown')
            
            warning_md = pn.pane.Markdown(f"""
            ## ⚠️ Cancel Review Cycle
            
            **Warning:** You are about to cancel the active review cycle for document:
            
            **{doc_number}**: {doc_title}
            
            This action cannot be undone. All reviewer assignments and comments will remain,
            but the review cycle will be marked as canceled.
            """)
            
            # Create form container
            form = pn.Column(
                warning_md,
                pn.Row(reason_input, sizing_mode='stretch_width'),
                pn.Row(confirm_checkbox, sizing_mode='stretch_width'),
                pn.Row(
                    pn.layout.HSpacer(),
                    cancel_btn,
                    submit_btn,
                    sizing_mode='stretch_width'
                ),
                width=500,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'shadow']
            )
            
            # Enable/disable submit button based on checkbox
            def update_submit_btn(event):
                submit_btn.disabled = not confirm_checkbox.value
            
            confirm_checkbox.param.watch(update_submit_btn, 'value')
            
            # Define submission handler
            def submit_cancel_review(event):
                # Validate inputs
                if not reason_input.value or len(reason_input.value.strip()) < 10:
                    self.notification_area.object = "**Error:** Please provide a detailed reason for cancellation"
                    return
                    
                if not confirm_checkbox.value:
                    self.notification_area.object = "**Error:** You must confirm the action"
                    return
                    
                # Call controller function
                from CDocs.controllers.review_controller import cancel_review_cycle
                try:
                    result = cancel_review_cycle(
                        user=self.user,
                        review_uid=review_uid,
                        reason=reason_input.value
                    )
                    
                    if result.get('success', False):
                        self.notification_area.object = "**Success:** Review cycle canceled successfully"
                        # Close form and refresh document details
                        self._load_document()
                    else:
                        self.notification_area.object = f"**Error:** {result.get('message', 'Unknown error')}"
                except Exception as e:
                    self.notification_area.object = f"**Error:** {str(e)}"
            
            # Define cancel handler
            def cancel_action(event):
                self.notification_area.object = "Cancellation aborted"
                # Remove the form from view
                if hasattr(self, 'main_content'):
                    for i, item in enumerate(self.main_content):
                        if item is form:
                            self.main_content.pop(i)
                            break
            
            # Add handlers
            submit_btn.on_click(submit_cancel_review)
            cancel_btn.on_click(cancel_action)
            
            # Clear display area and show form
            # FIX: Check if template exists, otherwise use main_content
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(form)
                
        except Exception as e:
            logger.error(f"Error showing cancel review form: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _load_review_details_by_uid(self, review_uid):
        """Helper method to refresh review details after an action"""
        try:
            # Just re-trigger the review selection with the same review UID
            # This will fetch fresh data and update the UI
            class DummyEvent:
                pass
                
            dummy_event = DummyEvent()
            dummy_event.new = [0]  # Simulate first row selection
            
            # Store review UID so _review_selected can find it
            self._selected_review_uid = review_uid
            
            # Re-trigger the review selection handler
            self._review_selected(dummy_event)
            
        except Exception as e:
            logger.error(f"Error refreshing review details: {e}")
            self.notification_area.object = f"**Error refreshing review details:** {str(e)}"

    def _convert_neo4j_datetimes(self, data):
        """
        Recursively convert all Neo4j DateTime objects to Python datetime objects or strings.
        
        Args:
            data: Any data structure potentially containing Neo4j DateTime objects
            
        Returns:
            Same data structure with Neo4j DateTime objects converted to Python datetime
        """
        if data is None:
            return None
            
        # Handle Neo4j DateTime objects
        if hasattr(data, '__class__') and data.__class__.__name__ == 'DateTime':
            try:
                # Try to convert to Python datetime
                import datetime
                py_datetime = datetime.datetime(
                    year=data.year, 
                    month=data.month, 
                    day=data.day,
                    hour=data.hour, 
                    minute=data.minute, 
                    second=data.second,
                    microsecond=data.nanosecond // 1000
                )
                return py_datetime
            except (AttributeError, ValueError):
                # If conversion fails, return as string
                return str(data)
                
        # Handle dictionaries
        elif isinstance(data, dict):
            return {k: self._convert_neo4j_datetimes(v) for k, v in data.items()}
            
        # Handle lists
        elif isinstance(data, list):
            return [self._convert_neo4j_datetimes(item) for item in data]
            
        # Handle tuples
        elif isinstance(data, tuple):
            return tuple(self._convert_neo4j_datetimes(item) for item in data)
            
        # Return other types unchanged
        return data
    
    def _create_approvals_tab(self):
        """Create the approvals tab content with support for the new approval schema"""
        # Main container
        approvals_tab = pn.Column(sizing_mode='stretch_width')
        approvals_tab.append(pn.pane.Markdown("## Document Approvals"))
        
        try:
            # Get approvals for this document
            from CDocs.controllers.approval_controller import get_document_approvals
            result = get_document_approvals(self.document_uid)
            
            if not result or 'approvals' not in result or not result['approvals']:
                approvals_tab.append(pn.pane.Markdown("*No approvals found for this document*"))
                return approvals_tab
            
            approvals = result['approvals']
            
            # Sort approvals by date, newest first
            approvals.sort(key=lambda x: x.get('initiated_date', ''), reverse=True)
            
            # Create approval cards
            for approval in approvals:
                # Extract data with fallbacks
                status = approval.get('status', 'UNKNOWN')
                workflow_type = approval.get('workflow_type', 'Standard')
                initiated_date = self._format_date(approval.get('initiated_date'))
                due_date = self._format_date(approval.get('due_date'))
                completion_date = self._format_date(approval.get('completion_date', ''))
                
                # Get approval steps
                steps = approval.get('steps', [])
                
                # Calculate completion stats
                steps_completed = sum(1 for step in steps if step.get('status') == 'COMPLETED')
                steps_total = len(steps)
                steps_percentage = int((steps_completed / steps_total) * 100) if steps_total > 0 else 0
                
                # Create approval card header
                header = pn.Row(
                    pn.pane.Markdown(f"### {workflow_type} Approval"),
                    pn.pane.Markdown(f"<span style='color:{self._get_status_color(status)};'>Status: {status}</span>", sizing_mode='stretch_width'),
                    pn.layout.HSpacer(),
                    pn.widgets.Button(name="View Details", button_type="primary", width=120, 
                                     on_click=lambda e, uid=approval.get('UID'): self._view_approval_details(uid)),
                    sizing_mode='stretch_width'
                )
                
                # Create approval card body
                body = pn.Column(
                    pn.pane.Markdown(f"**Initiated:** {initiated_date}"),
                    pn.pane.Markdown(f"**Due Date:** {due_date}"),
                    pn.pane.Markdown(f"**Completion Date:** {completion_date if completion_date else 'Not completed'}"),
                    pn.pane.Markdown(f"**Progress:** {steps_completed} of {steps_total} steps completed ({steps_percentage}%)"),
                    sizing_mode='stretch_width'
                )
                
                # Create step progress section
                steps_section = pn.Column(
                    pn.pane.Markdown("#### Approval Steps"),
                    sizing_mode='stretch_width'
                )
                
                for i, step in enumerate(steps, 1):
                    step_status = step.get('status', 'PENDING')
                    step_card = self._create_step_card(step, i)
                    steps_section.append(step_card)
                
                # Create the complete card
                card = pn.Column(
                    header,
                    pn.layout.Divider(),
                    body,
                    pn.layout.Divider(),
                    steps_section,
                    styles={'background': '#f8f9fa'},
                    css_classes=['p-3', 'border', 'rounded', 'mb-4'],
                    sizing_mode='stretch_width'
                )
                
                approvals_tab.append(card)
                
        except Exception as e:
            logger.error(f"Error creating approvals tab: {e}")
            logger.error(traceback.format_exc())
            approvals_tab.append(pn.pane.Markdown(f"**Error loading approvals:** {str(e)}"))
        
        return approvals_tab
    
    def _create_step_card(self, step, step_number):
        """Create a card for an approval step"""
        # Extract data
        status = step.get('status', 'PENDING')
        step_type = step.get('step_type', 'Standard')
        completion_date = self._format_date(step.get('completion_date', ''))
        
        # Get approvers
        approvers = step.get('approvers', [])
        
        # Create the step card
        step_card = pn.Column(
            pn.Row(
                pn.pane.Markdown(f"**Step {step_number}:** {step_type}"),
                pn.layout.HSpacer(),
                pn.pane.Markdown(f"<span style='color:{self._get_status_color(status)};'>Status: {status}</span>"),
                sizing_mode='stretch_width'
            ),
            sizing_mode='stretch_width',
            styles={'background': '#ffffff'},
            css_classes=['p-2', 'border', 'rounded', 'mb-2']
        )
        
        # Add approvers information
        approvers_list = pn.Column(sizing_mode='stretch_width')
        
        for approver in approvers:
            approver_name = approver.get('approver_name', 'Unknown')
            approver_status = approver.get('status', 'PENDING')
            decision = approver.get('decision', '')
            decision_date = self._format_date(approver.get('decision_date', ''))
            
            approver_item = pn.Row(
                pn.pane.Markdown(f"**{approver_name}**"),
                pn.layout.HSpacer(),
                pn.pane.Markdown(f"<span style='color:{self._get_status_color(approver_status)};'>{approver_status}</span>"),
                pn.pane.Markdown(f"{decision + ' on ' + decision_date if decision and decision_date else ''}"),
                sizing_mode='stretch_width'
            )
            
            approvers_list.append(approver_item)
        
        step_card.append(approvers_list)
        return step_card
    
    def _get_status_color(self, status):
        """Get color for a status value"""
        # Default colors for various statuses
        status_colors = {
            'PENDING': '#6c757d',    # Gray
            'IN_PROGRESS': '#ffc107', # Yellow
            'COMPLETED': '#28a745',  # Green
            'APPROVED': '#28a745',   # Green
            'REJECTED': '#dc3545',   # Red
            'CANCELED': '#6c757d'    # Gray
        }
        
        # Look up in settings if available
        if hasattr(settings, 'APPROVAL_STATUS_CONFIG'):
            status_config = getattr(settings, 'APPROVAL_STATUS_CONFIG', {}).get(status, {})
            if status_config and 'color' in status_config:
                return status_config['color']
        
        # Fallback to our local mapping
        return status_colors.get(status, '#6c757d')  # Default gray if not found
    
    def _view_approval_details(self, approval_uid):
        """View detailed information about an approval workflow"""
        try:
            # Get the full approval panel from approval_panel module
            from CDocs.ui.approval_panel import create_approval_panel
            
            # Create an embedded version of the panel
            approval_panel = create_approval_panel(
                session_manager=self.session_manager,
                parent_app=self.parent_app,
                embedded=True
            )
            
            # Set user
            if self.user:
                approval_panel.set_user(self.user)
            
            # Load the specific approval
            approval_panel._load_approval(approval_uid)
            
            # Show in main content
            self.main_content.clear()
            self.main_content.append(pn.Row(
                pn.widgets.Button(
                    name='← Back',
                    button_type='default',
                    width=100,
                    on_click=lambda e: self._load_document()
                ),
                sizing_mode='stretch_width'
            ))
            self.main_content.append(approval_panel.get_approval_view())
            
        except Exception as e:
            logger.error(f"Error viewing approval details: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"❌ Error loading approval details: {str(e)}"
    
    def _create_audit_tab(self):
        """Create the audit trail tab content"""
        # Get audit trail from document
        audit_trail = []
        
        try:
            # Import the utilities for audit trail
            from CDocs.utils.audit_trail import get_document_history
            
            # Fetch document history/audit trail
            audit_trail = get_document_history(self.document_uid)
            
            # If no audit trail events were found, check if it's in the document object
            if not audit_trail and self.document and isinstance(self.document, dict):
                audit_trail = self.document.get('audit_trail', [])
            
            logger.debug(f"Fetched {len(audit_trail)} audit trail events")
        except Exception as e:
            logger.error(f"Error fetching audit trail: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return Column(
                Markdown("# Document Audit Trail"),
                Markdown(f"**Error loading audit trail:** {str(e)}"),
                sizing_mode='stretch_width'
            )
        
        if not audit_trail:
            return Column(
                Markdown("# Document Audit Trail"),
                Markdown("*No audit trail events found for this document*"),
                sizing_mode='stretch_width'
            )
        
        # Ensure audit_trail is a list of dictionaries
        if not isinstance(audit_trail, list):
            audit_trail = [audit_trail] if audit_trail else []
            
        # Convert to DataFrame for tabulator
        try:
            # Create a clean list of dictionaries for the DataFrame
            audit_data = []
            
            for event in audit_trail:
                if isinstance(event, dict):
                    # Ensure timestamp exists and is properly formatted
                    timestamp = event.get('timestamp', '')
                    if timestamp:
                        # Try to parse the timestamp to ensure it's valid
                        try:
                            if isinstance(timestamp, datetime):
                                formatted_time = timestamp.strftime('%Y-%m-%d %H:%M')
                            else:
                                # Try to parse it as ISO format
                                dt = pd.to_datetime(timestamp)
                                formatted_time = dt.strftime('%Y-%m-%d %H:%M')
                        except:
                            formatted_time = str(timestamp)  # Fallback to string representation
                    else:
                        formatted_time = ''
                    
                    # Extract key information with pre-formatted timestamp
                    event_data = {
                        'timestamp': formatted_time,  # Use pre-formatted timestamp
                        'eventType': event.get('eventType', event.get('event_type', 'Unknown')),
                        'userName': event.get('userName', event.get('user_name', '')),
                        'description': event.get('description', ''),
                        'details': str(event.get('details', ''))
                    }
                    audit_data.append(event_data)
            
            # Create DataFrame
            if audit_data:
                audit_df = pd.DataFrame(audit_data)
                
                # No need for timestamp conversion as we pre-formatted the timestamps
                
                # Select and rename columns for display
                display_columns = ['timestamp', 'eventType', 'userName', 'description', 'details']
                column_names = {
                    'timestamp': 'Time',
                    'eventType': 'Event Type',
                    'userName': 'User',
                    'description': 'Description',
                    'details': 'Details'
                }
                
                # Filter columns that exist in the DataFrame
                exist_columns = [col for col in display_columns if col in audit_df.columns]
                audit_df = audit_df[exist_columns]
                
                # Rename columns
                rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
                audit_df = audit_df.rename(columns=rename_dict)
                
                # Sort by timestamp if it exists
                if 'Time' in audit_df.columns:
                    audit_df = audit_df.sort_values('Time', ascending=False)
                
                # Create audit table
                audit_table = Tabulator(
                    audit_df,
                    pagination='local',
                    page_size=20,
                    sizing_mode='stretch_width',
                    height=600,
                    show_index=False
                )
                
                # Layout
                audit_layout = Column(
                    Markdown("# Document Audit Trail"),
                    Markdown("The table below shows the complete history of actions performed on this document."),
                    audit_table,
                    sizing_mode='stretch_width'
                )
                
                return audit_layout
            else:
                return Column(
                    Markdown("# Document Audit Trail"),
                    Markdown("*No valid audit trail events found for this document*"),
                    sizing_mode='stretch_width'
                )
        except Exception as df_error:
            logger.error(f"Error creating audit trail DataFrame: {df_error}")
            import traceback
            logger.error(traceback.format_exc())
            
            return Column(
                Markdown("# Document Audit Trail"),
                Markdown(f"**Error formatting audit trail data:** {str(df_error)}"),
                sizing_mode='stretch_width'
            )
    
    def _create_document_viewer(self):
        """Create a document viewer for the current version"""
        if not self.current_version:
            return Markdown("*No document version available*")
        
        # Document type
        file_name = self.current_version.get('file_name', '')
        file_type = file_name.split('.')[-1].lower() if '.' in file_name else ''
        
        # For PDF, use iframe
        if file_type == 'pdf':
            # Get document content
            try:
                version_uid = self.current_version.get('UID')
                doc_content = download_document_version(
                        user=self.user, 
                        document_uid=self.document_uid, 
                        version_uid=version_uid
                    )
                
                if (doc_content and 'content' in doc_content) or isinstance(doc_content, bytes):
                    # Convert content to base64
                    content_b64 = base64.b64encode(doc_content['content'] if 'content' in doc_content else doc_content).decode('utf-8')
                    
                    # Create data URL
                    data_url = f"data:application/pdf;base64,{content_b64}"
                    
                    # Create iframe HTML
                    iframe_html = f"""
                    <iframe src="{data_url}" width="100%" height="600px" style="border: 1px solid #ddd;"></iframe>
                    """
                    
                    return HTML(iframe_html)
                else:
                    return Markdown("*Error loading document content*")
            except Exception as e:
                logger.error(f"Error creating PDF viewer: {e}")
                return Markdown("*Error creating document viewer*")
        
        # For images
        elif file_type in ['png', 'jpg', 'jpeg', 'gif']:
            try:
                version_uid = self.current_version.get('UID')
                doc_content = download_document_version(
                        user=self.user, 
                        document_uid=self.document_uid, 
                        version_uid=version_uid
                    )
                
                if doc_content and 'content' in doc_content:
                    # Convert content to base64
                    content_b64 = base64.b64encode(doc_content['content']).decode('utf-8')
                    
                    # Create data URL
                    data_url = f"data:image/{file_type};base64,{content_b64}"
                    
                    # Create image HTML
                    img_html = f"""
                    <img src="{data_url}" style="max-width: 100%; max-height: 600px; border: 1px solid #ddd;">
                    """
                    
                    return HTML(img_html)
                else:
                    return Markdown("*Error loading image content*")
            except Exception as e:
                logger.error(f"Error creating image viewer: {e}")
                return Markdown("*Error creating document viewer*")
        
        # For other file types, just show download link
        else:
            download_btn = Button(
                name=f"Download {file_name}", 
                button_type="primary", 
                width=200
            )
            download_btn.on_click(self._download_current_version)
            
            return Column(
                Markdown(f"Document type **{file_type}** cannot be previewed in the browser."),
                download_btn
            )
    
    def _version_selected(self, event):
        """Handle version selection from table"""
        logger = logging.getLogger('CDocs.ui.document_detail')
        
        try:
            # Handle different event types
            row_index = None
            row_data = None
            
            # Debug the event type
            logger.debug(f"Event type: {type(event).__name__}")
            
            # Check if this is a CellClickEvent (from clicking on Action column)
            if hasattr(event, 'row') and event.row is not None:
                logger.debug(f"Cell click event detected for row: {event.row}")
                row_index = event.row
                
                # Store this early so we don't lose it
                if hasattr(event, 'column'):
                    logger.debug(f"Column: {event.column}")
                    
                # For CellClickEvent, extract row data directly from the event model
                if hasattr(event, 'model') and hasattr(event.model, 'source') and hasattr(event.model.source, 'data'):
                    source_data = event.model.source.data
                    logger.debug(f"Source data keys: {list(source_data.keys())}")
                    
                    # Extract the row data directly from source data
                    # Each key in source_data is a column, with a list of values
                    try:
                        # Create a dictionary with column name -> value for this row
                        row_data = {col: values[row_index] for col, values in source_data.items() if len(values) > row_index}
                        logger.debug(f"Extracted row data directly: {row_data}")
                        
                        # LOOK FOR UID SPECIFICALLY
                        # The UID might be in index or hidden columns not directly visible
                        for col, values in source_data.items():
                            if col.lower().endswith('UID') or col == '_uid' or col == 'UID' or col == '__uid':
                                if len(values) > row_index:
                                    logger.debug(f"Found potential UID column '{col}': {values[row_index]}")
                                    # Store this directly in case it's not included in the regular row data
                                    if values[row_index]:  # Only if not empty
                                        self._selected_version_uid = values[row_index]
                                        logger.debug(f"Directly stored version UID: {self._selected_version_uid}")
                        
                    except Exception as extract_err:
                        logger.error(f"Error extracting row data: {extract_err}")
                    
                # Even for CellClickEvent, try to find UID from the actual table
                # This is the part that needs safer handling
                if hasattr(self, 'tabs') and len(self.tabs) > 1:
                    try:
                        versions_tab = self.tabs[1][1]
                        
                        # Check if versions_tab is a container before iterating
                        if hasattr(versions_tab, 'objects'):
                            # Look for Tabulator in the versions tab
                            for obj in versions_tab.objects:
                                if isinstance(obj, pn.widgets.Tabulator):
                                    if hasattr(obj, 'value'):
                                        df = obj.value
                                        logger.debug(f"Found table in versions tab with {len(df)} rows")
                                        
                                        # If we have a valid row index and DataFrame, get the UID
                                        if row_index < len(df):
                                            for uid_col in ['_uid', 'UID', '__uid']:
                                                if uid_col in df.columns:
                                                    self._selected_version_uid = df.iloc[row_index][uid_col]
                                                    logger.debug(f"Found UID in table column '{uid_col}': {self._selected_version_uid}")
                                                    break
                        else:
                            logger.debug(f"Versions tab is not a container: {type(versions_tab).__name__}")
                    except Exception as tab_err:
                        logger.error(f"Error searching for table in versions tab: {tab_err}")
                    
            # Handle TabSelector selection event format (event.new)
            elif hasattr(event, 'new') and event.new:
                row_index = event.new[0] if isinstance(event.new, list) else event.new
                logger.debug(f"Selection event detected for row: {row_index}")
                
                # For regular events, the table is in event.obj
                if hasattr(event, 'obj') and hasattr(event.obj, 'value'):
                    df = event.obj.value
                    logger.debug(f"Using DataFrame from event.obj.value with {len(df)} rows")
                    
                    if row_index < len(df):
                        row_data = df.iloc[row_index].to_dict()
                        logger.debug(f"Row data from DataFrame: {row_data}")
                        
                        # Look for UID column
                        for uid_col in ['_uid', 'UID', '__uid']:
                            if uid_col in df.columns:
                                self._selected_version_uid = df.iloc[row_index][uid_col]
                                logger.debug(f"Found UID in DataFrame column '{uid_col}': {self._selected_version_uid}")
                                break
                
            # Exit if no row index found
            if row_index is None:
                logger.warning("No row index found in event")
                return
                
            # If we still don't have row_data, try to find it
            if row_data is None:
                logger.warning("No row data extracted, searching for DataFrame")
                df = None
                
                # First try to get the DataFrame from the event directly
                if hasattr(event, 'obj') and hasattr(event.obj, 'value'):
                    df = event.obj.value
                    logger.debug(f"Got DataFrame from event.obj.value")
                elif hasattr(event, 'model') and hasattr(event.model, 'data'):
                    # Try to convert model data to DataFrame
                    try:
                        import pandas as pd
                        source_data = event.model.source.data
                        df = pd.DataFrame(source_data)
                        logger.debug(f"Created DataFrame from model.source.data")
                    except Exception as df_err:
                        logger.error(f"Error creating DataFrame from model data: {df_err}")
                
                # If still no DataFrame, find the versions table in the versions tab
                if df is None and hasattr(self, 'tabs') and len(self.tabs) > 1:
                    try:
                        versions_tab = self.tabs[1][1]
                        
                        # Check if versions_tab is a container before iterating
                        if hasattr(versions_tab, 'objects'):
                            # Look for Tabulator in the versions tab objects
                            for obj in versions_tab.objects:
                                if isinstance(obj, pn.widgets.Tabulator):
                                    if hasattr(obj, 'value'):
                                        df = obj.value
                                        logger.debug(f"Found table in versions tab with {len(df)} rows and columns: {df.columns.tolist()}")
                                        break
                        elif isinstance(versions_tab, pn.widgets.Tabulator):
                            # The tab itself might be a Tabulator
                            if hasattr(versions_tab, 'value'):
                                df = versions_tab.value
                                logger.debug(f"Tab itself is a Tabulator with {len(df)} rows")
                        else:
                            logger.debug(f"Versions tab is not a container or Tabulator: {type(versions_tab).__name__}")
                    except Exception as tab_err:
                        logger.error(f"Error searching for table in versions tab: {tab_err}")
                    
                # If we found a DataFrame and the row index is valid, extract row data
                if df is not None and row_index < len(df):
                    row_data = df.iloc[row_index].to_dict()
                    logger.debug(f"Retrieved row data from DataFrame: {row_data}")
                    
                    # Look for UID column again
                    for uid_col in ['_uid', 'UID', '__uid']:
                        if uid_col in df.columns:
                            self._selected_version_uid = df.iloc[row_index][uid_col]
                            logger.debug(f"Found UID in DataFrame column '{uid_col}': {self._selected_version_uid}")
                            break
            
            # Get version UID from row_data if we still don't have it
            if not hasattr(self, '_selected_version_uid') or not self._selected_version_uid:
                if row_data:
                    # Look for UID in the row data with different possible keys
                    uid_keys = ['_uid', 'uid', 'UID', 'version_uid', 'versionUID', '__uid']
                    for key in uid_keys:
                        if key in row_data and row_data[key]:
                            self._selected_version_uid = row_data[key]
                            logger.debug(f"Found UID in row_data with key {key}: {self._selected_version_uid}")
                            break
                    
                    # If still not found, check if any key ends with 'uid'
                    if not hasattr(self, '_selected_version_uid') or not self._selected_version_uid:
                        for key in row_data.keys():
                            if key.lower().endswith('UID'):
                                self._selected_version_uid = row_data[key]
                                logger.debug(f"Found UID in row_data with key ending with 'uid': {self._selected_version_uid}")
                                break
            
            # Exit if no row data found
            if row_data is None:
                logger.error("Could not extract row data from event")
                self.notification_area.object = "**Error:** Could not access version data"
                return
            
            # Extract version information directly from row_data
            logger.debug(f"Final row data keys: {list(row_data.keys())}")
            
            # Extract version UID from row data - try different column names
            if not hasattr(self, '_selected_version_uid') or not self._selected_version_uid:
                version_uid = None
                
                # Try different possible locations for the version UID
                uid_variants = ['_uid', 'uid', 'UID', 'version_uid', 'versionUID', '__uid']
                for key in uid_variants:
                    if key in row_data and row_data[key]:
                        version_uid = row_data[key]
                        break
                        
                # If still not found, check if there's a key ending with 'uid' (case insensitive)
                if not version_uid:
                    for key in row_data.keys():
                        if key.lower().endswith('UID'):
                            version_uid = row_data[key]
                            break
                
                logger.debug(f"Selected version UID: {version_uid}")
                
                # IMPORTANT: Store the version UID in the class instance for action buttons
                if version_uid:
                    self._selected_version_uid = version_uid
                    logger.debug(f"Stored version UID (from row data): {self._selected_version_uid}")
            
            # Final check - do we have a valid UID?
            if hasattr(self, '_selected_version_uid') and self._selected_version_uid:
                logger.debug(f"Final selected version UID: {self._selected_version_uid}")
            else:
                logger.warning("No version UID found after exhaustive search")
                self.notification_area.object = "**Error:** Could not determine version UID"
                return
            
            # Extract other information from row data
            # Handle potential column name variations based on renaming
            version_number = row_data.get('Version', row_data.get('version_number', 'Unknown'))
            created_date = row_data.get('Created', row_data.get('created_date', 'Unknown'))
            file_name = row_data.get('File Name', row_data.get('file_name', 'Unknown'))
            
            # Create download button
            download_btn = Button(
                name="Download Version", 
                button_type="primary", 
                width=150
            )
            
            # Set up click handler for download button
            download_btn.on_click(self._download_selected_version)
            
            # Create "Set as Current" button if user has permission
            set_current_btn = None
            if hasattr(self.user, 'has_permission') and self.user.has_permission("MANAGE_VERSIONS"):
                set_current_btn = Button(
                    name="Set as Current Version", 
                    button_type="success", 
                    width=200
                )
                set_current_btn.on_click(self._set_as_current_version)
            
            # Create content for the version action area
            buttons_row = pn.Row(download_btn)
            if set_current_btn:
                buttons_row.append(set_current_btn)
            
            # Display the version UID in debug mode for verification
            version_info = [
                pn.pane.Markdown(f"## Version {version_number}"),
                pn.pane.Markdown(f"Created: {created_date}"),
                pn.pane.Markdown(f"File: {file_name}")
            ]
            
            # Always add UID for easier debugging but only show in debug mode
            try:
                from CDocs.config import settings
                if getattr(settings, 'DEBUG', False):
                    version_info.append(pn.pane.Markdown(f"UID: {self._selected_version_uid}"))
            except Exception as settings_err:
                logger.error(f"Error accessing settings: {settings_err}")
            
            version_action_area = pn.Column(
                *version_info,
                buttons_row,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                sizing_mode='stretch_width'
            )
            
            # Safely update the version action area
            if hasattr(self, '_version_action_area'):
                try:
                    self._version_action_area.objects = version_action_area.objects
                except Exception as update_err:
                    logger.error(f"Error updating _version_action_area: {update_err}")
                    # Try a full replacement
                    if hasattr(self, 'tabs') and len(self.tabs) > 1:
                        versions_tab = self.tabs[1][1]
                        if isinstance(versions_tab, pn.Column) and len(versions_tab) > 0:
                            try:
                                # Replace the last element
                                versions_tab[-1] = version_action_area
                            except Exception as replace_err:
                                logger.error(f"Error replacing version action area: {replace_err}")
            else:
                # Try to find the action area in the versions tab
                if hasattr(self, 'tabs') and len(self.tabs) > 1:
                    versions_tab = self.tabs[1][1]
                    if isinstance(versions_tab, pn.Column) and len(versions_tab) > 0:
                        try:
                            # Replace the last element
                            versions_tab[-1] = version_action_area
                        except Exception as replace_err:
                            logger.error(f"Error replacing version action area: {replace_err}")
            
        except Exception as e:
            logger.error(f"Error selecting version: {str(e)}")
            import traceback
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _approval_selected(self, event):
        """Handle approval selection from table"""
        if not event.new:
            return
            
        try:
            # Get selected row index
            selected_idx = event.new[0]
            
            # Get data from the DataFrame
            df = event.obj.value
            
            # Create placeholder approval details
            approval_details = Column(
                Markdown("## Selected Approval Workflow"),
                Markdown(f"Type: {df.iloc[selected_idx]['Type']}"),
                Markdown(f"Status: {df.iloc[selected_idx]['Status']}"),
                Markdown(f"Started: {df.iloc[selected_idx]['Started']}"),
                Markdown(f"Due: {df.iloc[selected_idx]['Due']}"),
                Markdown("### Approvers"),
                Markdown("*Approver information would be displayed here*"),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                sizing_mode='stretch_width'
            )
            
            # Replace existing details area
            self.tabs[3][1].objects[-1] = approval_details
            
        except Exception as e:
            logger.error(f"Error selecting approval: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _navigate_back(self, event=None):
        """Navigate back to dashboard"""
        return pn.state.execute("window.location.href = '/dashboard'")
    
    def _view_document(self, event=None):
        """View the current document version"""
        if not self.current_version:
            self.notification_area.object = "**Error:** No document version available"
            return
        
        # Show loading message
        self.notification_area.object = "**Getting document URL...**"
        
        try:
            # Import the document controller function
            from CDocs.controllers.document_controller import get_document_edit_url
            
            # Call API to get download URL
            result = get_document_edit_url(
                user=self.user,
                document_uid=self.document_uid
            )
            
            if result.get('success'):
                download_url = result.get('edit_url')
                file_type = result.get('file_type', 'Document')
                
                # Create a clickable link to open the document
                self.notification_area.object = f"""
                **Document ready for viewing!**
                
                Click this link to view the {file_type}: 
                [Open in FileCloud Viewer]({download_url})
                
                This link will expire in 24 hours.
                """
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Unable to get document URL')}"
        
        except Exception as e:
            import traceback
            logger.error(f"Error viewing document: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error viewing document:** {str(e)}"
    
    def _download_current_version(self, event=None):
        """Download the current document version"""
        if not self.current_version:
            self.notification_area.object = "**Error:** No document version available"
            return
            
        try:
            # Get version UID
            version_uid = self.current_version.get('UID')
            
            # Import required modules
            import io
            from panel.widgets import FileDownload
            from CDocs.controllers.document_controller import download_document_version
            
            # Create a callback function that will fetch the file when the download button is clicked
            def get_file_content():
                try:
                    # Get document content with all required parameters
                    doc_content = download_document_version(
                        user=self.user, 
                        document_uid=self.document_uid, 
                        version_uid=version_uid
                    )
                    
                    if isinstance(doc_content, dict) and 'content' in doc_content and doc_content['content']:
                        # Return the binary content and filename
                        file_name = doc_content.get('file_name', 'document.pdf')
                        return io.BytesIO(doc_content['content']), file_name
                    else:
                        # Handle error
                        self.notification_area.object = "**Error:** Could not download document"
                        return None, None
                except Exception as e:
                    logger.error(f"Error downloading document: {e}")
                    self.notification_area.object = f"**Error:** {str(e)}"
                    return None, None
            
            # Get file name from current version
            file_name = self.current_version.get('file_name', 'document.pdf')
            
            # Create download widget
            download_widget = FileDownload(
                callback=get_file_content,
                filename=file_name,
                button_type="success",
                label=f"Download {file_name}"
            )
            
            # Show the download widget in the notification area
            self.notification_area.object = pn.Column(
                "**Document ready for download:**", 
                download_widget
            )
            
        except Exception as e:
            logger.error(f"Error setting up download: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _show_edit_form(self, event=None):
        """Show form to edit document metadata"""
        self.notification_area.object = "Loading edit form..."
        
        try:
            # Create edit form with current values
            title_input = TextInput(
                name="Title",
                value=self.doc_title or self.document.get('title', ''),
                width=400
            )
            
            description_input = TextAreaInput(
                name="Description",
                value=self.document.get('description', ''),
                width=400,
                height=150
            )
            
            # Create save button
            save_btn = Button(
                name="Save Changes",
                button_type="success",
                width=120
            )
            
            # Create cancel button
            cancel_btn = Button(
                name="Cancel",
                button_type="default",
                width=120
            )
            
            # Create form layout
            edit_form = Column(
                Markdown("# Edit Document Metadata"),
                title_input,
                description_input,
                Row(
                    cancel_btn,
                    save_btn,
                    align='end'
                ),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                width=450
            )
            
            # Set up event handlers
            save_btn.on_click(lambda event: self._save_document_changes(
                title_input.value,
                description_input.value
            ))
            
            cancel_btn.on_click(self._load_document)
            
            # Clear display area and show form
            # FIX: Check if template exists, otherwise use main_content
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(edit_form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(edit_form)
            
            # Clear notification
            self.notification_area.object = ""
            
        except Exception as e:
            logger.error(f"Error showing edit form: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _save_document_changes(self, title, description):
        """Save document metadata changes"""
        try:
            # Validate inputs
            if not title:
                self.notification_area.object = "**Error:** Title is required"
                return
                
            # Update document
            update_result = update_document(
                document_uid=self.document_uid,
                user=self.user,
                data={
                    'title': title,
                    'description': description
                }
            )
            
            if update_result and update_result.get('success'):
                self.notification_area.object = "Document updated successfully"
                # Reload document
                self._load_document()
            else:
                error_msg = update_result.get('message', 'An error occurred')
                self.notification_area.object = f"**Error:** {error_msg}"
                
        except Exception as e:
            logger.error(f"Error saving document changes: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _show_upload_form(self, event=None):
        """Show form to upload a new document version"""
        self.notification_area.object = "Loading upload form..."
        
        try:
            # Create file input
            file_input = FileInput(accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt')
            
            # Create comment input
            comment_input = TextAreaInput(
                name="Version Comment",
                placeholder="Enter a comment for this version",
                width=400,
                height=100
            )
            
            # Create upload button
            upload_btn = Button(
                name="Upload Version",
                button_type="success",
                width=120
            )
            
            # Create cancel button
            cancel_btn = Button(
                name="Cancel",
                button_type="default",
                width=120
            )
            
            # Create form layout
            upload_form = Column(
                Markdown("# Upload New Version"),
                Markdown("Select a file to upload as a new version of this document."),
                file_input,
                comment_input,
                Row(
                    cancel_btn,
                    upload_btn,
                    align='end'
                ),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                width=450
            )
            
            # Set up event handlers
            upload_btn.on_click(lambda event: self._upload_new_version(
                file_input.value,
                file_input.filename,
                comment_input.value
            ))
            
            cancel_btn.on_click(self._load_document)
            
            # Clear display area and show form
            # FIX: Check if template exists, otherwise use main_content
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(upload_form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(upload_form)
        
            # Clear notification
            self.notification_area.object = ""
        
        except Exception as e:
            logger.error(f"Error showing upload form: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _upload_new_version(self, file_content, file_name, comment):
        """Upload a new document version using FileCloud for storage"""
        try:
            # Validate file
            if not file_content:
                self.notification_area.object = "**Error:** Please select a file to upload"
                return
                
            # Show upload in progress message
            self.notification_area.object = "**Uploading new version...**"
            
            # First create the document version in Neo4j
            from CDocs.controllers.document_controller import create_document_version
            
            # Call create_document_version which will handle the Neo4j part
            result = create_document_version(
                user=self.user,
                document_uid=self.document_uid,
                file_content=file_content,
                file_name=file_name,
                comment=comment
            )
            
            if not result:
                error_msg = "Failed to create new document version"
                self.notification_area.object = f"**Error:** {error_msg}"
                return
            
            doc=ControlledDocument(uid=self.document_uid)
            doc.set_current_version(result.get('UID'))

                
            # Now upload the file to FileCloud
            from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
            
            # Prepare metadata for FileCloud
            # metadata = {
            #     "doc_uid": self.document_uid,
            #     "doc_number": self.doc_number,
            #     "version_uid": result.get('UID'),
            #     "version_number": result.get('version_number'),
            #     "title": self.doc_title,
            #     "status": self.doc_status,
            #     "owner": self.doc_owner,
            #     "comment": comment
            # }
            
            # Upload to FileCloud
            filecloud_result = upload_document_to_filecloud(
                user=self.user,
                document=doc,
                file_content=file_content,
                version_comment=comment,
                metadata=None
            )
            
            if not filecloud_result or not filecloud_result.get('success'):
                error_msg = "Failed to upload file to FileCloud storage"
                self.notification_area.object = f"**Error:** {error_msg}"
                return
                
            # Success!
            self.notification_area.object = "New version uploaded successfully"
            
            # Reload document
            self._load_document()
                    
        except Exception as e:
            logger.error(f"Error uploading new version: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error uploading new version:** {str(e)}"
    
    # Helper methods
    def _format_date(self, date_str):
        """Format date string for display"""
        if not date_str:
            return None
            
        try:
            date = datetime.fromisoformat(date_str)
            return date.strftime('%Y-%m-%d')
        except Exception:
            return date_str
    
    def _get_status_color(self, status_code):
        """Get color for document status"""
        return settings.get_status_color(status_code)
    
    # Form placeholders for actions that would be implemented
    def _show_review_form(self, event=None):
        """Show form to start a review cycle"""
        self.notification_area.object = "Loading review form..."
        
        try:
            # Create form elements
            from CDocs.models.user_extensions import DocUser
            
            # Get all users who can be reviewers
            reviewers = []
            try:
                # Directly query users since get_potential_reviewers doesn't exist
                from CDocs.db.db_operations import run_query  # Changed from 'from CDocs.db import db'
                
                reviewers_result = run_query(
                    """
                    MATCH (u:User)
                    WHERE (u.UID <> $current_user_uid) and not('Template' in labels(u))
                    RETURN u.UID as uid, u.Name as name, u.Role as role, u.Department as department
                    ORDER BY u.Name
                    """, 
                    {"current_user_uid": self.user.uid}
                )
                
                reviewers = [{"uid": r["uid"], "name": r["name"], "role": r.get("role", ""), 
                            "department": r.get("department", "")} for r in reviewers_result]
                            
            except Exception as e:
                # If db query fails, use a simple fallback with an empty list
                logger.warning(f"Error fetching reviewers: {e}")
                reviewers = []
            

            # Fix for Panel 1.6.1 - create the widget with an options dictionary instead of tuples
            reviewer_options = []
            for r in reviewers:
                # Handle different possible data structures
                if isinstance(r, dict):
                    uid = r.get("uid", "")
                    name = r.get("name", "Unknown")
                    role = r.get("role", "")
                    display_text = f"{name} ({role})" if role else name
                    reviewer_options.append((uid, display_text))
                else:
                    # Fallback for non-dictionary items
                    logger.warning(f"Unexpected reviewer data format: {type(r)}")

            # Fix for Panel 1.6.1 - invert the dictionary mapping to fix display issue
            # Use display text as key and UID as value (this is counter-intuitive but works for Panel 1.6.1)
            reviewer_display_dict = {display: uid for uid, display in reviewer_options}

            reviewer_select = pn.widgets.MultiSelect(
                name="Select Reviewers",
                options=reviewer_display_dict,  # Use inverted dictionary
                value=[],
                size=6,
                width=400
            ) 
            
            # Create due date picker (default to 2 weeks from now)
            # Convert the date string to a proper date object
            default_due_date = (datetime.now() + timedelta(days=14)).date()  # Use .date() to get date object
            due_date_picker = pn.widgets.DatePicker(
                name="Due Date",
                value=default_due_date,
                width=200
            )
            
            # Create review type dropdown
            review_type_select = pn.widgets.Select(
                name="Review Type",
                options=settings.REVIEW_TYPES,
                value="STANDARD",
                width=200
            )
            
            # Create sequential checkbox
            sequential_check = pn.widgets.Checkbox(
                name="Sequential Review",
                value=False
            )
            
            # Required approval percentage (default 100%)
            approval_pct = pn.widgets.IntSlider(
                name="Required Approval Percentage",
                start=1,
                end=100,
                value=100,
                step=1,
                width=200
            )
            
            # Instructions textarea
            instructions_input = pn.widgets.TextAreaInput(
                name="Instructions for Reviewers",
                placeholder="Enter instructions for reviewers",
                width=400,
                height=100
            )
            
            # Create start button
            start_btn = pn.widgets.Button(
                name="Start Review Cycle",
                button_type="success",
                width=150
            )
            
            # Create cancel button
            cancel_btn = pn.widgets.Button(
                name="Cancel",
                button_type="default",
                width=120
            )
            
            # Create form layout
            review_form = pn.Column(
                pn.pane.Markdown("# Start Review Cycle"),
                pn.pane.Markdown(f"## {self.doc_number}: {self.doc_title}"),
                reviewer_select,
                pn.Row(
                    review_type_select,
                    due_date_picker
                ),
                pn.Row(
                    sequential_check,
                    approval_pct
                ),
                instructions_input,
                pn.Row(
                    cancel_btn,
                    start_btn,
                    align='end'
                ),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                width=500
            )
            
            # Set up event handlers
            start_btn.on_click(lambda event: self._start_review_cycle(
                reviewer_uids=reviewer_select.value,
                due_date=due_date_picker.value,
                review_type=review_type_select.value,
                sequential=sequential_check.value,
                required_approval_percentage=approval_pct.value,
                instructions=instructions_input.value
            ))
            
            cancel_btn.on_click(self._load_document)
            
            # Clear display area and show form
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(review_form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(review_form)
            
            # Clear notification
            self.notification_area.object = ""
            
        except Exception as e:
            logger.error(f"Error showing review form: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error showing review form:** {str(e)}"

    def _start_review_cycle(self, reviewer_uids, due_date, review_type, sequential, 
                          required_approval_percentage, instructions):
        """Start a new review cycle for the current document"""
        # Validate inputs
        if not reviewer_uids or len(reviewer_uids) == 0:
            self.notification_area.object = "**Error:** At least one reviewer must be selected"
            return
        
        # Convert due_date string to datetime if needed
        if isinstance(due_date, str):
            try:
                due_date = datetime.fromisoformat(due_date)
            except ValueError:
                self.notification_area.object = "**Error:** Invalid date format"
                return
        elif isinstance(due_date, date):
            # Convert date to datetime at end of day
            due_date = datetime.combine(due_date, datetime.max.time())
        
        # Show processing message
        self.notification_area.object = "**Starting review cycle...**"
        
        try:
            # Call controller to create review cycle
            result = create_review_cycle(
                user=self.user,
                document_uid=self.document_uid,
                reviewer_uids=reviewer_uids,
                due_date=due_date,
                instructions=instructions,
                review_type=review_type,
                sequential=sequential,
                required_approval_percentage=required_approval_percentage
            )
            
            if result['success']:
                self.notification_area.object = "**Success:** Review cycle started successfully"
                
                # # Fix: Use properly formatted JavaScript execution
                # # In Panel 1.6.1, we need to use a lambda function for execution, not a string directly
                # pn.state.execute_with_timeout = lambda delay, callback: pn.state.execute(
                #     lambda: pn.state.add_timeout_callback(callback, delay)
                # )
                
                # # Use the timeout callback system instead of direct JS execution
                # pn.state.add_timeout_callback(self._load_document, 1000)
                
                # # Add notification about reloading
                # self.notification_area.object += "<br>*Reloading document in 1 second...*"
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Unknown error')}"
        
        except ResourceNotFoundError as e:
            self.notification_area.object = f"**Error:** {str(e)}"
        except ValidationError as e:
            self.notification_area.object = f"**Validation Error:** {str(e)}"
        except PermissionError as e:
            self.notification_area.object = f"**Permission Error:** {str(e)}"
        except BusinessRuleError as e:
            self.notification_area.object = f"**Business Rule Error:** {str(e)}"
        except Exception as e:
            logger.error(f"Error starting review cycle: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** An unexpected error occurred"
    
    def _show_approval_form(self, event=None):
        """Show the form to start an approval workflow"""
        if not self.document or not self.document_uid:
            self.notification_area.object = "❌ No document selected"
            return
    
        # Create form elements
        self.notification_area.object = "Setting up approval workflow..."

        # Get user options at the beginning so it's available in the entire scope
        user_options = self._get_user_options()
        
        # Create workflow type selector
        workflow_types = list(settings.APPROVAL_WORKFLOW_TYPES) if hasattr(settings, 'APPROVAL_WORKFLOW_TYPES') else ['STANDARD', 'DEPARTMENT', 'QUALITY']
        workflow_type = pn.widgets.Select(
            name='Approval Workflow Type',
            options=workflow_types,
            value='STANDARD'
        )
        
        # Create due date picker with default (14 days from now)
        default_due = datetime.now() + timedelta(days=14)
        due_date = pn.widgets.DatePicker(
            name='Due Date',
            value=default_due
        )
        
        # Create instructions input
        instructions = pn.widgets.TextAreaInput(
            name='Instructions for Approvers',
            placeholder='Enter instructions for approvers...',
            rows=3
        )
        
        # Create approvers selection
        # Here we'll use a dynamic approach to build steps
        step_cards = []
        
        # First step (always required)
        approver_step1 = self._create_approver_step("Step 1 (Required)",user_options)
        step_cards.append(approver_step1)
        
        # Button to add more steps
        add_step_btn = pn.widgets.Button(name="+ Add Step", button_type="default", width=150)
        
        # Container for steps
        steps_container = pn.Column(
            approver_step1,
            add_step_btn,
            sizing_mode='stretch_width'
        )
        
        # Current step count for dynamic addition
        step_count = [1]  # Using list for mutable reference
        
        def add_step(event):
            step_count[0] += 1
            new_step = self._create_approver_step(f"Step {step_count[0]}",user_options)
            step_cards.append(new_step)
            # Insert before the add button
            steps_container.insert(-1, new_step)
        
        add_step_btn.on_click(add_step)
        
        # Create submit button
        submit_btn = pn.widgets.Button(name='Start Approval', button_type='primary', width=200)
        
        # Create the form
        approval_form = pn.Column(
            pn.pane.Markdown("# Start Approval Workflow"),
            pn.pane.Markdown(f"Document: **{self.doc_number}** - {self.doc_title}"),
            workflow_type,
            due_date,
            instructions,
            pn.pane.Markdown("## Approval Steps"),
            pn.pane.Markdown("Define who needs to approve the document and in what order:"),
            steps_container,
            pn.layout.Spacer(height=20),
            submit_btn,
            sizing_mode='stretch_width'
        )
        
        # Handle submission
        def submit_approval(event):
            try:
                self.notification_area.object = "⏳ Starting approval workflow..."
        
                # Collect approvers from each step
                steps_data = []
                for i, step_card in enumerate(step_cards, 1):
                    # Get the widgets from the object attributes instead of param
                    if not hasattr(step_card, 'approvers_select') or not hasattr(step_card, 'all_approve_check'):
                        self.notification_area.object = f"❌ Error: Step {i} widget references not found"
                        return
                        
                    approvers_select = step_card.approvers_select
                    all_approve_check = step_card.all_approve_check
                    
                    selected_uids = approvers_select.value
                    
                    # Debug the selected values
                    print(f"Step {i} selected values: {selected_uids}")
                    
                    if not selected_uids or len(selected_uids) == 0:
                        self.notification_area.object = f"❌ Please select at least one approver for Step {i}"
                        return
                    
                    # Create step data with 'approvers' key instead of 'approver_uids'
                    steps_data.append({
                        'approvers': [{'user_uid': uid} for uid in selected_uids],  # Each approver is a dict with user_uid
                        'all_must_approve': all_approve_check.value,
                        'step_number': i,
                        'required_approvals': 1 if all_approve_check.value else len(selected_uids)
                    })
                
                # Validate we have at least one approver
                if not steps_data:
                    self.notification_area.object = "❌ Please add at least one approver"
                    return
                
                # Call API to create approval workflow
                from CDocs.controllers.approval_controller import create_approval_workflow
                result = create_approval_workflow(
                    user=self.user,
                    document_uid=self.document_uid,
                    steps=steps_data,
                    workflow_type=workflow_type.value,
                    due_date=due_date.value,
                    instructions=instructions.value
                )
                
                if result.get('success'):
                    self.notification_area.object = "✅ Approval workflow started successfully"
                    # Reload document to show updated status
                    self._load_document()
                else:
                    self.notification_area.object = f"❌ Error: {result.get('message', 'Unknown error')}"
                    
            except Exception as e:
                self.notification_area.object = f"❌ Error: {str(e)}"
                logger.error(f"Error starting approval: {e}")
                logger.error(traceback.format_exc())
        
        submit_btn.on_click(submit_approval)
        
        # Show the form in the main content area
        self.main_content.clear()
        self.main_content.append(approval_form)
    
    def _create_approver_step(self, title,user_options):
        """Helper to create a step card for approval workflow"""
       
        
        # Create a multi-select for approvers
        approvers_select = pn.widgets.MultiSelect(
            name='Approvers',
            options=user_options,
            size=5
        )
        
        # Step options
        all_approve_check = pn.widgets.Checkbox(
            name='All must approve',
            value=True
        )
        
        options_row = pn.Row(
            all_approve_check,
            name='Step Options'
        )
        
        # Create step card
        step_card = pn.Column(
            pn.pane.Markdown(f"### {title}"),
            approvers_select,
            options_row,
            styles={'background': '#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded', 'mb-3'],
            sizing_mode='stretch_width'
        )
        
        # Store references directly as attributes on the object instead of using param
        step_card.approvers_select = approvers_select
        step_card.all_approve_check = all_approve_check
        
        return step_card
    
    def _get_user_options(self):
        """Get user options for approver selection"""

        from CDocs.models.user_extensions import DocUser
        try:
            potential_approvers = DocUser.get_users_by_role(role="APPROVER")
            user_options = {f"{u.name} ({u.username})" : u.uid for u in potential_approvers}
            return user_options
        except Exception as e:
            logger.error(f"Error getting users: {e}")
            return {}  # Return empty dict on error
    
    def _show_publish_form(self, event=None):
        """Show form to publish the document"""
        self.notification_area.object = "Publish form would be shown here"
    
    def _show_archive_form(self, event=None):
        """Show form to archive the document"""
        self.notification_area.object = "Archive form would be shown here"
    
    def _show_clone_form(self, event=None):
        """Show form to clone the document"""
        try:
            # Check if document has a current version
            if not self.current_version:
                self.notification_area.object = "**Error:** No document version available to clone"
                return
    
            # Show loading message
            self.notification_area.object = "**Loading clone form...**"
            
            # Create form elements
            title_input = pn.widgets.TextInput(
                name="Title",
                value=f"Copy of {self.doc_title}",
                placeholder="Enter a title for the cloned document",
                width=400
            )
            
            comment_input = pn.widgets.TextAreaInput(
                name="Version Comment",
                placeholder="Enter a comment for the new version",
                value="Clone of document",
                width=400,
                height=100
            )
            
            # Create radio button for clone type
            clone_type = pn.widgets.RadioButtonGroup(
                name="Clone Type",
                options=["New Minor Version", "New Document"],
                value="New Minor Version",
                button_type="default"
            )
    
            # Create department dropdown for new document option
            # Only show when "New Document" is selected
            from CDocs.config import settings
            departments = list(settings.DEPARTMENTS.keys())
            department_select = pn.widgets.Select(
                name="Department",
                options=departments,
                value=None,
                width=200
            )
            
            # Document type dropdown for new document option
            doc_types = list(settings.DOCUMENT_TYPES.keys())
            doc_type_select = pn.widgets.Select(
                name="Document Type",
                options=doc_types,
                value=None,
                width=200
            )
    
            # Dynamic display of department and doc type based on clone type
            def update_form_fields(event):
                if event.new == "New Document":
                    new_doc_fields.visible = True
                else:
                    new_doc_fields.visible = False
    
            clone_type.param.watch(update_form_fields, 'value')
    
            # Group the new document fields for easy visibility toggle
            new_doc_fields = pn.Column(
                pn.pane.Markdown("### New Document Details"),
                pn.Row(doc_type_select, department_select),
                visible=False  # Hidden by default
            )
            
            # Create submit button
            submit_btn = pn.widgets.Button(
                name="Clone Document",
                button_type="primary",
                width=150
            )
            
            # Create cancel button
            cancel_btn = pn.widgets.Button(
                name="Cancel",
                button_type="default",
                width=100
            )
            
            # Create form layout
            clone_form = pn.Column(
                pn.pane.Markdown("# Clone Document"),
                pn.pane.Markdown(f"**Source:** {self.doc_number}: {self.doc_title}"),
                pn.pane.Markdown(f"**Version:** {self.current_version.get('version_number', 'Unknown')}"),
                pn.Row(clone_type, sizing_mode='stretch_width'),
                pn.Row(title_input, sizing_mode='stretch_width'),
                new_doc_fields,  # New document fields section
                pn.Row(comment_input, sizing_mode='stretch_width'),
                pn.Row(
                    pn.layout.HSpacer(),
                    cancel_btn,
                    submit_btn,
                    sizing_mode='stretch_width'
                ),
                width=450,
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded', 'shadow']
            )
            
            # Set up event handlers
            def submit_clone(event):
                # Show processing message
                self.notification_area.object = "**Cloning document...**"
                
                try:
                    # Download the current version
                    from CDocs.controllers.document_controller import download_document_version
                    
                    # Get document content
                    version_uid = self.current_version.get('UID')
                    doc_content_result = download_document_version(
                        user=self.user,
                        document_uid=self.document_uid,
                        version_uid=version_uid
                    )
                    
                    if not isinstance(doc_content_result, dict) or 'content' not in doc_content_result:
                        self.notification_area.object = "**Error:** Could not download source document"
                        return
                        
                    file_content = doc_content_result['content']
                    file_name = doc_content_result.get('file_name', self.current_version.get('file_name', 'document.pdf'))
                    
                    if clone_type.value == "New Minor Version":
                        # Create a new version of the same document
                        from CDocs.controllers.document_controller import create_document_version
                        
                        result = create_document_version(
                            user=self.user,
                            document_uid=self.document_uid,
                            file_content=file_content,
                            file_name=file_name,
                            comment=comment_input.value
                        )
                        
                        if result and 'UID' in result:
                            # Set as current version
                            from CDocs.controllers.document_controller import set_current_version
                            set_result = set_current_version(
                                user=self.user,
                                document_uid=self.document_uid,
                                version_uid=result['UID']
                            )
                            
                            # Get document and version objects for FileCloud upload
                            from CDocs.models.document import ControlledDocument, DocumentVersion
                            doc = ControlledDocument(uid=self.document_uid)
                            version = DocumentVersion(uid=result['UID'])
                            
                            # Upload to FileCloud
                            from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
                            filecloud_result = upload_document_to_filecloud(
                                user=self.user,
                                document=doc,
                                file_content=file_content,
                                version_comment=comment_input.value,
                                metadata=None
                            )
                            
                            if not filecloud_result or not filecloud_result.get('success', False):
                                self.notification_area.object = f"**Warning:** Document created but FileCloud upload failed: {filecloud_result.get('message', 'Unknown error')}"
                            else:
                                # Show success message
                                self.notification_area.object = "**Success:** Document cloned as new version successfully"
                            
                            # Reload document after a short delay
                            self._load_document()
                        else:
                            self.notification_area.object = f"**Error:** {result.get('message', 'Failed to create new version')}"
                    else:
                        # Create entirely new document based on the current one
                        from CDocs.controllers.document_controller import clone_document
                        
                        # Convert selected document type and department names to codes
                        from CDocs.config import settings
                        doc_type_code = settings.get_document_type_code(doc_type_select.value)
                        department_code = settings.get_department_code(department_select.value)
                        
                        result = clone_document(
                            user=self.user,
                            document_uid=self.document_uid,
                            new_title=title_input.value,
                            doc_type=doc_type_code,
                            department=department_code,
                            include_content=True,
                            clone_as_new_revision=False
                        )
                        
                        if result.get('success', False):
                            new_doc_uid = result.get('document', {}).get('uid')
                            new_doc_number = result.get('document', {}).get('doc_number', '')

                            from CDocs.controllers.document_controller import set_current_version
                            set_result = set_current_version(
                                user=self.user,
                                document_uid=result['document']['uid'],
                                version_uid=result['document']['version_uid']
                            )
                            
                            # Get document and version objects for FileCloud upload
                            from CDocs.models.document import ControlledDocument
                            doc = ControlledDocument(uid=new_doc_uid)
                            
                            # Get the version UID from result or fetch the current version
                            version_uid = result.get('version', {}).get('uid')
                            if not version_uid:
                                # Try to get the current version
                                current_version = doc.current_version
                                if current_version:
                                    version_uid = current_version.uid
                            
                            # Upload to FileCloud
                            from CDocs.controllers.filecloud_controller import upload_document_to_filecloud
                            filecloud_result = upload_document_to_filecloud(
                                user=self.user,
                                document=doc,
                                file_content=file_content,
                                version_comment=comment_input.value,
                                metadata=None
                            )
                            
                            if not filecloud_result or not filecloud_result.get('success', False):
                                self.notification_area.object = f"""
                                **Warning:** Document created but FileCloud upload failed
                                
                                New document: [{new_doc_number}](/document/{new_doc_uid})
                                
                                Error: {filecloud_result.get('message', 'Unknown error')}
                                """
                            else:
                                # Show success message with link to new document
                                self.notification_area.object = f"""
                                **Success:** Document cloned successfully as new document
                                
                                New document: [{new_doc_number}](/document/{new_doc_uid})
                                """
                        else:
                            self.notification_area.object = f"**Error:** {result.get('message', 'Failed to clone document')}"
                    
                except Exception as e:
                    logger.error(f"Error cloning document: {e}")
                    logger.error(traceback.format_exc())
                    self.notification_area.object = f"**Error cloning document:** {str(e)}"
            
            def cancel_action(event):
                self.notification_area.object = "Clone operation canceled"
                # Remove the form from view
                if hasattr(self, 'main_content'):
                    self.main_content.clear()
                    self.main_content.append(self.notification_area)
                    self._load_document()
            
            # Add handlers
            submit_btn.on_click(submit_clone)
            cancel_btn.on_click(cancel_action)
            
            # Clear display area and show form
            if self.template and hasattr(self.template, 'main'):
                # Standalone mode using template
                self.tabs.clear()
                self.template.main.clear()
                self.template.main.append(self.notification_area)
                self.template.main.append(clone_form)
            else:
                # Embedded mode using main_content
                self.main_content.clear()
                self.main_content.append(self.notification_area)
                self.main_content.append(clone_form)
            
            # Clear notification
            self.notification_area.object = ""
            
        except Exception as e:
            logger.error(f"Error showing clone form: {e}")
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error showing clone form:** {str(e)}"
    
    def _set_as_current_version(self, event):
        """Set the selected version as the current version"""
        logger = logging.getLogger('CDocs.ui.document_detail')
        
        # Debug the stored version UID
        logger.debug(f"_set_as_current_version called, stored version UID: {getattr(self, '_selected_version_uid', 'None')}")
        
        if not hasattr(self, '_selected_version_uid') or not self._selected_version_uid:
            self.notification_area.object = "**Error:** No version selected"
            return
            
        try:
            # Get version UID
            version_uid = self._selected_version_uid
            logger.debug(f"Setting version {version_uid} as current")
            
            # Show in progress message
            self.notification_area.object = "**Setting as current version...**"
            
            # Use controller to set current version
            from CDocs.controllers.document_controller import set_current_version
            
            result = set_current_version(
                user=self.user,
                document_uid=self.document_uid,
                version_uid=version_uid
            )
            
            if result and result.get('success'):
                self.notification_area.object = "**Success:** Version set as current"
                
                # Reload document to reflect the changes
                self._load_document()
            else:
                error_msg = "Unknown error" 
                if result and 'message' in result:
                    error_msg = result['message']
                self.notification_area.object = f"**Error:** Could not set as current version: {error_msg}"
                
        except Exception as e:
            logger.error(f"Error setting current version: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error:** {str(e)}"

    def _download_selected_version(self, event):
        """Download the selected document version"""
        logger = logging.getLogger('CDocs.ui.document_detail')
        
        if not hasattr(self, '_selected_version_uid') or not self._selected_version_uid:
            self.notification_area.object = "**Error:** No version selected"
            return
            
        try:
            # Get version UID
            version_uid = self._selected_version_uid
            logger.debug(f"Downloading version: {version_uid}")
            
            # Show download in progress message
            self.notification_area.object = "**Preparing download...**"
            
            # Import the necessary functions
            from CDocs.controllers.document_controller import download_document_version
            from panel.widgets import FileDownload
            
            # Create a callback function that will fetch the file when the download button is clicked
            def get_file_content():
                try:
                    # Get document content with all required parameters
                    doc_content = download_document_version(
                        user=self.user, 
                        document_uid=self.document_uid, 
                        version_uid=version_uid
                    )
                    
                    if isinstance(doc_content, dict) and 'content' in doc_content and doc_content['content']:
                        # Return ONLY the BytesIO object, not a tuple
                        file_name = doc_content.get('file_name', 'document.pdf')
                        return io.BytesIO(doc_content['content'])
                    else:
                        # Handle error
                        self.notification_area.object = "**Error:** Could not download document"
                        return None
                except Exception as e:
                    logger.error(f"Error downloading version: {e}")
                    import traceback
                    logger.error(f"Traceback: {traceback.format_exc()}")
                    self.notification_area.object = f"**Error:** {str(e)}"
                    return None
            
            # Find the version details to get the filename
            file_name = "document.pdf"  # Default
            logger.info("version details: ", self.document)
            for version in self.document.get('versions', []):
                if version.get('UID') == version_uid:
                    file_name = version.get('file_name', file_name)
                    break
            
            # Create download widget with separate filename parameter
            download_widget = FileDownload(
                callback=get_file_content,
                filename=file_name,  # Set filename separately
                button_type="success",
                label=f"Download {file_name}"
            )
            
            # Update the version action area to show the download widget
            if hasattr(self, '_version_action_area'):
                # Find the button row in the action area
                for idx, obj in enumerate(self._version_action_area):
                    if isinstance(obj, pn.Row) and any(isinstance(child, pn.widgets.Button) for child in obj):
                        # Replace existing download button with our FileDownload widget
                        new_row = pn.Row(*[child for child in obj if not (isinstance(child, pn.widgets.Button) 
                                                   and child.name == "Download Version")])
                        new_row.append(download_widget)
                        self._version_action_area[idx] = new_row
                        break
                else:
                    # If no button row found, add the download widget at the end
                    self._version_action_area.append(download_widget)
                    
                self.notification_area.object = "**Ready for download.** Click the download button to save the file."
            else:
                # If no action area exists, show download widget in notification area
                self.notification_area.object = pn.Column(
                    "**Ready for download:**", 
                    download_widget
                )
            
        except Exception as e:
            logger.error(f"Error setting up download: {e}")
            import traceback
            logger.error(f"Traceback: {traceback.format_exc()}")
            self.notification_area.object = f"**Error:** {str(e)}"

Parameters

Name Type Default Kind
bases param.Parameterized -

Parameter Details

bases: Parameter of type param.Parameterized

Return Value

Returns unspecified type

Class Interface

Methods

__init__(self, parent_app)

Purpose: Internal method: init

Parameters:

  • parent_app: Parameter

Returns: None

set_user(self, user)

Purpose: Set the current user.

Parameters:

  • user: Parameter

Returns: None

load_document(self, document_uid, doc_number)

Purpose: Load document by UID or document number.

Parameters:

  • document_uid: Parameter
  • doc_number: Parameter

Returns: None

get_document_view(self)

Purpose: Get the document view for embedding in other panels.

Returns: None

_get_current_user(self) -> DocUser

Purpose: Get the current user from session

Returns: Returns DocUser

_setup_header(self)

Purpose: Set up the header with title and actions

Returns: None

_setup_sidebar(self)

Purpose: Set up the sidebar with document actions

Returns: None

_setup_main_area(self)

Purpose: Set up the main area with document content tabs

Returns: None

_load_document(self, event)

Purpose: Load document data and update the UI.

Parameters:

  • event: Parameter

Returns: None

_extract_document_properties(self)

Purpose: Extract document properties from document data.

Returns: None

load_document_data(self, document_data)

Purpose: Load document directly from document data. Parameters: ----------- document_data : dict The document data to load

Parameters:

  • document_data: Parameter

Returns: None

_setup_document_info(self)

Purpose: Set up document info panel in sidebar

Returns: None

_setup_document_actions(self)

Purpose: Set up document action buttons in sidebar

Returns: None

_create_document_tabs(self)

Purpose: Create tabs for different document content sections

Returns: None

_create_overview_tab(self)

Purpose: Create the overview tab content

Returns: None

_create_versions_tab(self)

Purpose: Create the versions tab content

Returns: None

_edit_document_online(self, event)

Purpose: Get edit URL from FileCloud and open it

Parameters:

  • event: Parameter

Returns: None

_convert_to_pdf(self, event)

Purpose: Convert the current document version to PDF

Parameters:

  • event: Parameter

Returns: None

_create_reviews_tab(self)

Purpose: Create the reviews tab content

Returns: None

_review_selected(self, event)

Purpose: Handle review selection from table with support for both selection and cell click events

Parameters:

  • event: Parameter

Returns: None

_show_extend_review_deadline_form(self, review_uid)

Purpose: Show form to extend review deadline

Parameters:

  • review_uid: Parameter

Returns: None

_show_add_reviewer_form(self, review_uid)

Purpose: Show form to add a reviewer to an active review cycle

Parameters:

  • review_uid: Parameter

Returns: None

_show_cancel_review_form(self, review_uid)

Purpose: Show form to cancel an active review cycle

Parameters:

  • review_uid: Parameter

Returns: None

_load_review_details_by_uid(self, review_uid)

Purpose: Helper method to refresh review details after an action

Parameters:

  • review_uid: Parameter

Returns: None

_convert_neo4j_datetimes(self, data)

Purpose: Recursively convert all Neo4j DateTime objects to Python datetime objects or strings. Args: data: Any data structure potentially containing Neo4j DateTime objects Returns: Same data structure with Neo4j DateTime objects converted to Python datetime

Parameters:

  • data: Parameter

Returns: See docstring for return details

_create_approvals_tab(self)

Purpose: Create the approvals tab content with support for the new approval schema

Returns: None

_create_step_card(self, step, step_number)

Purpose: Create a card for an approval step

Parameters:

  • step: Parameter
  • step_number: Parameter

Returns: None

_get_status_color(self, status)

Purpose: Get color for a status value

Parameters:

  • status: Parameter

Returns: None

_view_approval_details(self, approval_uid)

Purpose: View detailed information about an approval workflow

Parameters:

  • approval_uid: Parameter

Returns: None

_create_audit_tab(self)

Purpose: Create the audit trail tab content

Returns: None

_create_document_viewer(self)

Purpose: Create a document viewer for the current version

Returns: None

_version_selected(self, event)

Purpose: Handle version selection from table

Parameters:

  • event: Parameter

Returns: None

_approval_selected(self, event)

Purpose: Handle approval selection from table

Parameters:

  • event: Parameter

Returns: None

_navigate_back(self, event)

Purpose: Navigate back to dashboard

Parameters:

  • event: Parameter

Returns: None

_view_document(self, event)

Purpose: View the current document version

Parameters:

  • event: Parameter

Returns: None

_download_current_version(self, event)

Purpose: Download the current document version

Parameters:

  • event: Parameter

Returns: None

_show_edit_form(self, event)

Purpose: Show form to edit document metadata

Parameters:

  • event: Parameter

Returns: None

_save_document_changes(self, title, description)

Purpose: Save document metadata changes

Parameters:

  • title: Parameter
  • description: Parameter

Returns: None

_show_upload_form(self, event)

Purpose: Show form to upload a new document version

Parameters:

  • event: Parameter

Returns: None

_upload_new_version(self, file_content, file_name, comment)

Purpose: Upload a new document version using FileCloud for storage

Parameters:

  • file_content: Parameter
  • file_name: Parameter
  • comment: Parameter

Returns: None

_format_date(self, date_str)

Purpose: Format date string for display

Parameters:

  • date_str: Parameter

Returns: None

_get_status_color(self, status_code)

Purpose: Get color for document status

Parameters:

  • status_code: Parameter

Returns: None

_show_review_form(self, event)

Purpose: Show form to start a review cycle

Parameters:

  • event: Parameter

Returns: None

_start_review_cycle(self, reviewer_uids, due_date, review_type, sequential, required_approval_percentage, instructions)

Purpose: Start a new review cycle for the current document

Parameters:

  • reviewer_uids: Parameter
  • due_date: Parameter
  • review_type: Parameter
  • sequential: Parameter
  • required_approval_percentage: Parameter
  • instructions: Parameter

Returns: None

_show_approval_form(self, event)

Purpose: Show the form to start an approval workflow

Parameters:

  • event: Parameter

Returns: None

_create_approver_step(self, title, user_options)

Purpose: Helper to create a step card for approval workflow

Parameters:

  • title: Parameter
  • user_options: Parameter

Returns: None

_get_user_options(self)

Purpose: Get user options for approver selection

Returns: None

_show_publish_form(self, event)

Purpose: Show form to publish the document

Parameters:

  • event: Parameter

Returns: None

_show_archive_form(self, event)

Purpose: Show form to archive the document

Parameters:

  • event: Parameter

Returns: None

_show_clone_form(self, event)

Purpose: Show form to clone the document

Parameters:

  • event: Parameter

Returns: None

_set_as_current_version(self, event)

Purpose: Set the selected version as the current version

Parameters:

  • event: Parameter

Returns: None

_download_selected_version(self, event)

Purpose: Download the selected document version

Parameters:

  • event: Parameter

Returns: None

Required Imports

import logging
import base64
from typing import Dict
from typing import List
from typing import Any

Usage Example

# Example usage:
# result = DocumentDetail(bases)

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class DocumentDetail 98.4% similar

    Document detail view component

    From: /tf/active/vicechatdev/document_detail_backup.py
  • function create_document_detail 58.4% similar

    Factory function that creates and initializes a DocumentDetail panel component, optionally loading a specific document by its UID.

    From: /tf/active/vicechatdev/document_detail_backup.py
  • class DocxMerger 49.7% similar

    A class named DocxMerger

    From: /tf/active/vicechatdev/word_merge.py
  • class PageDetails 49.5% similar

    PageDetails is a minimal data class that inherits from ClientValue, serving as a container for page-related details in the Office365 API context.

    From: /tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/sharepoint/publishing/diagnostics/page_details.py
  • class SharingDetail 47.9% similar

    A data class representing detailed information about how a document or resource was shared in Microsoft 365, including who shared it, when, and through what method.

    From: /tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/directory/insights/sharing_detail.py
← Back to Browse