class ApprovalPanel
Approval management interface component
/tf/active/vicechatdev/CDocs/ui/approval_panel_bis.py
62 - 1895
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: Parametersession_manager: Parameterparent_app: Parameterembedded: 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: Parametercomments: 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: Parameterreason: 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: Parameterinstructions: 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)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class ApprovalPanel_v1 98.0% similar
-
class ReviewPanel 75.1% similar
-
function create_approval_panel_v1 71.3% similar
-
function create_approval_panel 70.5% similar
-
class AdminPanel 70.3% similar