🔍 Code Extractor

class DocumentDashboard_v1

Maturity: 28

Dashboard for viewing and managing controlled documents.

File:
/tf/active/vicechatdev/CDocs single class/ui/document_dashboard.py
Lines:
37 - 1028
Complexity:
moderate

Purpose

Dashboard for viewing and managing controlled documents.

Source Code

class DocumentDashboard(param.Parameterized):
    """Dashboard for viewing and managing controlled documents."""
    
    doc_selected = param.String(default='')
    search_query = param.String(default='')
    doc_type_filter = param.Parameter()  # Use Parameter to accept any type of value
    department_filter = param.String(default='')
    status_filter = param.String(default='')
    date_from = param.Date(default=None)
    date_to = param.Date(default=None)
    
    def __init__(self, parent_app=None, **params):
        super().__init__(**params)
        self.parent_app = parent_app
        self.user = None  # Will be set by parent app
        self.document_list = None
        self._view_handler_added = False
        
        # Initialize template attribute to None - avoid AttributeError
        self.template = None
        
        # Initialize notification area
        self.notification_area = pn.pane.Markdown("")
        
        # Add this line to initialize the document_list_container
        self.document_list_container = pn.Column(sizing_mode='stretch_width')
        
        # Initialize document type and status lists
        from CDocs.config import settings
        
        # Use full names for document types in dropdowns
        self.document_types = list(settings.DOCUMENT_TYPES.keys())
        
        # Use full names for statuses in dropdowns
        self.document_statuses = list(settings.DOCUMENT_STATUSES.keys())
        
        # Remove reference to DEFAULT_DEPARTMENTS and use DEPARTMENTS instead
        self.departments = list(settings.DEPARTMENTS.keys())
        
        # Create search and filter widgets
        self.search_input = TextInput(name="Search", placeholder="Search documents...", width=200)
        
        # Create document type filter - use full names
        doc_types = [''] + self.document_types
        self.doc_type_select = Select(name="Document Type", options=doc_types, width=200)
        
        # Create status filter
        statuses = [''] + self.document_statuses
        self.status_select = Select(name="Status", options=statuses, width=200)
        
        # Create department filter - use full names
        departments = [''] + self.departments
        self.department_select = Select(name="Department", options=departments, width=200)
        
        # Create date filters
        self.date_from_picker = DatePicker(name="From Date", width=200)
        self.date_to_picker = DatePicker(name="To Date", width=200)
        
        # Store filters for later access
        self.filters = {
            'search': self.search_input,
            'doc_type': self.doc_type_select,
            'status': self.status_select,
            'department': self.department_select,
            'date_from': self.date_from_picker,
            'date_to': self.date_to_picker
        }
        
        # Initialize documents DataFrame if pandas is available
        if HAS_PANDAS:
            self.documents_df = pd.DataFrame()
        
        # Create button for new document
        self.create_doc_btn = Button(
            name='Create New Document',
            button_type='primary',
            width=150,
            disabled=True  # Default to disabled until user is set
        )
        self.create_doc_btn.on_click(self.show_create_document_form)
        
        # Add this line to track event registrations
        self._event_handlers_registered = False

    def set_user(self, user):
        """Set the current user from parent application"""
        self.user = user
        
        # Update UI elements that depend on user permissions
        self._update_ui_for_user()
        
        # Modified: Check if template attribute exists before using it
        # The template attribute might not be initialized in this class
        if hasattr(self, 'template') and self.template:
            self._setup_sidebar()
        else:
            logger.debug("No template attribute available, skipping sidebar setup")
        
        # Load initial documents
        self.update_document_list()
    
    def _update_ui_for_user(self):
        """Update UI components based on current user permissions"""
        # Example: Update create button based on permissions
        if hasattr(self, 'create_doc_btn'):
            # Enable button if user has permission or if no permissions system
            has_permission = True
            if hasattr(permissions, 'user_has_permission'):
                has_permission = permissions.user_has_permission(self.user, "CREATE_DOCUMENT")
            self.create_doc_btn.disabled = not has_permission
    
    def _setup_sidebar(self):
        """Set up the sidebar filters and actions."""
        try:
            # Check if template exists and has a sidebar
            if not hasattr(self, 'template') or not self.template or not hasattr(self.template, 'sidebar'):
                logger.warning("No template sidebar available for setup")
                return
                
            # Create sidebar section for document management
            self.template.sidebar.append(pn.pane.Markdown("## Document Management"))
            
            # Add the create document button to sidebar
            self.template.sidebar.append(self.create_doc_btn)
            
            # Create sidebar section for filters
            self.template.sidebar.append(pn.pane.Markdown("## Filters"))
            
            # Add widgets to sidebar
            self.template.sidebar.append(self.search_input)
            self.template.sidebar.append(self.doc_type_select)
            self.template.sidebar.append(self.status_select)
            self.template.sidebar.append(self.department_select)
            self.template.sidebar.append(self.date_from_picker)
            self.template.sidebar.append(self.date_to_picker)
            
            # Create button to apply filters
            apply_btn = pn.widgets.Button(
                name="Apply Filters",
                button_type="default",
                width=200
            )
            apply_btn.on_click(self._apply_filters)
            self.template.sidebar.append(apply_btn)
            
            # Create button to clear filters
            clear_btn = pn.widgets.Button(
                name="Clear Filters",
                button_type="default",
                width=200
            )
            clear_btn.on_click(self._clear_filters)
            self.template.sidebar.append(clear_btn)
            
        except Exception as e:
            logger.error(f"Error setting up sidebar: {e}")
            import traceback
            logger.error(traceback.format_exc())
    
    def get_dashboard_view(self):
        """Get the dashboard view with document list and actions."""
        try:
            # Create dashboard container
            dashboard = pn.Column(sizing_mode='stretch_width')
            
            # Add page header
            dashboard.append(pn.pane.Markdown("# Document Dashboard"))
            
            # Add notification area
            dashboard.append(self.notification_area)
            
            # Add toolbar with actions
            toolbar = pn.Row(
                self.create_doc_btn,
                pn.widgets.Button(name="Refresh", button_type="default", width=100, 
                                 on_click=self.update_document_list),
                pn.layout.HSpacer(),
                sizing_mode='stretch_width'
            )
            dashboard.append(toolbar)
            
            # Add horizontal filter bar for convenience
            filters = pn.Row(
                self.search_input,
                self.doc_type_select,
                self.status_select,
                pn.widgets.Button(name="Apply", button_type="default", width=60, 
                                 on_click=self._apply_filters),
                pn.widgets.Button(name="Reset", button_type="default", width=60, 
                                 on_click=self._clear_filters),
                pn.layout.HSpacer(),
                sizing_mode='stretch_width'
            )
            dashboard.append(filters)
            
            # Make sure document_list_container exists
            if not hasattr(self, 'document_list_container'):
                self.document_list_container = pn.Column(sizing_mode='stretch_width')
                
            # If document_list doesn't exist or document_list_container is empty,
            # initialize with a message and trigger an update
            if self.document_list is None or len(self.document_list_container) == 0:
                self.document_list_container.clear()
                self.document_list_container.append(pn.pane.Markdown("## Documents"))
                self.document_list_container.append(pn.pane.Markdown("Loading documents..."))
                # Trigger update_document_list after a short delay to ensure the UI is rendered
                pn.state.onload(lambda: self.update_document_list())
                    
            # Add document list container to dashboard
            dashboard.append(self.document_list_container)
            
            return dashboard
            
        except Exception as e:
            logger.error(f"Error creating dashboard view: {e}")
            import traceback
            logger.error(f"Traceback: {traceback.format_exc()}")
            
            # Return error message
            return pn.Column(
                pn.pane.Markdown("# Document Dashboard"),
                pn.pane.Markdown(f"**Error loading dashboard: {str(e)}**"),
                pn.widgets.Button(name="Retry", button_type="default", width=100, 
                                 on_click=lambda event: self.get_dashboard_view())
            )

    def _create_document_list(self):
        """Create the document list component."""
        try:
            import panel as pn
            
            # Initialize with empty pandas DataFrame
            import pandas as pd
            empty_df = pd.DataFrame(columns=[
                'doc_number', 'title', 'doc_type', 'status', 'created_date', 'action'
            ])
            
            # Create tabulator with pandas DataFrame - using proper pagination value
            self.document_list = pn.widgets.Tabulator(
                empty_df,
                formatters={'action': 'html'},
                pagination="local",  # Use "local" instead of True
                page_size=10,
                sizing_mode="stretch_width",
                height=400,
                selectable=True
            )
            
            # Add click handler for row selection
            self.document_list.on_click(self.document_selected)
                
        except Exception as e:
            logger.error(f"Error creating document list: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self.document_list = None
            
            # Create a fallback table using markdown
            md_table = """
            | Document Number | Title | Type | Status | Created |
            |---|---|---|---|---|
            | No documents found | | | | |
            """
            self.document_list = pn.pane.Markdown(md_table)

    #@guard_execution(cooldown_ms=1000)
    def update_document_list(self, event=None):
        """Update the document list with current filters."""
        logger.debug("Updating document list")
        
        try:
            # Show loading message
            self.notification_area.object = "Loading documents..."
            
            # Clear document list container
            if not hasattr(self, 'document_list_container'):
                self.document_list_container = pn.Column(sizing_mode='stretch_width')
            
            self.document_list_container.clear()
            
            # Set up document list header
            self.document_list_container.append(pn.pane.Markdown("## Document List"))
            
            # Get filter values
            doc_type = self.doc_type_select.value if hasattr(self, 'doc_type_select') else None
            status = self.status_select.value if hasattr(self, 'status_select') else None
            date_from = self.date_from_picker.value if hasattr(self, 'date_from_picker') else None
            date_to = self.date_to_picker.value if hasattr(self, 'date_to_picker') else None
            search_text = self.search_input.value if hasattr(self, 'search_input') else None
            
            # Convert display values to codes for API calls
            if doc_type and doc_type != '':
                doc_type = settings.get_document_type_code(doc_type)
                
            if status and status != '':
                status = settings.get_document_status_code(status)
                
            logger.debug(f"Filters - Type: {doc_type}, Status: {status}, Search: {search_text}")
            
            # Get documents
            from CDocs.controllers.document_controller import get_documents
            result = get_documents(
                user=self.user,
                doc_type=doc_type if doc_type and doc_type != '' else None,
                status=status if status and status != '' else None,
                date_from=date_from.isoformat() if date_from else None,
                date_to=date_to.isoformat() if date_to else None,
                search_text=search_text if search_text else None
            )
            
            documents = result.get('documents', [])
            logger.debug(f"Retrieved {len(documents)} documents")
            
            if not documents:
                self.document_list_container.append(
                    pn.pane.Markdown("No documents found matching your criteria.")
                )
                self.notification_area.object = ""
                return
            
            # Debug the Neo4j document structure
            if documents and len(documents) > 0:
                logger.debug(f"First document sample: {documents[0].keys()}")
            
            # Format documents for display with Neo4j field names
            data = []
            from CDocs.config import settings
            for doc in documents:
                # Get status code for workflow checks
                status_code = doc.get('status')
                
                # Create action buttons based on status
                actions = f"""<button class='btn btn-sm btn-primary view-btn' data-uid='{doc.get('UID')}'>View</button>"""
                
                # For documents in review, add review button
                if status_code == 'IN_REVIEW':
                    # Get latest review cycle if any
                    review_cycles = doc.get('review_cycles', [])
                    if review_cycles:
                        latest_review = review_cycles[0]  # Assuming most recent first
                        review_uid = latest_review.get('UID')
                        actions += f""" <button class='btn btn-sm btn-info review-btn' data-uid='{review_uid}'>Review</button>"""
                
                # For documents in approval, add approval button
                if status_code == 'IN_APPROVAL':
                    # Get latest approval cycle if any
                    approval_cycles = doc.get('approval_cycles', [])
                    if approval_cycles:
                        latest_approval = approval_cycles[0]  # Assuming most recent first
                        approval_uid = latest_approval.get('UID')
                        actions += f""" <button class='btn btn-sm btn-warning approval-btn' data-uid='{approval_uid}'>Approve</button>"""
                
                # Add row data
                data.append({
                    'UID': doc.get('UID', ''),
                    'doc_number': doc.get('doc_number', doc.get('docNumber', 'N/A')),
                    'title': doc.get('title', 'Untitled'),
                    'doc_type': settings.get_document_type_name(doc.get('doc_type', doc.get('docType', ''))),
                    'status': settings.get_document_status_name(status_code),
                    'last_updated': doc.get('updated_at', doc.get('last_updated', '')),
                    'action': actions
                })
            
            # Create DataFrame
            df = pd.DataFrame(data)
            
            # Format date columns
            date_columns = ['last_updated']
            for col in date_columns:
                if col in df.columns:
                    df[col] = pd.to_datetime(df[col], errors='coerce').dt.strftime('%Y-%m-%d %H:%M')
            
            # Create Tabulator widget for Panel 1.6.1
            doc_table = pn.widgets.Tabulator(
                df,
                formatters={
                    'action': {
                        'type': 'html',
                        'label': 'Action'
                    }
                },
                selectable='checkbox',
                pagination='local',
                page_size=10,
                sizing_mode='stretch_width',
                hidden_columns=['UID'],
                selection=[]
            )
            
            # Update reference and set up handlers - ONCE
            self.document_list = doc_table
            
            # Setup event handlers - register click events
            self.setup_event_handlers()
            
            # Add table to container - IMPORTANT: Add to container BEFORE setting up JS callbacks
            self.document_list_container.append(doc_table)
            
            # Set up JS button handling AFTER adding to container
            self.setup_view_buttons()
            
            # Show document count
            self.document_list_container.append(pn.pane.Markdown(f"*Showing {len(documents)} document(s)*"))
            
            # Clear notification
            self.notification_area.object = ""
            
        except Exception as e:
            logger.error(f"Error updating document list: {str(e)}")
            logger.error(f"Traceback: {traceback.format_exc()}")
            self.notification_area.object = f"**Error loading documents:** {str(e)}"
            
            # Safely create or clear the container
            if not hasattr(self, 'document_list_container'):
                self.document_list_container = pn.Column(sizing_mode='stretch_width')
            else:
                self.document_list_container.clear()
                
            self.document_list_container.append(pn.pane.Markdown("Error loading documents. Please try again."))

    def _show_sample_documents(self):
        """Show sample documents when database access fails."""
        try:
            # Create sample data
            sample_data = [
                {
                    "doc_number": "SOP-QA-001", 
                    "title": "Document Control Procedure", 
                    "doc_type": "SOP", 
                    "status": "Published", 
                    "created_date": "2025-01-15",
                    "action": "<button class='btn btn-sm btn-primary'>View</button>"
                },
                {
                    "doc_number": "POL-QA-001", 
                    "title": "Quality Management System Manual", 
                    "doc_type": "Policy", 
                    "status": "Published", 
                    "created_date": "2025-02-01",
                    "action": "<button class='btn btn-sm btn-primary'>View</button>"
                },
                {
                    "doc_number": "FORM-QA-001", 
                    "title": "Change Control Request Form", 
                    "doc_type": "Form", 
                    "status": "Draft", 
                    "created_date": "2025-03-10",
                    "action": "<button class='btn btn-sm btn-primary'>View</button>"
                },
                {
                    "doc_number": "WI-MFG-001", 
                    "title": "Product Assembly Instructions", 
                    "doc_type": "Work Instruction", 
                    "status": "In Review", 
                    "created_date": "2025-03-05",
                    "action": "<button class='btn btn-sm btn-primary'>View</button>"
                },
                {
                    "doc_number": "TMPL-RES-001", 
                    "title": "Research Report Template", 
                    "doc_type": "Template", 
                    "status": "Approved", 
                    "created_date": "2025-02-20",
                    "action": "<button class='btn btn-sm btn-primary'>View</button>"
                }
            ]
            
            # Create document list if it doesn't exist
            if not self.document_list:
                self._create_document_list()
            
            # Convert to pandas DataFrame for Tabulator
            try:
                import pandas as pd
                sample_df = pd.DataFrame(sample_data)
                
                # Update the document list with sample data
                if isinstance(self.document_list, pn.widgets.Tabulator):
                    self.document_list.value = sample_df
                elif isinstance(self.document_list, pn.pane.Markdown):
                    # If we have the markdown fallback
                    self.document_list.object = self._format_documents_as_markdown(sample_data)
            except ImportError:
                # If pandas is not available, use markdown fallback
                self.document_list = pn.pane.Markdown(self._format_documents_as_markdown(sample_data))
                    
        except Exception as e:
            logger.error(f"Error in _show_sample_documents: {e}")
            if hasattr(self, 'notification_area'):
                self.notification_area.object = f"Error showing sample documents: {str(e)}"

    def _format_documents_as_markdown(self, documents):
        """Format documents as markdown table for fallback display."""
        header = """
        | Document Number | Title | Type | Status | Created |
        |---|---|---|---|---|
        """
        
        if not documents:
            return header + "| No documents found | | | | |"
        
        rows = []
        for doc in documents:
            row = f"| {doc.get('doc_number', 'N/A')} | {doc.get('title', 'Untitled')} | " \
                  f"{doc.get('doc_type', 'General')} | {doc.get('status', 'Draft')} | " \
                  f"{doc.get('created_date', '')} |"
            rows.append(row)
        
        return header + "\n".join(rows)

    #@guard_execution()
    def document_selected(self, event):
        """Handle document selection from table"""
        logger.debug("Document selection event triggered")
        
        try:
            logger.debug(f"Event type: {type(event).__name__}")
            
            # With Panel 1.6.1, the event structure is different
            # We need to access the document_list directly instead of relying on the event
            
            # Get the currently selected document table
            if not hasattr(self, 'document_list') or self.document_list is None:
                logger.error("No document list available")
                self.notification_area.object = "**Error:** Document list not initialized"
                return
                
            table = self.document_list  # Use the current document_list
            
            # Extract row information from the event
            row_index = None
            
            # Handle different event types
            if hasattr(event, 'row') and event.row is not None:  # CellClickEvent
                row_index = event.row
                logger.debug(f"Using row from cell click: {row_index}")
                
                # Also check if this is a click on the action column (View button)
                if hasattr(event, 'column') and event.column == 'action':
                    logger.debug("Click detected on action (View) button")
            elif hasattr(event, 'selected') and event.selected:  # Selection event
                row_index = event.selected[0] if isinstance(event.selected, list) else event.selected
                logger.debug(f"Using row from selection event: {row_index}")
            elif hasattr(event, 'new') and event.new:  # Alternative selection format
                row_index = event.new[0] if isinstance(event.new, list) else event.new
                logger.debug(f"Using row from event.new: {row_index}")
                
            if row_index is None:
                logger.warning("Could not determine row index from event")
                
                # Try a fallback method - check if there's a selection on the table
                if hasattr(table, 'selection') and table.selection:
                    row_index = table.selection[0] if isinstance(table.selection, list) else table.selection
                    logger.debug(f"Using row from table selection: {row_index}")
                else:
                    self.notification_area.object = "**Error:** Could not determine which document was selected"
                    return
            
            # Get the DataFrame from the table
            df = None
            if hasattr(table, 'value'):
                df = table.value
                logger.debug(f"Got DataFrame from table.value with {len(df)} rows")
            else:
                logger.error("Table has no value attribute")
                self.notification_area.object = "**Error:** Could not access table data"
                return
            
            if df is None or len(df) == 0 or row_index >= len(df):
                logger.error(f"Invalid DataFrame or row index: df={df is not None}, len(df)={len(df) if df is not None else 0}, idx={row_index}")
                self.notification_area.object = "**Error:** Could not access selected document data"
                return
            
            # Debug the DataFrame columns and the selected row
            logger.debug(f"DataFrame columns: {df.columns.tolist()}")
            logger.debug(f"Selected row data: {df.iloc[row_index].to_dict()}")
            
            # Check for 'uid' column and extract document UID
            if 'UID' in df.columns:
                doc_uid = df.iloc[row_index]['UID']
                logger.debug(f"Found document UID in 'UID' column: {doc_uid}")
                
                # Call parent app's load_document method
                if hasattr(self, 'parent_app') and self.parent_app and hasattr(self.parent_app, 'load_document'):
                    logger.debug(f"Calling parent_app.load_document with UID: {doc_uid}")
                    self.parent_app.load_document(doc_uid)
                else:
                    logger.error("No parent_app reference available to load document")
                    self.notification_area.object = "**Error:** Cannot load document (missing parent app reference)"
                
                return
                
            # Fallback: Try to find the document by doc_number
            if 'doc_number' in df.columns:
                doc_number = df.iloc[row_index]['doc_number']
                logger.debug(f"Looking up document by doc_number: {doc_number}")
                
                # Import within function to avoid circular imports
                from CDocs.controllers.document_controller import get_documents
                result = get_documents(
                    user=self.user,
                    doc_number=doc_number
                )
                
                documents = result.get('documents', [])
                if documents and len(documents) > 0:
                    # Try to get the UID using the same helper method we use when building the table
                    doc_uid = self._get_field_case_insensitive(documents[0], ['UID', 'uid'])
                    if doc_uid:
                        logger.debug(f"Found UID by doc_number lookup: {doc_uid}")
                        
                        # Call parent app's load_document method
                        if hasattr(self, 'parent_app') and self.parent_app and hasattr(self.parent_app, 'load_document'):
                            self.parent_app.load_document(doc_uid)
                        else:
                            logger.error("No parent_app reference available to load document")
                            self.notification_area.object = "**Error:** Cannot load document (missing parent app reference)"
                        
                        return
                
            # If we got here, we couldn't find a document UID
            logger.error("Could not determine document UID from row data")
            self.notification_area.object = "**Error:** Could not determine document ID"
            
        except Exception as e:
            logger.error(f"Error selecting document: {str(e)}")
            import traceback
            logger.error(f"Traceback: {traceback.format_exc()}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def show_create_document_form(self, event=None):
        """Show the create document form"""
        logger.debug("Showing create document form")
        
        try:
            # Clear document list container
            self.document_list_container.clear()
            
            # Set notification
            self.notification_area.object = "Creating new document"
            
            # Create form widgets
            title_input = pn.widgets.TextInput(name="Title", placeholder="Enter document title", width=400)
            
            # Get document types from settings
            from CDocs.config import settings
            
            # Use full names as options but store the mapping to codes
            doc_type_options = list(settings.DOCUMENT_TYPES.keys())  # Full names as options
            
            logger.debug(f"Document type dropdown options: {doc_type_options}")
            
            # Create the dropdown with full names as options
            doc_type_select = pn.widgets.Select(
                name="Document Type", 
                options=doc_type_options,
                width=200
            )
            
            # Department selector - use full names as options
            dept_options = list(settings.DEPARTMENTS.keys())  # Full names as options
            department_select = pn.widgets.Select(name="Department", options=dept_options, width=200)
            
            # Content textarea
            content_area = pn.widgets.TextAreaInput(
                name="Content",
                placeholder="Enter document content or upload a file",
                rows=10,
                width=600
            )
            
            # File upload widget
            file_upload = pn.widgets.FileInput(name="Upload File", accept=".docx,.pdf,.txt,.md", width=300)
            
            # Buttons
            create_btn = pn.widgets.Button(name="Create Document", button_type="success", width=150)
            cancel_btn = pn.widgets.Button(name="Cancel", button_type="default", width=100)
            
            # Create form layout
            create_form = pn.Column(
                pn.pane.Markdown("# Create New Document"),
                title_input,
                pn.Row(doc_type_select, department_select),
                pn.Row(pn.Column(pn.pane.Markdown("**Upload File:**"), file_upload), 
                       pn.Column(pn.pane.Markdown("**Or enter content directly:**"))),
                content_area,
                pn.Row(
                    cancel_btn,
                    create_btn,
                    align='end'
                ),
                width=700,
                styles={'background':'#f8f9fa'},
                css_classes=['p-4', 'border', 'rounded']
            )
            
            # Define file upload handler
            def handle_file_upload(event):
                if file_upload.value is not None:
                    filename = file_upload.filename
                    
                    # Extract file content based on type
                    if filename.endswith('.txt') or filename.endswith('.md'):
                        # Text files can be displayed directly
                        try:
                            content = file_upload.value.decode('utf-8')
                            content_area.value = content
                        except:
                            self.notification_area.object = "**Error:** Failed to read file as text"
                    else:
                        # For other file types, just note that the file will be stored
                        content_area.value = f"[File content from: {filename}]"
            
            # Add file upload handler
            file_upload.param.watch(handle_file_upload, 'value')
            
            # Define button handlers
            def handle_create(event):
                logger.debug("Create button clicked")
                
                # Validate form
                if not title_input.value:
                    self.notification_area.object = "**Error:** Document title is required"
                    return
                    
                if not doc_type_select.value:
                    self.notification_area.object = "**Error:** Document type is required"
                    return
                    
                # Create document
                try:
                    from CDocs.controllers.document_controller import create_document
                    
                    # Extract content from file or text area
                    content = content_area.value or ""
                    if file_upload.value and not content_area.value:
                        try:
                            # Try to extract text if it's a text file
                            content = file_upload.value.decode('utf-8')
                        except:
                            # For binary files, save the reference
                            content = f"[File content from: {file_upload.filename}]"
                    
                    # Convert full names to codes for storage in Neo4j
                    # Get the document type code from the selected full name
                    doc_type_full_name = doc_type_select.value
                    doc_type_code = settings.get_document_type_code(doc_type_full_name)
                    
                    # Get the department code from the selected full name
                    dept_full_name = department_select.value
                    dept_code = settings.get_department_code(dept_full_name)
                    
                    logger.info(f"Creating document with type: {doc_type_full_name} (code: {doc_type_code})")
                    logger.info(f"Department: {dept_full_name} (code: {dept_code})")
                    
                    result = create_document(
                        user=self.user,
                        title=title_input.value,
                        doc_text=content,
                        doc_type=doc_type_code,  # Send the code, not the full name
                        department=dept_code,     # Send the code, not the full name
                        status='DRAFT'
                    )
                    
                    # Show success message if result is valid
                    if result and isinstance(result, dict) and 'document' in result:
                        doc_number = result['document'].get('doc_number', 'Unknown')
                        self.notification_area.object = f"**Success:** Document {doc_number} created"
                        
                        # Update document list
                        self.update_document_list()
                    else:
                        self.notification_area.object = "**Error:** Document creation failed - invalid result"
                    
                except Exception as e:
                    logger.error(f"Error creating document: {e}")
                    import traceback
                    logger.error(traceback.format_exc())
                    self.notification_area.object = f"**Error creating document:** {str(e)}"
            
            def handle_cancel(event):
                # Reset form and return to document list
                self.notification_area.object = ""
                self.update_document_list()
            
            # Bind handlers
            create_btn.on_click(handle_create)
            cancel_btn.on_click(handle_cancel)
            
            # Add form to document list container
            self.document_list_container.append(create_form)
            
        except Exception as e:
            logger.error(f"Error showing create document form: {e}")
            import traceback
            logger.error(traceback.format_exc())
            self.notification_area.object = f"**Error showing create document form:** {str(e)}"
            
            # Return to document list
            self.update_document_list()
    
    def _apply_filters(self, event=None):
        """Apply the selected filters to the document list."""
        try:
            # Since our filters now have full names but the backend expects codes,
            # we need to convert them before updating the document list
            from CDocs.config import settings
            
            # Store original values
            orig_doc_type = self.doc_type_select.value 
            orig_department = self.department_select.value
            orig_status = self.status_select.value
            
            # Convert to codes for the backend query if values are not empty
            if orig_doc_type:
                self.doc_type_select.value = settings.get_document_type_code(orig_doc_type)
                
            if orig_department:
                self.department_select.value = settings.get_department_code(orig_department)
                
            if orig_status:
                self.status_select.value = settings.get_document_status_code(orig_status)
            
            # Update the document list with the codes
            self.update_document_list()
            
            # Restore original display values for the UI
            self.doc_type_select.value = orig_doc_type
            self.department_select.value = orig_department
            self.status_select.value = orig_status
            
        except Exception as e:
            logger.error(f"Error applying filters: {e}")
            self.notification_area.object = f"Error applying filters: {str(e)}"

    def _clear_filters(self, event=None):
        """Clear all filters and reset the document list."""
        try:
            # Reset filter values
            self.search_input.value = ""
            self.doc_type_select.value = ""
            self.status_select.value = ""
            self.department_select.value = ""
            
            if hasattr(self, 'date_from_picker'):
                self.date_from_picker.value = None
            if hasattr(self, 'date_to_picker'):
                self.date_to_picker.value = None
            
            # Update document list with no filters
            self.update_document_list()
            
        except Exception as e:
            logger.error(f"Error clearing filters: {e}")
            self.notification_area.object = f"Error clearing filters: {str(e)}"

    def setup_view_buttons(self):
        """Set up HTML click handling for action buttons in the document table."""
        try:
            # Check if we have a table to work with
            if not self.document_list or not isinstance(self.document_list, pn.widgets.Tabulator):
                logger.warning("Document list not available or not a Tabulator")
                return
                    
            # Instead of using complex JavaScript callbacks, we'll use URL-based navigation
            # by modifying the action column to include complete HTML links
            if hasattr(self.document_list, 'value') and isinstance(self.document_list.value, pd.DataFrame):
                df = self.document_list.value.copy()
                
                # Modify the 'action' column to use standard HTML links
                if 'action' in df.columns and 'UID' in df.columns:
                    new_actions = []
                    
                    for i, row in df.iterrows():
                        doc_uid = row['UID']
                        status_code = row.get('status_code', '')  # Get status code if available
                        
                        # Create view button with direct link
                        actions = f'<a href="#document/{doc_uid}" class="btn btn-sm btn-primary">View</a>'
                        
                        # Get workflow UIDs if they exist in the data
                        review_uid = row.get('review_uid', '')
                        if review_uid and status_code == 'IN_REVIEW':
                            actions += f' <a href="#review/{review_uid}" class="btn btn-sm btn-info">Review</a>'
                            
                        approval_uid = row.get('approval_uid', '')
                        if approval_uid and status_code == 'IN_APPROVAL':
                            actions += f' <a href="#approval/{approval_uid}" class="btn btn-sm btn-warning">Approve</a>'
                        
                        new_actions.append(actions)
                    
                    df['action'] = new_actions
                    self.document_list.value = df
                    
                # Set up a catch-all JavaScript listener at the document level to handle hash changes
                js_code = """
                // Initialize hash change handler
                window.onhashchange = function() {
                    var hash = window.location.hash;
                    
                    // Parse the hash to get the action and UID
                    var parts = hash.substring(1).split('/');
                    if (parts.length !== 2) return;
                    
                    var action = parts[0];
                    var uid = parts[1];
                    
                    // Based on the action, navigate appropriately
                    if (action === 'document') {
                        // Navigate to document view
                        console.log('Loading document: ' + uid);
                        window.parent.postMessage({type: 'load_document', uid: uid}, '*');
                    } else if (action === 'review') {
                        // Navigate to review view
                        console.log('Loading review: ' + uid);
                        window.parent.postMessage({type: 'load_review', uid: uid}, '*');
                    } else if (action === 'approval') {
                        // Navigate to approval view 
                        console.log('Loading approval: ' + uid);
                        window.parent.postMessage({type: 'load_approval', uid: uid}, '*');
                    }
                };
                """
                
                # Add the JavaScript code to the page once
                pn.config.js_files['action_handler'] = {'raw': js_code}
                logger.debug("Set up HTML-based action buttons")
                
        except Exception as e:
            logger.error(f"Error setting up view buttons: {e}")
            logger.error(traceback.format_exc())

    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 str(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 str(doc_dict[name])
                
        # Return empty string if no match found
        return ''

    def setup_event_handlers(self):
        """Set up event handlers for the current document list."""
        try:
            # Register document selection handler for the current document list
            if hasattr(self, 'document_list') and self.document_list:
                # To avoid multiple event bindings, we use a special handler
                # that checks if this particular table instance already has our handler
                table_id = id(self.document_list)
                
                # Track if this specific table instance has been set up
                if hasattr(self, '_event_handler_table_id') and self._event_handler_table_id == table_id:
                    logger.debug(f"Event handlers already registered for table {table_id}")
                    return
                    
                # Store the ID of the table we're setting up
                self._event_handler_table_id = table_id
                
                # Register click event handler
                self.document_list.on_click(self.document_selected)
                logger.debug(f"Registered document_selected handler for table {table_id}")
        except Exception as e:
            logger.error(f"Error setting up event handlers: {e}")
            import traceback
            logger.error(traceback.format_exc())

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 from parent application

Parameters:

  • user: Parameter

Returns: None

_update_ui_for_user(self)

Purpose: Update UI components based on current user permissions

Returns: None

_setup_sidebar(self)

Purpose: Set up the sidebar filters and actions.

Returns: None

get_dashboard_view(self)

Purpose: Get the dashboard view with document list and actions.

Returns: None

_create_document_list(self)

Purpose: Create the document list component.

Returns: None

update_document_list(self, event)

Purpose: Update the document list with current filters.

Parameters:

  • event: Parameter

Returns: None

_show_sample_documents(self)

Purpose: Show sample documents when database access fails.

Returns: None

_format_documents_as_markdown(self, documents)

Purpose: Format documents as markdown table for fallback display.

Parameters:

  • documents: Parameter

Returns: None

document_selected(self, event)

Purpose: Handle document selection from table

Parameters:

  • event: Parameter

Returns: None

show_create_document_form(self, event)

Purpose: Show the create document form

Parameters:

  • event: Parameter

Returns: None

_apply_filters(self, event)

Purpose: Apply the selected filters to the document list.

Parameters:

  • event: Parameter

Returns: None

_clear_filters(self, event)

Purpose: Clear all filters and reset the document list.

Parameters:

  • event: Parameter

Returns: None

setup_view_buttons(self)

Purpose: Set up HTML click handling for action buttons in the document table.

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

setup_event_handlers(self)

Purpose: Set up event handlers for the current document list.

Returns: None

Required Imports

import logging
from typing import Dict
from typing import Any
from typing import List
from typing import Optional

Usage Example

# Example usage:
# result = DocumentDashboard(bases)

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class DocumentDashboard 97.8% similar

    Dashboard for viewing and managing controlled documents.

    From: /tf/active/vicechatdev/CDocs/ui/document_dashboard.py
  • class ControlledDocument_v1 71.1% similar

    Model representing a controlled document.

    From: /tf/active/vicechatdev/CDocs single class/models/document.py
  • class ControlledDocument_v1 69.0% similar

    Model representing a controlled document.

    From: /tf/active/vicechatdev/CDocs/models/document.py
  • class ControlledDocument 64.7% similar

    A class representing a controlled document in a document management system, providing access to document metadata stored in a Neo4j database.

    From: /tf/active/vicechatdev/CDocs single class/db/db_operations.py
  • class ControlledDocApp 64.3% similar

    A standalone Panel web application class that provides a complete controlled document management system with user authentication, navigation, and document lifecycle management features.

    From: /tf/active/vicechatdev/panel_app.py
← Back to Browse