🔍 Code Extractor

class ApprovalPanel

Maturity: 27

Approval management interface component

File:
/tf/active/vicechatdev/CDocs/ui/approval_panel_bis.py
Lines:
62 - 1895
Complexity:
moderate

Purpose

Approval management interface component

Source Code

class ApprovalPanel(param.Parameterized):
    """Approval management interface component"""
    
    approval_uid = param.String(default='')
    document_uid = param.String(default='')
    current_tab = param.String(default='my_approvals')
    
    def __init__(self, template, session_manager=None, parent_app=None, embedded=False, **params):
        super().__init__(**params)
        self.template = template
        self.session_manager = session_manager or SessionManager()
        self.parent_app = parent_app  # Store reference to parent app
        self.user = self._get_current_user()
        self.notification_area = pn.pane.Markdown("")
        self.approval_data = None
        self.document_data = None
        self.embedded = embedded  # Flag for embedded mode
        
        # Create container for main content
        self.main_content = pn.Column(sizing_mode='stretch_width')
        
        # Initialize stats area regardless of mode
        self.stats_area = pn.Column(sizing_mode='stretch_width')
        
        # Initialize other important containers
        self.approval_list_area = pn.Column(sizing_mode='stretch_width')
        self.approval_detail_area = pn.Column(sizing_mode='stretch_width')
        self.document_detail_area = pn.Column(sizing_mode='stretch_width')
        
        # Set up the user interface
        if not self.embedded:
            # Only set up template components in standalone mode
            self._setup_header()
            self._setup_sidebar()
            self.template.main.append(self.notification_area)
            self.template.main.append(self.main_content)
        
        # Always set up main content components
        self.main_content.append(self.stats_area)
        self.main_content.append(self.approval_list_area)
        self.main_content.append(self.approval_detail_area)
        self.main_content.append(self.document_detail_area)
        
        # Hide detail areas initially
        self.approval_detail_area.visible = False
        self.document_detail_area.visible = False
        
        # Load initial data
        self._load_pending_approvals()
    
    def _get_current_user(self) -> Optional['DocUser']:
        """Get the current user from session or parent app"""
        # First check if parent app has current_user
        if hasattr(self, 'parent_app') and self.parent_app and hasattr(self.parent_app, 'current_user'):
            return self.parent_app.current_user
            
        # Otherwise try session manager
        if self.session_manager:
            user_id = self.session_manager.get_user_id()
            if user_id:
                # Import DocUser class
                from CDocs.models.user_extensions import DocUser
                # Return complete DocUser object, not just the ID
                return DocUser(uid=user_id)
                
        return None
    
    def set_user(self, user):
        """Set the current user - needed for compatibility with main app"""
        # Store the user object
        self.user = user
        
        # Reload approval data with the new user
        try:
            # Ensure we have a stats_area
            if not hasattr(self, 'stats_area'):
                self.stats_area = pn.Column(sizing_mode='stretch_width')
                if hasattr(self, 'main_content'):
                    self.main_content.insert(0, self.stats_area)
                
            # Update statistics and reload data
            self._update_approval_statistics()
            self._load_pending_approvals()
        except Exception as e:
            import logging
            logger = logging.getLogger('CDocs.ui.approval_panel')
            logger.error(f"Error in set_user: {str(e)}")
    
    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._refresh_current_view)
        
        # Header with buttons
        header = Row(
            pn.pane.Markdown("# Approval Management"),
            refresh_btn,
            back_btn,
            sizing_mode='stretch_width',
            align='end'
        )
        
        self.template.header.append(header)
    
    def _setup_sidebar(self):
        """Set up the sidebar with navigation options"""
        # Create navigation buttons
        my_approvals_btn = Button(
            name='My Pending Approvals',
            button_type='primary',
            width=200
        )
        my_approvals_btn.on_click(self._load_pending_approvals)
        
        completed_approvals_btn = Button(
            name='My Completed Approvals',
            button_type='default',
            width=200
        )
        completed_approvals_btn.on_click(self._load_completed_approvals)
        
        # Add management buttons if user has manage approvals permission
        if self.user and permissions.user_has_permission(self.user, "MANAGE_APPROVALS"):
            all_approvals_btn = Button(
                name='All Approvals',
                button_type='default',
                width=200
            )
            all_approvals_btn.on_click(self._load_all_approvals)
            
            # Add to navigation area
            navigation = Column(
                Markdown("## Navigation"),
                my_approvals_btn,
                completed_approvals_btn,
                all_approvals_btn,
                sizing_mode='fixed'
            )
        else:
            # Add to navigation area without all approvals button
            navigation = Column(
                Markdown("## Navigation"),
                my_approvals_btn,
                completed_approvals_btn,
                sizing_mode='fixed'
            )
        
        self.template.sidebar.append(navigation)
        
        # Add statistics area
        self.stats_area = Column(
            Markdown("## Approval Statistics"),
            Markdown("*Loading statistics...*"),
            sizing_mode='fixed',
            styles={'background': '#f8f9fa'},
            css_classes=['p-3', 'border', 'rounded']
        )
        self.template.sidebar.append(self.stats_area)
        
        # Update statistics
        self._update_approval_statistics()
    
    def _setup_main_area(self):
        """Set up the main area with approvals and details"""
        # Create notification area
        self.template.main.append(self.notification_area)
        
        # Create main content area
        self.main_content = Column(
            sizing_mode='stretch_width'
        )
        self.template.main.append(self.main_content)
    
    def _refresh_current_view(self, event=None):
        """Refresh the current view"""
        if self.current_tab == 'my_approvals':
            self._load_pending_approvals()
        elif self.current_tab == 'completed_approvals':
            self._load_completed_approvals()
        elif self.current_tab == 'all_approvals':
            self._load_all_approvals()
        elif self.current_tab == 'approval_detail':
            self._load_approval(self.approval_uid)
    
    def _navigate_back(self, event=None):
        """Navigate back to dashboard"""
        # Check if we have a parent app reference
        if hasattr(self, 'parent_app') and self.parent_app is not None:
            # Use parent app's load_dashboard method
            try:
                self.parent_app.load_dashboard()
                return
            except Exception as e:
                logger.error(f"Error navigating back to dashboard via parent app: {e}")
        
        # Fallback to direct state manipulation
        try:
            import panel as pn
            if pn.state.curdoc:
                # Try to find an app object in the global variables
                import sys
                for name, obj in globals().items():
                    if isinstance(obj, object) and hasattr(obj, 'load_dashboard'):
                        obj.load_dashboard()
                        return
            # Last resort - use JavaScript
            pn.state.execute("window.location.href = '/'")
        except Exception as e:
            logger.error(f"Error navigating back to dashboard: {e}")
    
    def _update_approval_statistics(self):
        """Update approval statistics for the current user."""
        try:
            # First, ensure the stats_area exists
            if not hasattr(self, 'stats_area'):
                self.stats_area = pn.Column(sizing_mode='stretch_width')
                self.main_content.insert(0, self.stats_area)
            
            # Clear existing content
            self.stats_area.clear()
            
            if not self.user:
                # No user set, don't show statistics
                self.stats_area.append(pn.pane.Markdown("**No user logged in**"))
                return
                
            # Get statistics
            try:
                # Import the controller functions
                from CDocs.controllers.approval_controller import get_user_pending_approvals
                
                # Get pending approvals
                pending_result = get_user_pending_approvals(
                    user=self.user,
                    include_completed=False
                )
                pending_approvals = pending_result.get('approvals', [])
                pending_count = len(pending_approvals)
                
                # Get completed approvals (last 90 days)
                date_from = (datetime.now() - timedelta(days=90)).isoformat()
                try:
                    completed_result = get_user_pending_approvals(
                        user=self.user,
                        include_completed=True,
                        date_from=date_from
                    )
                    
                    # Filter completed approvals
                    all_approvals = completed_result.get('approvals', [])
                    completed_approvals = [a for a in all_approvals 
                                       if a.get('my_assignment', {}).get('status') == 'COMPLETED']
                    completed_count = len(completed_approvals)
                    
                    # Count by decision
                    approved_count = sum(1 for a in completed_approvals 
                                      if a.get('my_assignment', {}).get('decision') == 'APPROVED')
                    rejected_count = sum(1 for a in completed_approvals 
                                      if a.get('my_assignment', {}).get('decision') == 'REJECTED')
                except TypeError:
                    # Fall back to a simpler approach if include_completed param fails
                    completed_count = 0
                    approved_count = 0
                    rejected_count = 0
                    
                    # Try to get all approvals and filter manually
                    try:
                        all_result = get_user_pending_approvals(self.user)
                        all_approvals = all_result.get('approvals', [])
                        
                        # Filter and count manually
                        for approval in all_approvals:
                            approver_data = approval.get('my_assignment', {})
                            status = approver_data.get('status')
                            decision = approver_data.get('decision')
                            
                            if status == 'COMPLETED':
                                completed_count += 1
                                if decision == 'APPROVED':
                                    approved_count += 1
                                elif decision == 'REJECTED':
                                    rejected_count += 1
                    except Exception as filter_error:
                        logger.error(f"Error filtering approvals: {filter_error}")
                
                # Calculate efficiency metrics
                if completed_count > 0:
                    approval_rate = (approved_count / completed_count) * 100
                else:
                    approval_rate = 0
                
                # Create statistics cards
                stats_row = pn.Row(
                    pn.Column(
                        pn.pane.Markdown("## My Approval Statistics"),
                        pn.pane.Markdown(f"**Pending Approvals:** {pending_count}"),
                        pn.pane.Markdown(f"**Completed (Last 90 Days):** {completed_count}"),
                        pn.pane.Markdown(f"**Approval Rate:** {approval_rate:.1f}%"),
                        width=300,
                        styles={'background': '#f5f5f5', 'border': '1px solid #ddd', 'border-radius': '5px', 'padding': '10px'}
                    ),
                    pn.Column(
                        pn.pane.Markdown("## Decision Breakdown"),
                        pn.pane.Markdown(f"**Approved:** {approved_count}"),
                        pn.pane.Markdown(f"**Rejected:** {rejected_count}"),
                        width=300,
                        styles={'background': '#f5f5f5', 'border': '1px solid #ddd', 'border-radius': '5px', 'padding': '10px'}
                    ),
                    sizing_mode='stretch_width'
                )
                
                # Add to stats area in main content
                self.stats_area.append(stats_row)
                
                # Get more detailed system-wide statistics
                try:
                    from CDocs.controllers.approval_controller import get_approval_statistics
                    system_stats = get_approval_statistics(user=self.user)
                    
                    if system_stats and system_stats.get('success'):
                        statistics = system_stats.get('statistics', {})
                        
                        # Create a system-wide statistics row
                        if permissions.user_has_permission(self.user, "MANAGE_APPROVALS"):
                            system_stats_row = pn.Row(
                                pn.Column(
                                    pn.pane.Markdown("## System Approval Statistics"),
                                    pn.pane.Markdown(f"**Total Approvals:** {statistics.get('total_approvals', 0)}"),
                                    pn.pane.Markdown(f"**Pending Approvals:** {statistics.get('in_progress_count', 0)}"),
                                    pn.pane.Markdown(f"**Completed Approvals:** {statistics.get('approved_count', 0) + statistics.get('rejected_count', 0)}"),
                                    width=300,
                                    styles={'background': '#f5f5f5', 'border': '1px solid #ddd', 'border-radius': '5px', 'padding': '10px'}
                                ),
                                pn.Column(
                                    pn.pane.Markdown("## Approval Efficiency"),
                                    pn.pane.Markdown(f"**Approval Rate:** {(statistics.get('approved_count', 0) / (statistics.get('approved_count', 0) + statistics.get('rejected_count', 1)) * 100):.1f}%"),
                                    pn.pane.Markdown(f"**Avg. Completion Days:** {statistics.get('avg_completion_days', 0):.1f}"),
                                    pn.pane.Markdown(f"**Overdue Approvals:** {statistics.get('overdue_count', 0)}"),
                                    width=300,
                                    styles={'background': '#f5f5f5', 'border': '1px solid #ddd', 'border-radius': '5px', 'padding': '10px'}
                                ),
                                sizing_mode='stretch_width'
                            )
                            self.stats_area.append(system_stats_row)
                except Exception as sys_stats_error:
                    logger.error(f"Error loading system approval statistics: {sys_stats_error}")
                
            except Exception as e:
                logger.error(f"Error fetching approval statistics: {e}")
                # Show error message
                self.stats_area.append(pn.pane.Markdown(f"**Error loading statistics:** {str(e)}"))
                return
        
        except Exception as e:
            logger.error(f"Error updating approval statistics: {e}")
            # Try to create stats area if it doesn't exist
            if not hasattr(self, 'stats_area'):
                self.stats_area = pn.Column(sizing_mode='stretch_width')
                if hasattr(self, 'main_content'):
                    self.main_content.insert(0, self.stats_area)
            
            # Add error message
            if hasattr(self, 'stats_area'):
                self.stats_area.clear()
                self.stats_area.append(pn.pane.Markdown(f"**Error updating statistics:** {str(e)}"))
    
    def _load_pending_approvals(self, event=None):
        """Load pending approvals for the current user"""
        try:
            self.current_tab = 'my_approvals'
            self.notification_area.object = "Loading your pending approvals..."
            
            # Clear areas and prepare main content
            self.approval_list_area.clear()
            self.approval_detail_area.clear()
            self.document_detail_area.clear()
            self.approval_detail_area.visible = False
            self.document_detail_area.visible = False
            
            self.main_content.clear()
            self._update_approval_statistics()
            
            # Add content areas back to main_content
            self.main_content.append(self.stats_area)
            self.main_content.append(self.approval_list_area)
            self.main_content.append(self.approval_detail_area)
            self.main_content.append(self.document_detail_area)
            
            if not self.user:
                self.approval_list_area.append(Markdown("# My Pending Approvals"))
                self.approval_list_area.append(Markdown("Please log in to view your pending approvals."))
                self.notification_area.object = ""
                return
            
            # Get pending approvals
            try:
                from CDocs.controllers.approval_controller import get_user_assigned_approvals
                
                # Get only active approvals (PENDING and IN_APPROVAL status)
                result = get_user_assigned_approvals(
                    user=self.user,
                    status_filter=["PENDING", "IN_APPROVAL"],
                    include_completed=False
                )
                
                # Prepare data for display
                approvals = result.get('approvals', [])
                if not approvals:
                    self.approval_list_area.append(Markdown("# My Pending Approvals"))
                    self.approval_list_area.append(Markdown("You have no pending approvals."))
                    self.notification_area.object = ""
                    return
                    
                # Convert to DataFrame for table display
                approvals_data = []
                for approval in approvals:
                    document = approval.get('document', {})
                    
                    approvals_data.append({
                        'approval_uid': approval.get('UID', ''),
                        'document_uid': document.get('uid', ''),
                        'doc_number': document.get('docNumber', ''),
                        'title': document.get('title', ''),
                        'status': approval.get('status', ''),
                        'approval_type': approval.get('approvalType', ''),
                        'initiated_date': approval.get('createdAt', ''),
                        'due_date': approval.get('dueDate', ''),
                        'approver_status': approval.get('my_assignment', {}).get('status', '')
                    })
                
                # Create DataFrame
                df = pd.DataFrame(approvals_data)
                
                # Format dates
                if 'initiated_date' in df.columns:
                    df['initiated_date'] = df['initiated_date'].apply(self._format_date)
                if 'due_date' in df.columns:
                    df['due_date'] = df['due_date'].apply(self._format_date)
                
                # Add action column
                df['action'] = 'Approve'
                
                # Display columns setup
                display_columns = ['doc_number', 'title', 'status', 'approval_type', 
                                  'initiated_date', 'due_date', 'action', 'approval_uid']
                column_names = {
                    'doc_number': 'Document',
                    'title': 'Title',
                    'status': 'Status',
                    'approval_type': 'Type',
                    'initiated_date': 'Started',
                    'due_date': 'Due Date',
                    'action': 'Action',
                    'approval_uid': 'approval_uid'
                }
                
                # Filter and rename columns
                exist_columns = [col for col in display_columns if col in df.columns]
                df = df[exist_columns]
                rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
                df = df.rename(columns=rename_dict)
                
                # Create table
                approvals_table = Tabulator(
                    df,
                    pagination='local',
                    page_size=10,
                    sizing_mode='stretch_width',
                    selectable=1,
                    height=400,
                    hidden_columns=['approval_uid']
                )
                
                # Add click handler
                approvals_table.on_click(self._approval_selected)
                
                # Add to approval list area
                self.approval_list_area.append(Markdown("# My Pending Approvals"))
                self.approval_list_area.append(Markdown(f"You have {len(approvals_data)} pending approval(s) to complete."))
                self.approval_list_area.append(approvals_table)
                
                # Clear notification
                self.notification_area.object = ""
                
            except Exception as e:
                logger.error(f"Error fetching pending approvals: {e}")
                self.approval_list_area.append(Markdown("# My Pending Approvals"))
                self.approval_list_area.append(Markdown(f"Error fetching pending approvals: {str(e)}"))
                self.notification_area.object = ""
                
        except Exception as e:
            logger.error(f"Error loading pending approvals: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
            self.main_content.clear()
            self.main_content.append(self.stats_area)
            self.approval_list_area.clear()
            self.approval_list_area.append(Markdown("# My Pending Approvals"))
            self.approval_list_area.append(Markdown("*Error loading pending approvals*"))
            self.main_content.append(self.approval_list_area)

    def _load_completed_approvals(self, event=None):
        """Load completed approvals for the current user"""
        try:
            self.current_tab = 'completed_approvals'
            self.notification_area.object = "Loading your completed approvals..."
            
            # Clear areas and prepare main content
            self.approval_list_area.clear()
            self.approval_detail_area.clear()
            self.document_detail_area.clear()
            self.approval_detail_area.visible = False
            self.document_detail_area.visible = False
            
            self.main_content.clear()
            self._update_approval_statistics()
            
            # Add content areas back to main_content
            self.main_content.append(self.stats_area)
            self.main_content.append(self.approval_list_area)
            self.main_content.append(self.approval_detail_area)
            self.main_content.append(self.document_detail_area)
            
            if not self.user:
                self.approval_list_area.append(Markdown("# My Completed Approvals"))
                self.approval_list_area.append(Markdown("Please log in to view your completed approvals."))
                self.notification_area.object = ""
                return
            
            # Get completed approvals
            try:
                from CDocs.controllers.approval_controller import get_user_assigned_approvals
                
                # Get completed approvals
                result = get_user_assigned_approvals(
                    user=self.user,
                    include_completed=True,
                    limit=100
                )
                
                # Filter for completed approvals
                approvals = result.get('approvals', [])
                completed_approvals = [a for a in approvals 
                                      if a.get('my_assignment', {}).get('status', '') == 'COMPLETED']
                
                if not completed_approvals:
                    self.approval_list_area.append(Markdown("# My Completed Approvals"))
                    self.approval_list_area.append(Markdown("You have no completed approvals."))
                    self.notification_area.object = ""
                    return
                    
                # Convert to DataFrame for table display
                approvals_data = []
                for approval in completed_approvals:
                    document = approval.get('document', {})
                    my_assignment = approval.get('my_assignment', {})
                    
                    approvals_data.append({
                        'approval_uid': approval.get('UID', ''),
                        'document_uid': document.get('uid', ''),
                        'doc_number': document.get('docNumber', ''),
                        'title': document.get('title', ''),
                        'status': approval.get('status', ''),
                        'my_decision': my_assignment.get('decision', ''),
                        'decision_date': my_assignment.get('decisionDate', ''),
                        'completion_date': approval.get('completionDate', '')
                    })
                
                # Create DataFrame
                df = pd.DataFrame(approvals_data)
                
                # Format dates
                if 'decision_date' in df.columns:
                    df['decision_date'] = df['decision_date'].apply(self._format_date)
                if 'completion_date' in df.columns:
                    df['completion_date'] = df['completion_date'].apply(self._format_date)
                
                # Display columns setup
                display_columns = ['doc_number', 'title', 'status', 'my_decision', 
                                  'decision_date', 'completion_date', 'approval_uid']
                column_names = {
                    'doc_number': 'Document',
                    'title': 'Title',
                    'status': 'Status',
                    'my_decision': 'My Decision',
                    'decision_date': 'Decision Date',
                    'completion_date': 'Completed',
                    'approval_uid': 'approval_uid'
                }
                
                # Filter and rename columns
                exist_columns = [col for col in display_columns if col in df.columns]
                df = df[exist_columns]
                rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
                df = df.rename(columns=rename_dict)
                
                # Create table
                approvals_table = Tabulator(
                    df,
                    pagination='local',
                    page_size=10,
                    sizing_mode='stretch_width',
                    selectable=1,
                    height=400,
                    hidden_columns=['approval_uid']
                )
                
                # Add click handler
                approvals_table.on_click(self._approval_selected)
                
                # Add to approval list area
                self.approval_list_area.append(Markdown("# My Completed Approvals"))
                self.approval_list_area.append(Markdown(f"You have completed {len(approvals_data)} approval(s)."))
                self.approval_list_area.append(approvals_table)
                
                # Clear notification
                self.notification_area.object = ""
                
            except Exception as e:
                logger.error(f"Error fetching completed approvals: {e}")
                self.approval_list_area.append(Markdown("# My Completed Approvals"))
                self.approval_list_area.append(Markdown(f"Error fetching completed approvals: {str(e)}"))
                self.notification_area.object = ""
                
        except Exception as e:
            logger.error(f"Error loading completed approvals: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _load_all_approvals(self, event=None):
        """Load all approvals (admin view)"""
        try:
            if not self.user or not permissions.user_has_permission(self.user, "MANAGE_APPROVALS"):
                self.notification_area.object = "**Error:** You do not have permission to view all approvals."
                return
                
            self.current_tab = 'all_approvals'
            self.notification_area.object = "Loading all approvals..."
            
            # Clear areas and prepare main content
            self.approval_list_area.clear()
            self.approval_detail_area.clear()
            self.document_detail_area.clear()
            self.approval_detail_area.visible = False
            self.document_detail_area.visible = False
            
            self.main_content.clear()
            self._update_approval_statistics()
            
            # Add content areas back to main_content
            self.main_content.append(self.stats_area)
            self.main_content.append(self.approval_list_area)
            self.main_content.append(self.approval_detail_area)
            self.main_content.append(self.document_detail_area)
            
            # Create filters
            status_filter = Select(
                name='Status Filter',
                options=['All', 'In Progress', 'Approved', 'Rejected', 'Cancelled', 'Overdue'],
                value='All',
                width=200
            )
            
            date_range = Select(
                name='Date Range',
                options=['Last 7 Days', 'Last 30 Days', 'Last 90 Days', 'All Time'],
                value='Last 30 Days',
                width=200
            )
            
            # Apply filters button
            apply_btn = Button(
                name='Apply Filters',
                button_type='primary',
                width=100
            )
            
            # Add filter row
            filter_row = Row(
                status_filter,
                date_range,
                apply_btn,
                sizing_mode='stretch_width',
                align='center'
            )
            
            # Add filter area
            self.approval_list_area.append(Markdown("# All Approvals"))
            self.approval_list_area.append(Markdown("Manage all approval cycles in the system."))
            self.approval_list_area.append(filter_row)
            
            # Load approvals with default filters
            def load_filtered_approvals(event=None):
                # Convert UI filter values to query parameters
                status_map = {
                    'All': None,
                    'In Progress': ['PENDING', 'IN_APPROVAL'],
                    'Approved': ['APPROVED'],
                    'Rejected': ['REJECTED'],
                    'Cancelled': ['CANCELLED'],
                    'Overdue': ['PENDING', 'IN_APPROVAL']  # Will filter by due date later
                }
                
                date_range_map = {
                    'Last 7 Days': 7,
                    'Last 30 Days': 30,
                    'Last 90 Days': 90,
                    'All Time': None
                }
                
                status_val = status_map[status_filter.value]
                days_val = date_range_map[date_range.value]
                
                # Query approvals from database
                try:
                    # Create date filter
                    date_from = None
                    if days_val:
                        date_from = (datetime.now() - timedelta(days=days_val)).strftime('%Y-%m-%d')
                    
                    # Get approvals with filters
                    # We'll need to implement this query in the database
                    approvals_list = []
                    
                    # Implement using available APIs
                    from CDocs.controllers.approval_controller import get_approval_statistics
                    from CDocs.controllers.document_controller import search_documents
                    
                    # For now, get all documents and then filter
                    docs_result = search_documents(
                        query="",
                        doc_type=None,
                        department=None,
                        status=status_val if status_val else None,
                        owner=None,
                        limit=1000,
                        user=self.user
                    )
                    
                    all_docs = docs_result
                    
                    # Get approvals for each document
                    for doc in all_docs:
                        doc_uid = doc.get('UID', '')
                        if not doc_uid:
                            continue
                            
                        # Get all approvals for this document
                        from CDocs.controllers.approval_controller import get_document_approvals
                        doc_approvals_result = get_document_approvals(doc_uid)
                        
                        if not doc_approvals_result.get('success'):
                            continue
                            
                        doc_approvals = doc_approvals_result.get('approvals', [])
                        
                        # Add document info to each approval
                        for approval in doc_approvals:
                            approval['document'] = {
                                'uid': doc.get('UID', ''),
                                'docNumber': doc.get('docNumber', ''),
                                'title': doc.get('title', '')
                            }
                            approvals_list.append(approval)
                    
                    # Apply filters
                    filtered_approvals = approvals_list
                    
                    # Filter by status if specified
                    if status_val:
                        filtered_approvals = [a for a in filtered_approvals 
                                             if a.get('status', '') in status_val]
                    
                    # Filter by overdue status if selected
                    if status_filter.value == 'Overdue':
                        # Filter approvals that are past due date
                        filtered_approvals = [a for a in filtered_approvals 
                                             if a.get('dueDate') and 
                                               datetime.fromisoformat(a.get('dueDate').replace('Z', '+00:00')) < datetime.now()]
                    
                    # Filter by date if specified
                    if date_from:
                        date_from_dt = datetime.fromisoformat(date_from)
                        filtered_approvals = []
                        for a in filtered_approvals:
                            created_at = a.get('createdAt')
                            if created_at:
                                # Handle different types of date values
                                if isinstance(created_at, str):
                                    # If it's a string, parse it
                                    created_at_dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
                                elif hasattr(created_at, 'to_native'):
                                    # If it's a Neo4j DateTime with to_native method
                                    created_at_dt = created_at.to_native()
                                elif isinstance(created_at, datetime):
                                    # If it's already a datetime object
                                    created_at_dt = created_at
                                else:
                                    # Convert to string first as fallback
                                    created_at_dt = datetime.fromisoformat(str(created_at).replace('Z', '+00:00'))
                                    
                                # Add to filtered list if it meets the date criteria
                                if created_at_dt >= date_from_dt:
                                    filtered_approvals.append(a)
                    
                    # Clear existing table
                    for child in list(self.approval_list_area.objects):
                        if isinstance(child, Tabulator):
                            self.approval_list_area.remove(child)
                            
                    # If no approvals found
                    if not filtered_approvals:
                        self.approval_list_area.append(Markdown("No approvals found matching the selected filters."))
                        self.notification_area.object = ""
                        return
                    
                    # Convert to DataFrame for table display
                    approvals_data = []
                    for approval in filtered_approvals:
                        document = approval.get('document', {})
                        
                        approvals_data.append({
                            'approval_uid': approval.get('UID', ''),
                            'document_uid': document.get('uid', ''),
                            'doc_number': document.get('docNumber', ''),
                            'title': document.get('title', ''),
                            'status': approval.get('status', ''),
                            'approval_type': approval.get('approvalType', ''),
                            'initiated_date': approval.get('createdAt', ''),
                            'due_date': approval.get('dueDate', ''),
                            'completion_date': approval.get('completionDate', ''),
                            'approver_count': len(approval.get('approverAssignments', [])),
                            'is_overdue': approval.get('isOverdue', False)
                        })
                    
                    # Create DataFrame
                    df = pd.DataFrame(approvals_data)
                    
                    # Format dates
                    if 'initiated_date' in df.columns:
                        df['initiated_date'] = df['initiated_date'].apply(self._format_date)
                    if 'due_date' in df.columns:
                        df['due_date'] = df['due_date'].apply(self._format_date)
                    if 'completion_date' in df.columns:
                        df['completion_date'] = df['completion_date'].apply(self._format_date)
                    
                    # Add action column
                    df['action'] = 'View'
                    
                    # Display columns setup
                    display_columns = ['doc_number', 'title', 'status', 'approval_type', 
                                      'initiated_date', 'due_date', 'completion_date',
                                      'approver_count', 'action', 'approval_uid']
                    column_names = {
                        'doc_number': 'Document',
                        'title': 'Title',
                        'status': 'Status',
                        'approval_type': 'Type',
                        'initiated_date': 'Started',
                        'due_date': 'Due Date',
                        'completion_date': 'Completed',
                        'approver_count': 'Approvers',
                        'action': 'Action',
                        'approval_uid': 'approval_uid'
                    }
                    
                    # Filter and rename columns
                    exist_columns = [col for col in display_columns if col in df.columns]
                    df = df[exist_columns]
                    rename_dict = {col: column_names[col] for col in exist_columns if col in column_names}
                    df = df.rename(columns=rename_dict)
                    
                    # Create table
                    approvals_table = Tabulator(
                        df,
                        pagination='local',
                        page_size=20,
                        sizing_mode='stretch_width',
                        selectable=1,
                        height=600,
                        hidden_columns=['approval_uid']
                    )
                    
                    # Add click handler
                    approvals_table.on_click(self._approval_selected)
                    
                    # Add to approval list area
                    self.approval_list_area.append(approvals_table)
                    self.approval_list_area.append(Markdown(f"*Total approvals: {len(approvals_data)}*"))
                    
                    # Clear notification
                    self.notification_area.object = ""
                    
                except Exception as e:
                    logger.error(f"Error loading filtered approvals: {e}")
                    logger.error(traceback.format_exc())
                    self.notification_area.object = f"**Error:** Failed to load approvals."
            
            # Bind apply button action
            apply_btn.on_click(load_filtered_approvals)
            
            # Load initial data
            load_filtered_approvals()
            
        except Exception as e:
            logger.error(f"Error loading all approvals: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _format_date(self, date_str):
        """Format date string for display"""
        if not date_str:
            return ""
        
        try:
            # Handle different date formats
            if isinstance(date_str, str):
                # Handle ISO format with timezone info
                if 'T' in date_str and ('+' in date_str or 'Z' in date_str):
                    # Replace Z with +00:00 for proper parsing
                    date_str = date_str.replace('Z', '+00:00')
                    date_obj = datetime.fromisoformat(date_str)
                # Handle simple ISO format
                elif 'T' in date_str:
                    date_obj = datetime.fromisoformat(date_str)
                # Handle YYYY-MM-DD format
                else:
                    date_obj = datetime.fromisoformat(date_str)
            elif isinstance(date_str, datetime):
                date_obj = date_str
            else:
                return ""
                
            # Format date
            return date_obj.strftime('%Y-%m-%d')
        except Exception as e:
            logger.error(f"Error formatting date {date_str}: {e}")
            return str(date_str)
    
    def _approval_selected(self, event):
        """Handle approval selection from table"""
        try:
            if event.column == 'Action':
                # Get approval UID from the selected row
                approval_uid = event.row['approval_uid']
                
                if not approval_uid:
                    self.notification_area.object = "**Error:** Invalid approval selection."
                    return
                
                # Load the selected approval
                self._load_approval(approval_uid)
        except Exception as e:
            logger.error(f"Error in approval selection: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _load_approval(self, approval_uid):
        """Load approval details"""
        try:
            # Update current tab and UID
            self.current_tab = 'approval_detail'
            self.approval_uid = approval_uid
            self.notification_area.object = "Loading approval details..."
            
            try:
                # Hide list area and show detail area
                self.approval_list_area.visible = False
                self.approval_detail_area.visible = True
                self.approval_detail_area.clear()
                
                # Fetch approval details from controller
                from CDocs.controllers.approval_controller import get_approval
                
                approval_data = get_approval(
                    approval_uid=approval_uid,
                    include_document=True,
                    include_comments=True
                )
                
                if not approval_data:
                    self.approval_detail_area.append(Markdown("# Approval Details"))
                    self.approval_detail_area.append(Markdown("Approval not found or no access."))
                    self.notification_area.object = ""
                    return
                
                # Store data for reuse
                self.approval_data = approval_data
                self.document_data = approval_data.get('document', {})
                
                # Create back button
                back_btn = Button(
                    name='Back to List',
                    button_type='default',
                    width=100
                )
                back_btn.on_click(self._back_to_list)
                
                # Add back button to detail area
                self.approval_detail_area.append(Row(back_btn, sizing_mode='stretch_width'))
                
                # Create and populate the approval detail view
                self._create_approval_detail_view()
                
                # Show document details if available
                if self.document_data:
                    self.document_detail_area.visible = True
                    self.document_detail_area.clear()
                    self._create_document_detail_view()
                
                # Clear notification
                self.notification_area.object = ""
                
            except ResourceNotFoundError:
                self.approval_detail_area.append(Markdown("# Approval Details"))
                self.approval_detail_area.append(Markdown("Approval not found or no access."))
                self.notification_area.object = ""
            except Exception as e:
                logger.error(f"Error fetching approval details: {e}")
                logger.error(traceback.format_exc())
                self.approval_detail_area.append(Markdown("# Approval Details"))
                self.approval_detail_area.append(Markdown(f"Error loading approval: {str(e)}"))
        except Exception as e:
            logger.error(f"Error in _load_approval: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _create_approval_detail_view(self):
        """Create the approval detail view"""
        if not self.approval_data:
            return
            
        # Extract data
        approval = self.approval_data
        document = self.document_data
        
        # Get approval metadata with proper fallbacks for different field names
        status = approval.get('status', '')
        approval_type = approval.get('approvalType', '')
        initiated_date = self._format_date(approval.get('startDate', approval.get('initiated_date', approval.get('createdAt', ''))))
        due_date = self._format_date(approval.get('dueDate', approval.get('due_date', '')))
        initiated_by = approval.get('initiated_by_name', '')
        instructions = approval.get('instructions', '')
        
        # Check if approval is sequential
        is_sequential = approval.get('sequential', False)
        
        # Get approver data - handle different data structures
        approver_assignments = approval.get('approverAssignments', [])
        
        # Check for empty or missing assignments and look for approvers in alternate locations
        if not approver_assignments and 'approvers' in approval:
            # Try to convert approvers list to expected assignment format
            approvers = approval.get('approvers', [])
            approver_assignments = []
            for approver in approvers:
                assignment = {
                    'approver_uid': approver.get('UID', ''),
                    'approver_name': approver.get('name', ''),
                    'role': approver.get('role', ''),
                    'status': approver.get('status', 'PENDING')
                }
                approver_assignments.append(assignment)
                
        # Find the current user's assignment
        my_assignment = None
        if self.user:
            my_assignment = next((r for r in approver_assignments 
                            if r.get('approver_uid', r.get('approverUID')) == self.user.uid), None)
        
        # Get comment data with fallback options
        comments = approval.get('comments', [])
    
        # Create document header with fallbacks for different field naming
        doc_number = document.get('doc_number', document.get('docNumber', ''))
        doc_title = document.get('title', '')
        
        # Calculate approval status stats
        total_approvers = len(approver_assignments)
        approved_count = 0
        rejected_count = 0
        pending_count = 0
        
        for assignment in approver_assignments:
            decision = assignment.get('decision', '')
            status = assignment.get('status', '')
            
            if status == 'COMPLETED':
                if decision == 'APPROVED':
                    approved_count += 1
                elif decision == 'REJECTED':
                    rejected_count += 1
            else:
                pending_count += 1
        
        # Create approval header
        header = Column(
            Markdown(f"# Approval: {doc_number}"),
            Markdown(f"## {doc_title}"),
            sizing_mode='stretch_width'
        )
        
        # Create approval metadata card
        metadata_card = Card(
            Row(
                Column(
                    Markdown("### Approval Details"),
                    Markdown(f"**Status:** {status}"),
                    Markdown(f"**Type:** {approval_type}"),
                    Markdown(f"**Initiated:** {initiated_date}"),
                    Markdown(f"**Due Date:** {due_date}"),
                    width=300
                ),
                Column(
                    Markdown("### Approval Progress"),
                    Markdown(f"**Total Approvers:** {total_approvers}"),
                    Markdown(f"**Approved:** {approved_count}"),
                    Markdown(f"**Rejected:** {rejected_count}"),
                    Markdown(f"**Pending:** {pending_count}"),
                    width=300
                ),
                sizing_mode='stretch_width'
            ),
            title="Approval Information",
            styles={'background': '#f8f9fa'}
        )
        
        # Check if user can perform approval action
        can_approve = False
        if my_assignment and my_assignment.get('status') != 'COMPLETED' and status in ['PENDING', 'IN_APPROVAL']:
            can_approve = True
        
        # Create approval action card if user can approve
        action_card = None
        if can_approve:
            # Create approval form
            approval_comments = TextAreaInput(
                name="Comments",
                placeholder="Enter any comments about your approval decision...",
                height=100,
                width=400
            )
            
            approve_btn = Button(
                name="Approve",
                button_type="success",
                width=150
            )
            
            reject_btn = Button(
                name="Reject",
                button_type="danger", 
                width=150
            )
            
            # Create action functions
            def approve_action(event):
                self._submit_approval_decision('APPROVED', approval_comments.value)
                
            def reject_action(event):
                self._submit_approval_decision('REJECTED', approval_comments.value)
                
            # Bind actions to buttons
            approve_btn.on_click(approve_action)
            reject_btn.on_click(reject_action)
            
            # Create action card
            action_card = Card(
                Column(
                    Markdown("### Submit Your Approval"),
                    Markdown("Please review the document and provide your decision:"),
                    approval_comments,
                    Row(
                        approve_btn,
                        reject_btn,
                        sizing_mode='stretch_width',
                        align='end'
                    ),
                    sizing_mode='stretch_width'
                ),
                title="Approval Action",
                styles={'background': '#e9f5ff'}
            )
        
        # Create instructions card if instructions exist
        instructions_card = None
        if instructions and len(instructions.strip()) > 0:
            instructions_card = Card(
                Markdown(instructions),
                title="Approval Instructions",
                styles={'background': '#f8f9fa'}
            )
            
        # Create approvers table
        approvers_table_data = []
        for idx, approver in enumerate(approver_assignments):
            approvers_table_data.append({
                'index': idx + 1,
                'name': approver.get('approver_name', approver.get('name', '')),
                'role': approver.get('role', ''),
                'status': approver.get('status', 'PENDING'),
                'decision': approver.get('decision', ''),
                'decision_date': self._format_date(approver.get('decisionDate', approver.get('decision_date', ''))),
                'sequence': approver.get('sequenceOrder', approver.get('sequence_order', ''))
            })
            
        # Create DataFrame for approvers table
        approvers_df = pd.DataFrame(approvers_table_data)
        
        # Create approvers card
        approvers_card = Card(
            Column(
                Markdown(f"### Approvers {' (Sequential)' if is_sequential else ''}"),
                Tabulator(
                    approvers_df,
                    pagination=False,
                    selectable=False,
                    sizing_mode='stretch_width'
                ),
                sizing_mode='stretch_width'
            ),
            title="Assigned Approvers",
            styles={'background': '#f8f9fa'}
        )
            
        # Create comments table if there are comments
        comments_card = None
        if comments:
            # Prepare comments data
            comments_table_data = []
            for comment in comments:
                comments_table_data.append({
                    'user_name': comment.get('user_name', ''),
                    'text': comment.get('text', ''),
                    'timestamp': self._format_date(comment.get('timestamp', '')),
                    'requires_resolution': comment.get('requiresResolution', False),
                    'is_resolved': comment.get('resolution') is not None,
                    'resolution': comment.get('resolution', '')
                })
                
            # Create DataFrame for comments table
            comments_df = pd.DataFrame(comments_table_data)
            
            # Create comments card
            comments_card = Card(
                Column(
                    Markdown("### Approval Comments"),
                    Tabulator(
                        comments_df,
                        pagination=False,
                        selectable=False,
                        sizing_mode='stretch_width'
                    ),
                    sizing_mode='stretch_width'
                ),
                title="Comments and Issues",
                styles={'background': '#f8f9fa'}
            )
        
        # Add all components to the approval detail area
        self.approval_detail_area.append(header)
        self.approval_detail_area.append(metadata_card)
        
        # Add action card if exists
        if action_card:
            self.approval_detail_area.append(action_card)
            
        # Add instructions card if exists
        if instructions_card:
            self.approval_detail_area.append(instructions_card)
            
        # Always add approvers card
        self.approval_detail_area.append(approvers_card)
        
        # Add comments card if exists
        if comments_card:
            self.approval_detail_area.append(comments_card)
            
        # Add admin actions if user has manage permissions
        if self.user and permissions.user_has_permission(self.user, "MANAGE_APPROVALS") and status not in ["APPROVED", "REJECTED", "CANCELLED"]:
            admin_actions = Row(
                Button(name="Cancel Approval", button_type="danger", width=150, 
                      on_click=self._show_cancel_approval_form),
                Button(name="Extend Deadline", button_type="primary", width=150,
                      on_click=self._show_extend_deadline_form),
                Button(name="Add Approver", button_type="primary", width=150,
                      on_click=self._show_add_approver_form),
                sizing_mode='stretch_width'
            )
            
            admin_card = Card(
                Column(
                    Markdown("### Administrative Actions"),
                    admin_actions,
                    sizing_mode='stretch_width'
                ),
                title="Admin Controls",
                styles={'background': '#fff3dc'}
            )
            
            self.approval_detail_area.append(admin_card)
        
    def _create_document_detail_view(self):
        """Create document detail view"""
        if not self.document_data:
            return
            
        # Extract data
        document = self.document_data
        
        # Get document metadata
        doc_number = document.get('docNumber', '')
        doc_title = document.get('title', '')
        doc_version = document.get('version', '')
        doc_status = document.get('status', '')
        doc_type = document.get('docType', '')
        
        # Create document card
        document_card = Card(
            Row(
                Column(
                    Markdown("### Document Information"),
                    Markdown(f"**Number:** {doc_number}"),
                    Markdown(f"**Title:** {doc_title}"),
                    Markdown(f"**Version:** {doc_version}"),
                    width=300
                ),
                Column(
                    Markdown("### Status Information"),
                    Markdown(f"**Status:** {doc_status}"),
                    Markdown(f"**Type:** {doc_type}"),
                    width=300
                ),
                sizing_mode='stretch_width'
            ),
            title="Document Details",
            styles={'background': '#f8f9fa'}
        )
        
        # Create document access controls
        try:
            from CDocs.ui.components.document_access_controls import DocumentAccessControls
            
            # Get document UID from document data
            document_uid = self.document_data.get('uid') if self.document_data else None
            
            if document_uid and self.user:
                # Create access controls for this document
                access_controls = DocumentAccessControls(
                    document_uid=document_uid,
                    user_uid=self.user.uid,
                    show_access_indicator=False
                )
                
                document_access_view = access_controls.view()
            else:
                # Fallback to simple button
                view_btn = Button(
                    name="View Document",
                    button_type="primary",
                    width=150
                )
                view_btn.on_click(lambda event: self._view_document())
                document_access_view = view_btn
                
        except Exception as e:
            logger.warning(f"Error creating access controls: {e}")
            # Fallback to simple button
            view_btn = Button(
                name="View Document",
                button_type="primary",
                width=150
            )
            view_btn.on_click(lambda event: self._view_document())
            document_access_view = view_btn

        # Create document actions
        document_actions = Card(
            Row(
                document_access_view,
                sizing_mode='stretch_width',
                align='center'
            ),
            title="Document Actions",
            styles={'background': '#f8f9fa'}
        )
        
        # Add to document detail area
        self.document_detail_area.append(document_card)
        self.document_detail_area.append(document_actions)
    
    def _back_to_list(self, event=None):
        """Navigate back to approval list"""
        self.approval_list_area.visible = True
        self.approval_detail_area.visible = False
        self.document_detail_area.visible = False
        
        # Clear stored data
        self.approval_data = None
        self.document_data = None
        
        # Return to appropriate tab
        if self.current_tab == 'approval_detail':
            self._refresh_current_view()
    
    def _submit_approval_decision(self, decision, comments=None):
        """Submit approval decision"""
        try:
            if not self.approval_uid:
                self.notification_area.object = "**Error:** No approval selected."
                return
                
            if not self.user:
                self.notification_area.object = "**Error:** You must be logged in to perform this action."
                return
                
            # Show loading message
            self.notification_area.object = "**Submitting your approval decision...**"
            
            # Import controller function
            from CDocs.controllers.approval_controller import complete_approval
            
            # Submit approval decision
            result = complete_approval(
                user=self.user,
                approval_uid=self.approval_uid,
                decision=decision,
                comments=comments
            )
            
            if result.get('success'):
                self.notification_area.object = f"**Success:** Your approval decision has been recorded."
                # Reload approval details
                time.sleep(1)  # Small delay to ensure data is updated
                self._load_approval(self.approval_uid)
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to record your approval decision.')}"
                
        except Exception as e:
            logger.error(f"Error submitting approval decision: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _view_document(self):
        """View the document associated with the approval"""
        if not self.document_data:
            self.notification_area.object = "**Error:** No document available."
            return
            
        try:
            # Get document UID
            document_uid = self.document_data.get('uid')
            
            if not document_uid:
                self.notification_area.object = "**Error:** Document UID not available."
                return
            
            # Import document controller function
            from CDocs.controllers.document_controller import get_document_view_url
            
            # Get document URL
            result = get_document_view_url(
                user=self.user,
                document_uid=document_uid
            )
            
            if result.get('success'):
                url = result.get('view_url')
                # Open document in new tab
                import panel as pn
                pn.state.execute(f"window.open('{url}', '_blank')")
                self.notification_area.object = "**Document opened in new tab.**"
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to open document.')}"
                
        except Exception as e:
            logger.error(f"Error viewing document: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
    
    def _show_cancel_approval_form(self, event=None):
        """Show form to cancel the approval cycle"""
        if not self.approval_uid:
            self.notification_area.object = "**Error:** No approval selected."
            return
            
        # Create cancel form
        cancel_reason = TextAreaInput(
            name="Cancellation Reason",
            placeholder="Please provide a reason for cancelling the approval cycle...",
            height=100,
            width=400
        )
        
        confirm_btn = Button(
            name="Cancel Approval Cycle",
            button_type="danger",
            width=150
        )
        
        cancel_btn = Button(
            name="Back",
            button_type="default",
            width=150
        )
        
        # Create action function
        def confirm_cancel(event):
            self._cancel_approval_cycle(cancel_reason.value)
            
        def hide_form(event):
            # Remove the form
            self._refresh_current_view()
            
        # Bind actions to buttons
        confirm_btn.on_click(confirm_cancel)
        cancel_btn.on_click(hide_form)
        
        # Create form card
        form_card = Card(
            Column(
                Markdown("### Cancel Approval Cycle"),
                Markdown("This will permanently cancel the approval cycle and any pending approval decisions."),
                Markdown("**This action cannot be undone.**"),
                cancel_reason,
                Row(
                    confirm_btn,
                    cancel_btn,
                    sizing_mode='stretch_width',
                    align='end'
                ),
                sizing_mode='stretch_width'
            ),
            title="Cancel Approval Cycle",
            styles={'background': '#fff0f0'}
        )
        
        # Show form
        self.main_content.clear()
        self.main_content.append(form_card)
    
    def _cancel_approval_cycle(self, reason):
        """Cancel the approval cycle"""
        try:
            if not self.approval_uid:
                self.notification_area.object = "**Error:** No approval selected."
                return
                
            if not reason or not reason.strip():
                self.notification_area.object = "**Error:** Cancellation reason is required."
                return
                
            # Show loading message
            self.notification_area.object = "**Cancelling approval cycle...**"
            
            # Import controller function
            from CDocs.controllers.approval_controller import cancel_approval_cycle
            
            # Cancel approval cycle
            result = cancel_approval_cycle(
                user=self.user,
                approval_uid=self.approval_uid,
                reason=reason
            )
            
            if result.get('success'):
                self.notification_area.object = f"**Success:** The approval cycle has been cancelled."
                # Reload approval details
                time.sleep(1)  # Small delay to ensure data is updated
                self._load_approval(self.approval_uid)
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to cancel approval cycle.')}"
                # Reload the current view
                self._refresh_current_view()
                
        except Exception as e:
            logger.error(f"Error cancelling approval cycle: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
            # Reload the current view
            self._refresh_current_view()
    
    def _show_extend_deadline_form(self, event=None):
        """Show form to extend the approval deadline"""
        if not self.approval_uid or not self.approval_data:
            self.notification_area.object = "**Error:** No approval selected."
            return
            
        # Get current due date
        current_due_date = None
        try:
            due_date_str = self.approval_data.get('dueDate')
            if due_date_str:
                if isinstance(due_date_str, str):
                    # Handle ISO format with timezone info
                    due_date_str = due_date_str.replace('Z', '+00:00')
                    current_due_date = datetime.fromisoformat(due_date_str).date()
                elif isinstance(due_date_str, datetime):
                    current_due_date = due_date_str.date()
        except Exception as e:
            logger.error(f"Error parsing due date {due_date_str}: {e}")
            
        # Create default value for new date picker (current due date + 7 days)
        default_new_date = (current_due_date + timedelta(days=7) if current_due_date else 
                          (datetime.now() + timedelta(days=7)).date())
        
        # Create extension form
        new_date = DatePicker(
            name="New Due Date",
            value=default_new_date,
            width=200
        )
        
        extension_reason = TextAreaInput(
            name="Reason for Extension",
            placeholder="Please provide a reason for extending the deadline...",
            height=100,
            width=400
        )
        
        confirm_btn = Button(
            name="Extend Deadline",
            button_type="primary",
            width=150
        )
        
        cancel_btn = Button(
            name="Back",
            button_type="default",
            width=150
        )
        
        # Create action function
        def confirm_extend(event):
            # Convert date to datetime
            new_due_date = datetime.combine(new_date.value, datetime.min.time())
            self._extend_approval_deadline(new_due_date, extension_reason.value)
            
        def hide_form(event):
            # Remove the form
            self._refresh_current_view()
            
        # Bind actions to buttons
        confirm_btn.on_click(confirm_extend)
        cancel_btn.on_click(hide_form)
        
        # Create form card
        form_card = Card(
            Column(
                Markdown("### Extend Approval Deadline"),
                Markdown(f"Current due date: {self._format_date(self.approval_data.get('dueDate'))}"),
                new_date,
                extension_reason,
                Row(
                    confirm_btn,
                    cancel_btn,
                    sizing_mode='stretch_width',
                    align='end'
                ),
                sizing_mode='stretch_width'
            ),
            title="Extend Approval Deadline",
            styles={'background': '#f0f8ff'}
        )
        
        # Show form
        self.main_content.clear()
        self.main_content.append(form_card)
    
    def _extend_approval_deadline(self, new_due_date, reason):
        """Extend the approval deadline"""
        try:
            if not self.approval_uid:
                self.notification_area.object = "**Error:** No approval selected."
                return
                
            if not new_due_date:
                self.notification_area.object = "**Error:** New due date is required."
                return
                
            # Show loading message
            self.notification_area.object = "**Extending approval deadline...**"
            
            # Import controller function
            from CDocs.controllers.approval_controller import extend_approval_deadline
            
            # Extend approval deadline
            result = extend_approval_deadline(
                user=self.user,
                approval_uid=self.approval_uid,
                new_due_date=new_due_date,
                reason=reason
            )
            
            if result.get('success'):
                self.notification_area.object = f"**Success:** The approval deadline has been extended."
                # Reload approval details
                time.sleep(1)  # Small delay to ensure data is updated
                self._load_approval(self.approval_uid)
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to extend deadline.')}"
                # Reload the current view
                self._refresh_current_view()
                
        except Exception as e:
            logger.error(f"Error extending approval deadline: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
            # Reload the current view
            self._refresh_current_view()
    
    def _show_add_approver_form(self, event=None):
        """Show form to add a new approver"""
        if not self.approval_uid:
            self.notification_area.object = "**Error:** No approval selected."
            return
        
        # TODO: Implement user search/selection
        # For now, a simple text input for user UID
        user_uid_input = TextInput(
            name="Approver User ID",
            placeholder="Enter the user ID of the approver to add",
            width=300
        )
        
        instructions_input = TextAreaInput(
            name="Instructions",
            placeholder="Enter specific instructions for this approver (optional)",
            height=100,
            width=400
        )
        
        confirm_btn = Button(
            name="Add Approver",
            button_type="primary",
            width=150
        )
        
        cancel_btn = Button(
            name="Back",
            button_type="default",
            width=150
        )
        
        # Create action function
        def confirm_add(event):
            self._add_approver(user_uid_input.value, instructions_input.value)
            
        def hide_form(event):
            # Remove the form
            self._refresh_current_view()
            
        # Bind actions to buttons
        confirm_btn.on_click(confirm_add)
        cancel_btn.on_click(hide_form)
        
        # Create form card
        form_card = Card(
            Column(
                Markdown("### Add Approver"),
                Markdown("Add a new approver to the current approval cycle."),
                user_uid_input,
                instructions_input,
                Row(
                    confirm_btn,
                    cancel_btn,
                    sizing_mode='stretch_width',
                    align='end'
                ),
                sizing_mode='stretch_width'
            ),
            title="Add Approver",
            styles={'background': '#f0f8ff'}
        )
        
        # Show form
        self.main_content.clear()
        self.main_content.append(form_card)
    
    def _add_approver(self, approver_uid, instructions):
        """Add a new approver to the approval cycle"""
        try:
            if not self.approval_uid:
                self.notification_area.object = "**Error:** No approval selected."
                return
                
            if not approver_uid or not approver_uid.strip():
                self.notification_area.object = "**Error:** Approver User ID is required."
                return
                
            # Show loading message
            self.notification_area.object = "**Adding approver...**"
            
            # Import controller function
            from CDocs.controllers.approval_controller import add_approver_to_active_approval
            
            # Add approver
            result = add_approver_to_active_approval(
                user=self.user,
                approval_uid=self.approval_uid,
                approver_uid=approver_uid,
                instructions=instructions
            )
            
            if result.get('success'):
                self.notification_area.object = f"**Success:** The approver has been added."
                # Reload approval details
                time.sleep(1)  # Small delay to ensure data is updated
                self._load_approval(self.approval_uid)
            else:
                self.notification_area.object = f"**Error:** {result.get('message', 'Failed to add approver.')}"
                # Reload the current view
                self._refresh_current_view()
                
        except Exception as e:
            logger.error(f"Error adding approver: {e}")
            self.notification_area.object = f"**Error:** {str(e)}"
            # Reload the current view
            self._refresh_current_view()

        # Add the following method to the ApprovalPanel class:
    def get_main_content(self):
        """Return just the main content for embedding in other panels"""
        # Ensure notification area is included with main content
        container = pn.Column(
            self.notification_area,
            self.main_content,
            sizing_mode='stretch_width'
        )
        return container

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, template, session_manager, parent_app, embedded)

Purpose: Internal method: init

Parameters:

  • template: Parameter
  • session_manager: Parameter
  • parent_app: Parameter
  • embedded: Parameter

Returns: None

_get_current_user(self) -> Optional['DocUser']

Purpose: Get the current user from session or parent app

Returns: Returns Optional['DocUser']

set_user(self, user)

Purpose: Set the current user - needed for compatibility with main app

Parameters:

  • user: Parameter

Returns: None

_setup_header(self)

Purpose: Set up the header with title and actions

Returns: None

_setup_sidebar(self)

Purpose: Set up the sidebar with navigation options

Returns: None

_setup_main_area(self)

Purpose: Set up the main area with approvals and details

Returns: None

_refresh_current_view(self, event)

Purpose: Refresh the current view

Parameters:

  • event: Parameter

Returns: None

_navigate_back(self, event)

Purpose: Navigate back to dashboard

Parameters:

  • event: Parameter

Returns: None

_update_approval_statistics(self)

Purpose: Update approval statistics for the current user.

Returns: None

_load_pending_approvals(self, event)

Purpose: Load pending approvals for the current user

Parameters:

  • event: Parameter

Returns: None

_load_completed_approvals(self, event)

Purpose: Load completed approvals for the current user

Parameters:

  • event: Parameter

Returns: None

_load_all_approvals(self, event)

Purpose: Load all approvals (admin view)

Parameters:

  • event: Parameter

Returns: None

_format_date(self, date_str)

Purpose: Format date string for display

Parameters:

  • date_str: Parameter

Returns: None

_approval_selected(self, event)

Purpose: Handle approval selection from table

Parameters:

  • event: Parameter

Returns: None

_load_approval(self, approval_uid)

Purpose: Load approval details

Parameters:

  • approval_uid: Parameter

Returns: None

_create_approval_detail_view(self)

Purpose: Create the approval detail view

Returns: None

_create_document_detail_view(self)

Purpose: Create document detail view

Returns: None

_back_to_list(self, event)

Purpose: Navigate back to approval list

Parameters:

  • event: Parameter

Returns: None

_submit_approval_decision(self, decision, comments)

Purpose: Submit approval decision

Parameters:

  • decision: Parameter
  • comments: Parameter

Returns: None

_view_document(self)

Purpose: View the document associated with the approval

Returns: None

_show_cancel_approval_form(self, event)

Purpose: Show form to cancel the approval cycle

Parameters:

  • event: Parameter

Returns: None

_cancel_approval_cycle(self, reason)

Purpose: Cancel the approval cycle

Parameters:

  • reason: Parameter

Returns: None

_show_extend_deadline_form(self, event)

Purpose: Show form to extend the approval deadline

Parameters:

  • event: Parameter

Returns: None

_extend_approval_deadline(self, new_due_date, reason)

Purpose: Extend the approval deadline

Parameters:

  • new_due_date: Parameter
  • reason: Parameter

Returns: None

_show_add_approver_form(self, event)

Purpose: Show form to add a new approver

Parameters:

  • event: Parameter

Returns: None

_add_approver(self, approver_uid, instructions)

Purpose: Add a new approver to the approval cycle

Parameters:

  • approver_uid: Parameter
  • instructions: Parameter

Returns: None

get_main_content(self)

Purpose: Return just the main content for embedding in other panels

Returns: See docstring for return details

Required Imports

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

Usage Example

# Example usage:
# result = ApprovalPanel(bases)

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class ApprovalPanel_v1 98.0% similar

    Approval management interface component

    From: /tf/active/vicechatdev/CDocs/ui/approval_panel.py
  • class ReviewPanel 75.1% similar

    Review management interface component

    From: /tf/active/vicechatdev/CDocs/ui/review_panel.py
  • function create_approval_panel_v1 71.3% similar

    Factory function that creates and initializes an ApprovalPanel instance for managing document approvals, with error handling and fallback to a minimal panel on failure.

    From: /tf/active/vicechatdev/CDocs/ui/approval_panel.py
  • function create_approval_panel 70.5% similar

    Factory function that creates and initializes an ApprovalPanel instance with error handling, supporting both standalone and embedded modes for document approval management.

    From: /tf/active/vicechatdev/CDocs/ui/approval_panel_bis.py
  • class AdminPanel 70.3% similar

    Admin configuration interface component

    From: /tf/active/vicechatdev/CDocs/ui/admin_panel.py
← Back to Browse