🔍 Code Extractor

class DocumentDetail_v3

Maturity: 27

Document detail view component

File:
/tf/active/vicechatdev/CDocs single class/ui/document_detail.py
Lines:
69 - 4335
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 details"""
        if not self.document_uid:
            self.notification_area.object = "**Error:** No document ID provided"
            return
            
        try:
            self.notification_area.object = "Loading document..."
            
            # Get document from controller
            from CDocs.controllers.document_controller import get_document
            self.document = get_document(self.document_uid)
            
            if not self.document:
                self.notification_area.object = "**Error:** Document not found"
                return
                
            # Extract document data
            self.doc_title = self.document.get('title', '')
            self.doc_number = self.document.get('doc_number', self.document.get('docNumber', ''))
            self.doc_status = self.document.get('status', '')
            self.doc_owner = self.document.get('owner_name', '')
            
            # Get current version
            versions = self.document.get('versions', [])
            if versions:
                self.current_version = versions[0]  # Most recent version
                
                # Extract current version data
                self.current_version_number = self.current_version.get('version_number', '')
                self.current_version_date = self._format_date(self.current_version.get('created_date', ''))
                self.current_version_comment = self.current_version.get('comment', '')
            else:
                self.current_version = None
            logger.info("collected document data versions ", self.current_version)
            # Get review/approval data
            try:
                # Get active review cycle
                from CDocs.controllers.review_controller import get_document_review_cycles
                reviews = get_document_review_cycles(self.document_uid, include_active_only=True)
                if reviews:
                    self.active_review = reviews['review_cycles'][0] if isinstance(reviews['review_cycles'], list) else None
                else:
                    self.active_review = None
                logger.info(f"collected document data reviews {self.active_review}")
                # Get active approval cycle
                from CDocs.controllers.approval_controller import get_document_approval_cycles
                approvals = get_document_approval_cycles(self.document_uid, include_active_only=True)
                if approvals:
                    self.active_approval = approvals['approval_cycles'][0] if isinstance(approvals['approval_cycles'], list) else None
                else:
                    self.active_approval = None
                logger.info("collected document data approvals ")
            except Exception as e:
                logger.error(f"Error loading workflow data: {e}")
                self.active_review = None
                self.active_approval = None
            #logger.info("collected document data", versions,reviews, approvals)
            # Update UI
            self._update_document_display()
            # Clear notification area
            self.notification_area.object = ""

            return True
            
            
            
        except Exception as e:
            logger.error(f"Error loading document: {e}")
            self.notification_area.object = f"**Error loading document:** {str(e)}"
            return False

    def _update_document_display(self):
        """Update the document display panels"""
        if not self.document:
            return
            
        # Create document header
        header = self._create_document_header()
        #logger.info("collected document data header ", header)
        
        # Create document status display
        #status_display = self._create_status_display()
        
        # Create document action buttons
        action_buttons = self._create_document_action_buttons()
        #logger.info("collected document data action buttons ", action_buttons)
        
        # Create action bar
        action_bar = pn.Row(*action_buttons, sizing_mode='stretch_width')
        
        # Create tabs
        tabs = pn.Tabs(
            ('Details', self._create_details_tab()),
            ('Versions', self._create_versions_tab()),
            ('Reviews', self._create_reviews_tab()),
            ('Approvals', self._create_approvals_tab()),
            ('Audit Trail', self._create_audit_tab())
        )
        #logger.info("collected document data tabs ", tabs)
        # Assemble main content

        #logger.info("ready to update display")
        self.main_content.clear()
        #self.main_content.append(self.notification_area)
        #logger.info("ready to update display header")
        self.main_content.append(header)
        #logger.info("ready to update display action")
        self.main_content.append(action_bar)
        #logger.info("ready to update display divider")
        self.main_content.append(pn.layout.Divider())
        #logger.info("ready to update display tabs")
        self.main_content.append(tabs)

        return
        

    def _create_details_tab(self):
        """Create the details tab for document metadata"""
        if not self.document:
            return pn.Column(pn.pane.Markdown("No document loaded"))
        
        try:
            # Extract key document metadata with fallback options
            doc_number = self._get_field_case_insensitive(self.document, ['doc_number', 'docNumber'])
            title = self._get_field_case_insensitive(self.document, ['title'])
            doc_type = self._get_field_case_insensitive(self.document, ['doc_type', 'docType'])
            department = self._get_field_case_insensitive(self.document, ['department'])
            status = self._get_field_case_insensitive(self.document, ['status'])
            description = self._get_field_case_insensitive(self.document, ['description'])
            created_date = self._format_date(self._get_field_case_insensitive(self.document, ['created_date', 'createdDate']))
            modified_date = self._format_date(self._get_field_case_insensitive(self.document, ['modified_date', 'modifiedDate']))
            effective_date = self._format_date(self._get_field_case_insensitive(self.document, ['effective_date']))
            expiry_date = self._format_date(self._get_field_case_insensitive(self.document, ['expiry_date']))
            owner_name = self._get_field_case_insensitive(self.document, ['owner_name', 'ownerName'])
            
            # Document properties section
            properties_section = pn.Column(
                pn.pane.Markdown("### Document Properties"),
                pn.pane.Markdown(f"**Document Number:** {doc_number}"),
                pn.pane.Markdown(f"**Title:** {title}"),
                pn.pane.Markdown(f"**Type:** {settings.get_document_type_name(doc_type)}"),
                pn.pane.Markdown(f"**Department:** {settings.get_department_name(department)}"),
                pn.pane.Markdown(f"**Status:** {settings.get_document_status_name(status)}"),
                pn.pane.Markdown(f"**Owner:** {owner_name}"),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                width=350
            )
            
            # Dates section
            dates_section = pn.Column(
                pn.pane.Markdown("### Document Timeline"),
                pn.pane.Markdown(f"**Created:** {created_date}"),
                pn.pane.Markdown(f"**Last Modified:** {modified_date}"),
                pn.pane.Markdown(f"**Effective Date:** {effective_date or 'Not set'}"),
                pn.pane.Markdown(f"**Expiry Date:** {expiry_date or 'Not set'}"),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                width=350
            )
            
            # Combine properties and dates in a row
            info_row = pn.Row(
                properties_section,
                pn.Spacer(width=20),
                dates_section,
                sizing_mode='stretch_width'
            )
            
            # Description section
            description_section = pn.Column(
                pn.pane.Markdown("### Description"),
                pn.pane.Markdown(description or "No description available"),
                styles={'background':'#f8f9fa'},
                css_classes=['p-3', 'border', 'rounded'],
                sizing_mode='stretch_width'
            )
            
            # Current version preview if available
            preview_section = pn.Column(
                pn.pane.Markdown("### Document Preview"),
                sizing_mode='stretch_width',
                height=500
            )
            
            if hasattr(self, 'current_version') and self.current_version:
                try:
                    # Create preview - depending on document type, adjust rendering approach
                    preview_section.append(
                        pn.pane.Markdown("Document preview will be shown here")
                    )
                except Exception as e:
                    preview_section.append(
                        pn.pane.Markdown(f"**Error loading preview:** {str(e)}")
                    )
            else:
                preview_section.append(
                    pn.pane.Markdown("*No document version available for preview*")
                )
            
            # Create layout
            details_layout = pn.Column(
                info_row,
                pn.layout.Divider(),
                description_section,
                pn.layout.Divider(),
                preview_section,
                sizing_mode='stretch_width'
            )
            
            return details_layout
            
        except Exception as e:
            logger.error(f"Error creating details tab: {e}")
            logger.error(traceback.format_exc())
            return pn.Column(pn.pane.Markdown(f"**Error loading details:** {str(e)}"))

    def _get_field_case_insensitive(self, doc_dict, field_names):
        """
        Get a field value from a document dictionary using case-insensitive matching
        and multiple possible field names.
        
        Parameters
        ----------
        doc_dict : dict
            Document dictionary
        field_names : list
            List of possible field names
                
        Returns
        -------
        str
            Field value if found, empty string otherwise
        """
        if not doc_dict or not field_names:
            return ''
                
        # Convert dictionary keys to lowercase for case-insensitive comparison
        lower_dict = {k.lower(): v for k, v in doc_dict.items()}
            
        # Try each field name in order
        for name in field_names:
            if name.lower() in lower_dict and lower_dict[name.lower()]:
                return lower_dict[name.lower()]
            
        # If no match found, try direct access as fallback
        for name in field_names:
            if name in doc_dict and doc_dict[name]:
                return doc_dict[name]
                    
        # Return empty string if no match found
        return ''
    
    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=100)
            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=100)
                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=100)
            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=100)
            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=100)
            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=100)
            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=100)
            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=100)
            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=100)
            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=100)
            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 _review_selected(self, event=None,review_uid=None):
        """Handle review selection from table with support for both selection and cell click events"""
        try:
            if review_uid:
                self._selected_review_uid=review_uid
            else:
                # 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 _load_approval_details_by_uid(self, approval_uid):
        """Helper method to refresh approval 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_approval_uid = approval_uid
            
            # Re-trigger the review selection handler
            self._approval_selected(dummy_event)
            
        except Exception as e:
            logger.error(f"Error refreshing approval details: {e}")
            self.notification_area.object = f"**Error refreshing approval details:** {str(e)}"

    def _create_document_header(self):
        """
        Creates a header section for the document with key metadata.
        
        Returns:
            pn.Column: A column containing the document header elements
        """
        try:
            # Make sure we have document data
            if not self.document:
                logger.warning("Attempted to create document header with no document data")
                return pn.Column(pn.pane.Markdown("# Document Not Available"))
            
            doc = self.document
            
            # Get document metadata with fallbacks for different field names
            doc_number = doc.get('doc_number', doc.get('docNumber', 'N/A'))
            title = doc.get('title', 'Untitled Document')
            doc_type = doc.get('doc_type', doc.get('docType', ''))
            status = doc.get('status', 'DRAFT')
            department = doc.get('department', 'N/A')
            revision = doc.get('revision', doc.get('version', '1.0'))
            
            # Get document owner information
            owner_name = "Unknown"
            if 'owner' in doc and doc['owner']:
                owner_name = doc['owner'].get('name', "Unknown")
            elif 'owner_uid' in doc and doc['owner_uid']:
                owner_name = doc.get('owner_name', "Unknown")
            
            # Create header section
            header = pn.Column(sizing_mode='stretch_width')
            
            # Document title
            header.append(pn.pane.Markdown(f"# {title}"))
            
            # Document metadata
            metadata_section = pn.Row(
                pn.Column(
                    pn.pane.Markdown(f"**Document Number:** {doc_number}"),
                    pn.pane.Markdown(f"**Type:** {self._format_document_type(doc_type)}"),
                    pn.pane.Markdown(f"**Owner:** {owner_name}"),
                    width=300
                ),
                pn.Column(
                    pn.pane.Markdown(f"**Status:** {self._format_document_status(status)}"),
                    pn.pane.Markdown(f"**Department:** {self._format_department(department)}"),
                    pn.pane.Markdown(f"**Revision:** {revision}"),
                    width=300
                ),
                sizing_mode='stretch_width'
            )
            
            header.append(metadata_section)
            
            # Add status indicator/badge
            status_color = settings.get_status_color(status)
            status_name = settings.get_document_status_name(status)
            
            #status_badge = pn.pane.HTML(
            #    f"""<span class="badge" style="background-color: {status_color}; 
            #    padding: 8px 16px; font-size: 14px; border-radius: 16px; color: white; 
            #    font-weight: bold;">{status_name}</span>""",
            #    width=150
            #)

            status_badge = self._create_status_display(status)
            
            header.append(status_badge)
            
            # Add document description if available
            if 'description' in doc and doc['description']:
                header.append(pn.pane.Markdown(f"**Description:** {doc['description']}"))
                
            # Add a separator
            header.append(pn.pane.HTML("<hr style='margin: 15px 0;'>"))
            
            return header
            
        except Exception as e:
            logger.error(f"Error creating document header: {e}")
            logger.error(traceback.format_exc())
            return pn.Column(pn.pane.Markdown("# Error Loading Document Header"))
    
    def _format_document_type(self, doc_type: str) -> str:
        """Format document type code to display name."""
        return settings.get_document_type_name(doc_type)
    
    def _format_document_status(self, status: str) -> str:
        """Format document status code to display name."""
        return settings.get_document_status_name(status)
    
    def _format_department(self, department: str) -> str:
        """Format department code to display name."""
        return settings.get_department_name(department)

    def _create_status_display(self, status):
        """
        Create a status display indicator/badge for a document status.
        
        Args:
            status: The document status code
            
        Returns:
            pn.pane.HTML: An HTML pane containing the status badge
        """
        try:
            # Get status color and name from settings
            status_color = settings.get_status_color(status)
            status_name = settings.get_document_status_name(status)
            
            # Create HTML badge with appropriate styling
            html = f"""
            <span class="badge" style="background-color: {status_color}; 
            padding: 8px 16px; font-size: 14px; border-radius: 16px; color: white; 
            font-weight: bold;">{status_name}</span>
            """
            
            # Return as HTML pane
            return pn.pane.HTML(html, width=150)
        
        except Exception as e:
            logger.error(f"Error creating status display: {e}")
            return pn.pane.HTML(f"<span class='badge bg-secondary'>Unknown</span>")

    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_reviews_tab(self):
        """Create the reviews tab showing all review cycles for this document"""
        try:
            from CDocs.controllers.review_controller import get_document_review_cycles
            
            # Get all review cycles for this document
            reviews = get_document_review_cycles(self.document_uid)

            # Convert Neo4j DateTime objects
            reviews = self._convert_neo4j_datetimes(reviews)
            
            if not reviews['review_cycles'] or not isinstance(reviews['review_cycles'], list) or len(reviews['review_cycles']) == 0:
                return pn.pane.Markdown("No reviews found for this document.")
            
            # Create a dataframe for display
            reviews_data = []
            for review in reviews['review_cycles']:
                # Extract data with fallbacks for different field names
                status = review.get('status', '')
                review_type = review.get('review_type', '')
                start_date = self._format_date(review.get('startDate', review.get('start_date', '')))
                due_date = self._format_date(review.get('dueDate', review.get('due_date', '')))
                completion_date = self._format_date(review.get('completionDate', review.get('completed_at', '')))
                initiated_by = review.get('initiated_by_name', '')
                
                # Get reviewer count with fallbacks
                reviewer_count = 0
                if 'reviewer_assignments' in review:
                    reviewer_count = len(review.get('reviewer_assignments', []))
                elif 'reviewers' in review:
                    reviewer_count = len(review.get('reviewers', []))
                    
                # Add to data
                reviews_data.append({
                    'UID': review.get('UID'),
                    'Status': status,
                    'Type': review_type,
                    'Started': start_date,
                    'Due': due_date,
                    'Completed': completion_date,
                    'Initiated By': initiated_by,
                    'Reviewers': reviewer_count
                })
            
            # Create DataFrame
            df = pd.DataFrame(reviews_data)
            
            # Create selection callback that handles both event types
            def selection_callback(event):
                # Get the selected review's UID
                selected_uid = None
                
                # For TabulatorEvents with .new attribute (Panel versions < 1.0)
                if hasattr(event, 'new') and event.new:
                    selected_uid = event.new[0]['UID']
                
                # For CellClickEvent with .row attribute (newer Panel versions)
                elif hasattr(event, 'row') and event.row is not None:
                    row_index = event.row
                    if 0 <= row_index < len(df):
                        selected_uid = df.iloc[row_index]['UID']
                
                # For selection events
                elif hasattr(event, 'selected') and event.selected:
                    rows = event.selected
                    if rows and len(rows) > 0:
                        row_index = rows[0]
                        if 0 <= row_index < len(df):
                            selected_uid = df.iloc[row_index]['UID']
                
                # Navigate to the review details by loading the review panel
                if selected_uid:
                    self._review_selected(review_uid=selected_uid)  
                    
            # Create tabulator for display
            reviews_table = Tabulator(
                df,
                selectable=True,
                height=400,
                layout='fit_columns',
                sizing_mode='stretch_width',
                header_filters=True,
                hidden_columns=['UID']  # Hide UID column
            )
            
            # Add selection callback
            reviews_table.on_click(selection_callback)
            
            # Create container
            reviews_tab = pn.Column(
                pn.pane.Markdown("## Document Reviews"),
                reviews_table,
                sizing_mode='stretch_width'
            )
            
            return reviews_tab
            
        except Exception as e:
            logger.error(f"Error creating reviews tab: {e}")
            return pn.pane.Markdown(f"**Error loading reviews:** {str(e)}")
    
    def _create_approvals_tab(self):
        """Create the approvals tab showing all approval cycles for this document"""
        try:
            from CDocs.controllers.approval_controller import get_document_approval_cycles
            
            # Get all approval cycles for this document
            approvals = get_document_approval_cycles(self.document_uid)
            
            if not approvals or not isinstance(approvals, list) or len(approvals) == 0:
                return pn.pane.Markdown("No approvals found for this document.")
            
            # Create a dataframe for display
            approvals_data = []
            for approval in approvals:
                # Extract data with fallbacks for different field names
                status = approval.get('status', '')
                approval_type = approval.get('approval_type', '')
                sequential = "Sequential" if approval.get('sequential', True) else "Parallel"
                start_date = self._format_date(approval.get('startDate', approval.get('start_date', '')))
                due_date = self._format_date(approval.get('dueDate', approval.get('due_date', '')))
                completion_date = self._format_date(approval.get('completionDate', approval.get('completed_at', '')))
                initiated_by = approval.get('initiated_by_name', '')
                
                # Get approver count with fallbacks
                approver_count = 0
                if 'approver_assignments' in approval:
                    approver_count = len(approval.get('approver_assignments', []))
                elif 'approvers' in approval:
                    approver_count = len(approval.get('approvers', []))
                    
                # Add to data
                approvals_data.append({
                    'UID': approval.get('UID'),
                    'Status': status,
                    'Type': approval_type,
                    'Flow': sequential,
                    'Started': start_date,
                    'Due': due_date,
                    'Completed': completion_date,
                    'Initiated By': initiated_by,
                    'Approvers': approver_count
                })
            
            # Create DataFrame
            df = pd.DataFrame(approvals_data)

            # Create selection callback that handles both event types
            def selection_callback(event):
                # Get the selected review's UID
                selected_uid = None
                
                # For TabulatorEvents with .new attribute (Panel versions < 1.0)
                if hasattr(event, 'new') and event.new:
                    selected_uid = event.new[0]['UID']
                
                # For CellClickEvent with .row attribute (newer Panel versions)
                elif hasattr(event, 'row') and event.row is not None:
                    row_index = event.row
                    if 0 <= row_index < len(df):
                        selected_uid = df.iloc[row_index]['UID']
                
                # For selection events
                elif hasattr(event, 'selected') and event.selected:
                    rows = event.selected
                    if rows and len(rows) > 0:
                        row_index = rows[0]
                        if 0 <= row_index < len(df):
                            selected_uid = df.iloc[row_index]['UID']
                
                # Navigate to the review page if we found a UID
                if selected_uid:
                    self._load_approval_details_by_uid(selected_uid)
            
                    
            # Create tabulator for display
            approvals_table = Tabulator(
                df,
                selectable=True,
                height=400,
                layout='fit_columns',
                sizing_mode='stretch_width',
                header_filters=True,
                hidden_columns=['UID']  # Hide UID column
            )
            
            # Add selection callback
            approvals_table.on_click(selection_callback)
            
            # Create container
            approvals_tab = pn.Column(
                pn.pane.Markdown("## Document Approvals"),
                approvals_table,
                sizing_mode='stretch_width'
            )
            
            return approvals_tab
            
        except Exception as e:
            logger.error(f"Error creating approvals tab: {e}")
            return pn.pane.Markdown(f"**Error loading approvals:** {str(e)}")
    
    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)
    
    def _show_review_form(self, event=None):
        """Show form to start a review cycle"""
        self.notification_area.object = "Loading review form..."
        import time
        time.sleep(5)
        
        try:
            # Create form elements
            from CDocs.models.user_extensions import DocUser
            
            # Get all users who can be reviewers
            reviewers = []
            try:
                # Get users with REVIEW_DOCUMENT permission or REVIEWER role
                reviewers = DocUser.get_users_by_role(role="REVIEWER")
                if not reviewers:
                    reviewers = DocUser.get_users_by_permission("REVIEW_DOCUMENT")
                            
            except Exception as e:
                logger.error(f"Error fetching reviewers: {e}")
                reviewers = []
            # Create reviewer selection options dictionary
            reviewer_options = {f"{r.name} ({r.username})": r.uid for r in reviewers}
    
            reviewer_select = pn.widgets.MultiSelect(
                name="Select Reviewers",
                options=reviewer_options,
                value=[],
                size=6,
                width=400
            ) 
            
            # Create due date picker (default to 2 weeks from now)
            default_due_date = (datetime.now() + timedelta(days=settings.DEFAULT_REVIEW_PERIOD_DAYS)).date()
            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=settings.REVIEW_TYPES[0] if settings.REVIEW_TYPES else "STANDARD",
                width=200
            )
            
            # Create sequential checkbox
            sequential_check = pn.widgets.Checkbox(
                name="Sequential Review",
                value=settings.SEQUENTIAL_REVIEW_DEFAULT,
            )
            
            # Required approval percentage (default 100%)
            approval_pct = pn.widgets.IntSlider(
                name="Required Approval Percentage",
                start=1,
                end=100,
                value=settings.REQUIRED_APPROVAL_PERCENTAGE_DEFAULT,
                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
            )
            logger.info("form created")
            # Set up event handlers
            start_btn.on_click(lambda event: self._start_review_cycle(
                reviewer_uids=list(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
            self.main_content.clear()
            self.main_content.append(review_form)

            
            # Clear notification
            self.notification_area.object = ""

        except Exception as e:
            logger.error(f"Error showing review form: {e}")
            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 due 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:
            # Get current document version UID
            if not self.current_version or not self.current_version.get('UID'):
                self.notification_area.object = "**Error:** No current document version found"
                return
            
            version_uid = self.current_version.get('UID')
            
            # Call controller to create review cycle
            from CDocs.controllers.review_controller import create_review_cycle
            result = create_review_cycle(
                user=self.user,
                document_version_uid=version_uid,
                reviewers=reviewer_uids,
                due_date=due_date,
                instructions=instructions,
                sequential=sequential,
                required_approval_percentage=required_approval_percentage,
                review_type=review_type
            )
            
            if result and isinstance(result, dict):
                self.notification_area.object = "**Review cycle started successfully!**"
                
                # Update document status if needed
                if self.doc_status == "DRAFT":
                    from CDocs.controllers.document_controller import update_document
                    update_result = update_document(
                        user=self.user,
                        document_uid=self.document_uid,
                        status="IN_REVIEW"
                    )
                    if not update_result or not update_result.get('success', False):
                        self.notification_area.object += "\nWarning: Document status could not be updated."
                
                # Reload document to show updated status
                pn.state.onload(lambda: self._load_document())
            else:
                self.notification_area.object = f"**Error starting review cycle:** {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
        from CDocs.models.user_extensions import DocUser
        try:
            # Get users with APPROVE_DOCUMENT permission or APPROVER role
            potential_approvers = DocUser.get_users_by_role(role="APPROVER")
            if not potential_approvers:
                potential_approvers = DocUser.get_users_by_permission("APPROVE_DOCUMENT")
                
            user_options = {f"{u.name} ({u.username})": u.uid for u in potential_approvers}
            
            if not user_options:
                self.notification_area.object = "❌ No users with approval permission found"
                return
        except Exception as e:
            logger.error(f"Error getting users: {e}")
            self.notification_area.object = f"❌ Error loading approvers: {str(e)}"
            return
    
        # 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=settings.DEFAULT_APPROVAL_PERIOD_DAYS)).date()
        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 sequential checkbox
        sequential_check = pn.widgets.Checkbox(
            name="Sequential Approval Flow",
            value=True  # Default is sequential for approvals
        )
        
        # First step (always required)
        approver_step1 = self._create_approver_step("Step 1 (Required)", user_options)
        step_cards = [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 cancel button
        cancel_btn = pn.widgets.Button(name='Cancel', button_type='default', width=120)
        
        # 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,
            sequential_check,
            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),
            pn.Row(
                cancel_btn,
                submit_btn,
                align='end'
            ),
            sizing_mode='stretch_width'
        )
        
        # Handle submission
        def submit_approval(event):
            try:
                self.notification_area.object = "⏳ Starting approval workflow..."
        
                # Get current version UID
                if not self.current_version or not self.current_version.get('UID'):
                    self.notification_area.object = "**Error:** No current document version found"
                    return
                    
                version_uid = self.current_version.get('UID')
                
                # Collect approvers from each step
                steps_data = []
                for i, step_card in enumerate(step_cards, 1):
                    # Get the widgets from the object attributes
                    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 = list(approvers_select.value)
                    
                    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
                    steps_data.append({
                        'approvers': [{'user_uid': uid} for uid in selected_uids],
                        'all_must_approve': all_approve_check.value,
                        'step_number': i,
                        'required_approvals': len(selected_uids) if all_approve_check.value else 1
                    })
                
                # Call API to create approval workflow
                from CDocs.controllers.approval_controller import create_approval_workflow
                
                # Check if we're using the new multi-step API
                import inspect
                sig = inspect.signature(create_approval_workflow)
                if 'steps' in sig.parameters:
                    # Using new multi-step API
                    result = create_approval_workflow(
                        user=self.user,
                        document_version_uid=version_uid,
                        steps=steps_data,
                        workflow_type=workflow_type.value,
                        due_date=due_date.value,
                        instructions=instructions.value,
                        sequential=sequential_check.value
                    )
                else:
                    # Using older API - flatten approvers from all steps
                    all_approvers = []
                    for step in steps_data:
                        all_approvers.extend([a['user_uid'] for a in step['approvers']])
                    
                    result = create_approval_workflow(
                        user=self.user,
                        document_version_uid=version_uid,
                        approvers=all_approvers,
                        due_date=due_date.value,
                        instructions=instructions.value,
                        sequential=sequential_check.value
                    )
                
                if result and (result.get('success') or 'UID' in result):
                    self.notification_area.object = "✅ Approval workflow started successfully"
                    
                    # Update document status if needed
                    if self.doc_status == "DRAFT" or self.doc_status == "IN_REVIEW":
                        from CDocs.controllers.document_controller import update_document
                        update_result = update_document(
                            user=self.user,
                            document_uid=self.document_uid,
                            status="IN_APPROVAL"
                        )
                        if not update_result or not update_result.get('success', False):
                            self.notification_area.object += "\nWarning: Document status could not be updated."
                    
                    # Reload document to show updated status
                    pn.state.onload(lambda: 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())
        
        # Set up event handlers
        submit_btn.on_click(submit_approval)
        cancel_btn.on_click(self._load_document)
        
        # 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,
            width=350
        )
        
        # 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
        step_card.approvers_select = approvers_select
        step_card.all_approve_check = all_approve_check
        
        return step_card
    
    def _create_document_action_buttons(self):
        """Create buttons for document actions based on permissions and status"""
        buttons = []
        
        # Always show view button if we have a current version
        if self.current_version:
            view_btn = Button(name="View Document", button_type="primary")
            view_btn.on_click(self._view_document)
            buttons.append(view_btn)
        
        # Show action buttons based on permissions and document status
        if self.user:
            # Check permissions
            can_edit = permissions.user_has_permission(self.user, "EDIT_DOCUMENT")
            can_review = permissions.user_has_permission(self.user, "INITIATE_REVIEW")
            can_approve = permissions.user_has_permission(self.user, "INITIATE_APPROVAL")
            can_publish = permissions.user_has_permission(self.user, "PUBLISH_DOCUMENT")
            can_archive = permissions.user_has_permission(self.user, "ARCHIVE_DOCUMENT")
            
            # Show Upload New Version button if document is not archived/obsolete
            if can_edit and self.doc_status not in ["ARCHIVED", "OBSOLETE"]:
                upload_btn = Button(name="Upload New Version", button_type="default")
                upload_btn.on_click(self._show_upload_form)
                buttons.append(upload_btn)
            
            # Show Edit Metadata button
            if can_edit:
                edit_btn = Button(name="Edit Metadata", button_type="default")
                edit_btn.on_click(self._show_edit_form)
                buttons.append(edit_btn)
            
            # Show Start Review button if document is in DRAFT
            if can_review and self.doc_status == "DRAFT":
                review_btn = Button(name="Start Review", button_type="warning")
                review_btn.on_click(self._show_review_form)
                buttons.append(review_btn)
            
            # Show Start Approval button if document is in DRAFT or IN_REVIEW
            if can_approve and self.doc_status in ["DRAFT", "IN_REVIEW"]:
                approve_btn = Button(name="Start Approval", button_type="warning")
                approve_btn.on_click(self._show_approval_form)
                buttons.append(approve_btn)
            
            # Show View Current Review button if in review
            if self.doc_status == "IN_REVIEW":
                view_review_btn = Button(name="View Review", button_type="light")
                view_review_btn.on_click(self._view_current_review)
                buttons.append(view_review_btn)
            
            # Show View Current Approval button if in approval
            if self.doc_status == "IN_APPROVAL":
                view_approval_btn = Button(name="View Approval", button_type="light")
                view_approval_btn.on_click(self._view_current_approval)
                buttons.append(view_approval_btn)
            
            # Show Publish button if document is approved
            if can_publish and self.doc_status == "APPROVED":
                publish_btn = Button(name="Publish", button_type="success")
                publish_btn.on_click(self._show_publish_form)
                buttons.append(publish_btn)
            
            # Show Archive button if document is published
            if can_archive and self.doc_status == "PUBLISHED":
                archive_btn = Button(name="Archive", button_type="danger")
                archive_btn.on_click(self._show_archive_form)
                buttons.append(archive_btn)

            # Show Clone button if document is not archived/obsolete
            if self.doc_status not in ["ARCHIVED", "OBSOLETE"]:
                clone_btn = Button(name="Clone Document", button_type="default")
                clone_btn.on_click(self._show_clone_form)
                buttons.append(clone_btn)   

            # Show Convert button if document is not archived/obsolete
            if self.doc_status not in ["ARCHIVED", "OBSOLETE"]:
                convert_btn = Button(name="Convert to PDF", button_type="default")
                convert_btn.on_click(self._convert_to_pdf)
                buttons.append(convert_btn)


        
        return buttons

    def _view_current_review(self, event=None):
        """Navigate to review panel for current document"""
        if not self.document_uid:
            self.notification_area.object = "**Error:** No document selected"
            return
            
        try:
            # Get latest review cycle
            from CDocs.controllers.review_controller import get_document_review_cycles
            reviews = get_document_review_cycles(self.document_uid, include_active_only=True)
            
            if not reviews or not isinstance(reviews, list) or len(reviews) == 0:
                self.notification_area.object = "No active review found for this document"
                return
                
            # Sort by start date descending and take the most recent
            if len(reviews) > 1:
                reviews.sort(key=lambda x: x.get('startDate', ''), reverse=True)
                
            review_uid = reviews[0].get('UID')
            
            # Navigate to review panel with specific review UID
            url = f"/review/{review_uid}"
            pn.state.location.pathname = url
            
        except Exception as e:
            logger.error(f"Error navigating to review: {e}")
            self.notification_area.object = f"**Error navigating to review:** {str(e)}"
    
    def _view_current_approval(self, event=None):
        """Navigate to approval panel for current document"""
        if not self.document_uid:
            self.notification_area.object = "**Error:** No document selected"
            return
            
        try:
            # Get latest approval cycle
            from CDocs.controllers.approval_controller import get_document_approval_cycles
            approvals = get_document_approval_cycles(self.document_uid, include_active_only=True)
            
            if not approvals or not isinstance(approvals, list) or len(approvals) == 0:
                self.notification_area.object = "No active approval found for this document"
                return
                
            # Sort by start date descending and take the most recent
            if len(approvals) > 1:
                approvals.sort(key=lambda x: x.get('startDate', ''), reverse=True)
                
            approval_uid = approvals[0].get('UID')
            
            # Navigate to approval panel with specific approval UID
            url = f"/approval/{approval_uid}"
            pn.state.location.pathname = url
            
        except Exception as e:
            logger.error(f"Error navigating to approval: {e}")
            self.notification_area.object = f"**Error navigating to approval:** {str(e)}"
    
    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 details

Parameters:

  • event: Parameter

Returns: None

_update_document_display(self)

Purpose: Update the document display panels

Returns: None

_create_details_tab(self)

Purpose: Create the details tab for document metadata

Returns: None

_get_field_case_insensitive(self, doc_dict, field_names)

Purpose: Get a field value from a document dictionary using case-insensitive matching and multiple possible field names. Parameters ---------- doc_dict : dict Document dictionary field_names : list List of possible field names Returns ------- str Field value if found, empty string otherwise

Parameters:

  • doc_dict: Parameter
  • field_names: Parameter

Returns: See docstring for return details

_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

_review_selected(self, event, review_uid)

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

Parameters:

  • event: Parameter
  • review_uid: 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

_load_approval_details_by_uid(self, approval_uid)

Purpose: Helper method to refresh approval details after an action

Parameters:

  • approval_uid: Parameter

Returns: None

_create_document_header(self)

Purpose: Creates a header section for the document with key metadata. Returns: pn.Column: A column containing the document header elements

Returns: See docstring for return details

_format_document_type(self, doc_type) -> str

Purpose: Format document type code to display name.

Parameters:

  • doc_type: Type: str

Returns: Returns str

_format_document_status(self, status) -> str

Purpose: Format document status code to display name.

Parameters:

  • status: Type: str

Returns: Returns str

_format_department(self, department) -> str

Purpose: Format department code to display name.

Parameters:

  • department: Type: str

Returns: Returns str

_create_status_display(self, status)

Purpose: Create a status display indicator/badge for a document status. Args: status: The document status code Returns: pn.pane.HTML: An HTML pane containing the status badge

Parameters:

  • status: Parameter

Returns: See docstring for return details

_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_reviews_tab(self)

Purpose: Create the reviews tab showing all review cycles for this document

Returns: None

_create_approvals_tab(self)

Purpose: Create the approvals tab showing all approval cycles for this document

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

_create_document_action_buttons(self)

Purpose: Create buttons for document actions based on permissions and status

Returns: None

_view_current_review(self, event)

Purpose: Navigate to review panel for current document

Parameters:

  • event: Parameter

Returns: None

_view_current_approval(self, event)

Purpose: Navigate to approval panel for current document

Parameters:

  • event: 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_v1 97.9% similar

    Document detail view component

    From: /tf/active/vicechatdev/document_detail_old.py
  • class DocumentDetail_v2 97.5% similar

    Document detail view component

    From: /tf/active/vicechatdev/CDocs/ui/document_detail.py
  • class DocumentDetail 97.3% similar

    Document detail view component

    From: /tf/active/vicechatdev/document_detail_backup.py
  • class DocumentDashboard_v1 58.4% similar

    Dashboard for viewing and managing controlled documents.

    From: /tf/active/vicechatdev/CDocs single class/ui/document_dashboard.py
  • function create_document_detail 57.6% 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
← Back to Browse