class DocumentDashboard_v1
Dashboard for viewing and managing controlled documents.
/tf/active/vicechatdev/CDocs single class/ui/document_dashboard.py
37 - 1028
moderate
Purpose
Dashboard for viewing and managing controlled documents.
Source Code
class DocumentDashboard(param.Parameterized):
"""Dashboard for viewing and managing controlled documents."""
doc_selected = param.String(default='')
search_query = param.String(default='')
doc_type_filter = param.Parameter() # Use Parameter to accept any type of value
department_filter = param.String(default='')
status_filter = param.String(default='')
date_from = param.Date(default=None)
date_to = param.Date(default=None)
def __init__(self, parent_app=None, **params):
super().__init__(**params)
self.parent_app = parent_app
self.user = None # Will be set by parent app
self.document_list = None
self._view_handler_added = False
# Initialize template attribute to None - avoid AttributeError
self.template = None
# Initialize notification area
self.notification_area = pn.pane.Markdown("")
# Add this line to initialize the document_list_container
self.document_list_container = pn.Column(sizing_mode='stretch_width')
# Initialize document type and status lists
from CDocs.config import settings
# Use full names for document types in dropdowns
self.document_types = list(settings.DOCUMENT_TYPES.keys())
# Use full names for statuses in dropdowns
self.document_statuses = list(settings.DOCUMENT_STATUSES.keys())
# Remove reference to DEFAULT_DEPARTMENTS and use DEPARTMENTS instead
self.departments = list(settings.DEPARTMENTS.keys())
# Create search and filter widgets
self.search_input = TextInput(name="Search", placeholder="Search documents...", width=200)
# Create document type filter - use full names
doc_types = [''] + self.document_types
self.doc_type_select = Select(name="Document Type", options=doc_types, width=200)
# Create status filter
statuses = [''] + self.document_statuses
self.status_select = Select(name="Status", options=statuses, width=200)
# Create department filter - use full names
departments = [''] + self.departments
self.department_select = Select(name="Department", options=departments, width=200)
# Create date filters
self.date_from_picker = DatePicker(name="From Date", width=200)
self.date_to_picker = DatePicker(name="To Date", width=200)
# Store filters for later access
self.filters = {
'search': self.search_input,
'doc_type': self.doc_type_select,
'status': self.status_select,
'department': self.department_select,
'date_from': self.date_from_picker,
'date_to': self.date_to_picker
}
# Initialize documents DataFrame if pandas is available
if HAS_PANDAS:
self.documents_df = pd.DataFrame()
# Create button for new document
self.create_doc_btn = Button(
name='Create New Document',
button_type='primary',
width=150,
disabled=True # Default to disabled until user is set
)
self.create_doc_btn.on_click(self.show_create_document_form)
# Add this line to track event registrations
self._event_handlers_registered = False
def set_user(self, user):
"""Set the current user from parent application"""
self.user = user
# Update UI elements that depend on user permissions
self._update_ui_for_user()
# Modified: Check if template attribute exists before using it
# The template attribute might not be initialized in this class
if hasattr(self, 'template') and self.template:
self._setup_sidebar()
else:
logger.debug("No template attribute available, skipping sidebar setup")
# Load initial documents
self.update_document_list()
def _update_ui_for_user(self):
"""Update UI components based on current user permissions"""
# Example: Update create button based on permissions
if hasattr(self, 'create_doc_btn'):
# Enable button if user has permission or if no permissions system
has_permission = True
if hasattr(permissions, 'user_has_permission'):
has_permission = permissions.user_has_permission(self.user, "CREATE_DOCUMENT")
self.create_doc_btn.disabled = not has_permission
def _setup_sidebar(self):
"""Set up the sidebar filters and actions."""
try:
# Check if template exists and has a sidebar
if not hasattr(self, 'template') or not self.template or not hasattr(self.template, 'sidebar'):
logger.warning("No template sidebar available for setup")
return
# Create sidebar section for document management
self.template.sidebar.append(pn.pane.Markdown("## Document Management"))
# Add the create document button to sidebar
self.template.sidebar.append(self.create_doc_btn)
# Create sidebar section for filters
self.template.sidebar.append(pn.pane.Markdown("## Filters"))
# Add widgets to sidebar
self.template.sidebar.append(self.search_input)
self.template.sidebar.append(self.doc_type_select)
self.template.sidebar.append(self.status_select)
self.template.sidebar.append(self.department_select)
self.template.sidebar.append(self.date_from_picker)
self.template.sidebar.append(self.date_to_picker)
# Create button to apply filters
apply_btn = pn.widgets.Button(
name="Apply Filters",
button_type="default",
width=200
)
apply_btn.on_click(self._apply_filters)
self.template.sidebar.append(apply_btn)
# Create button to clear filters
clear_btn = pn.widgets.Button(
name="Clear Filters",
button_type="default",
width=200
)
clear_btn.on_click(self._clear_filters)
self.template.sidebar.append(clear_btn)
except Exception as e:
logger.error(f"Error setting up sidebar: {e}")
import traceback
logger.error(traceback.format_exc())
def get_dashboard_view(self):
"""Get the dashboard view with document list and actions."""
try:
# Create dashboard container
dashboard = pn.Column(sizing_mode='stretch_width')
# Add page header
dashboard.append(pn.pane.Markdown("# Document Dashboard"))
# Add notification area
dashboard.append(self.notification_area)
# Add toolbar with actions
toolbar = pn.Row(
self.create_doc_btn,
pn.widgets.Button(name="Refresh", button_type="default", width=100,
on_click=self.update_document_list),
pn.layout.HSpacer(),
sizing_mode='stretch_width'
)
dashboard.append(toolbar)
# Add horizontal filter bar for convenience
filters = pn.Row(
self.search_input,
self.doc_type_select,
self.status_select,
pn.widgets.Button(name="Apply", button_type="default", width=60,
on_click=self._apply_filters),
pn.widgets.Button(name="Reset", button_type="default", width=60,
on_click=self._clear_filters),
pn.layout.HSpacer(),
sizing_mode='stretch_width'
)
dashboard.append(filters)
# Make sure document_list_container exists
if not hasattr(self, 'document_list_container'):
self.document_list_container = pn.Column(sizing_mode='stretch_width')
# If document_list doesn't exist or document_list_container is empty,
# initialize with a message and trigger an update
if self.document_list is None or len(self.document_list_container) == 0:
self.document_list_container.clear()
self.document_list_container.append(pn.pane.Markdown("## Documents"))
self.document_list_container.append(pn.pane.Markdown("Loading documents..."))
# Trigger update_document_list after a short delay to ensure the UI is rendered
pn.state.onload(lambda: self.update_document_list())
# Add document list container to dashboard
dashboard.append(self.document_list_container)
return dashboard
except Exception as e:
logger.error(f"Error creating dashboard view: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
# Return error message
return pn.Column(
pn.pane.Markdown("# Document Dashboard"),
pn.pane.Markdown(f"**Error loading dashboard: {str(e)}**"),
pn.widgets.Button(name="Retry", button_type="default", width=100,
on_click=lambda event: self.get_dashboard_view())
)
def _create_document_list(self):
"""Create the document list component."""
try:
import panel as pn
# Initialize with empty pandas DataFrame
import pandas as pd
empty_df = pd.DataFrame(columns=[
'doc_number', 'title', 'doc_type', 'status', 'created_date', 'action'
])
# Create tabulator with pandas DataFrame - using proper pagination value
self.document_list = pn.widgets.Tabulator(
empty_df,
formatters={'action': 'html'},
pagination="local", # Use "local" instead of True
page_size=10,
sizing_mode="stretch_width",
height=400,
selectable=True
)
# Add click handler for row selection
self.document_list.on_click(self.document_selected)
except Exception as e:
logger.error(f"Error creating document list: {e}")
import traceback
logger.error(traceback.format_exc())
self.document_list = None
# Create a fallback table using markdown
md_table = """
| Document Number | Title | Type | Status | Created |
|---|---|---|---|---|
| No documents found | | | | |
"""
self.document_list = pn.pane.Markdown(md_table)
#@guard_execution(cooldown_ms=1000)
def update_document_list(self, event=None):
"""Update the document list with current filters."""
logger.debug("Updating document list")
try:
# Show loading message
self.notification_area.object = "Loading documents..."
# Clear document list container
if not hasattr(self, 'document_list_container'):
self.document_list_container = pn.Column(sizing_mode='stretch_width')
self.document_list_container.clear()
# Set up document list header
self.document_list_container.append(pn.pane.Markdown("## Document List"))
# Get filter values
doc_type = self.doc_type_select.value if hasattr(self, 'doc_type_select') else None
status = self.status_select.value if hasattr(self, 'status_select') else None
date_from = self.date_from_picker.value if hasattr(self, 'date_from_picker') else None
date_to = self.date_to_picker.value if hasattr(self, 'date_to_picker') else None
search_text = self.search_input.value if hasattr(self, 'search_input') else None
# Convert display values to codes for API calls
if doc_type and doc_type != '':
doc_type = settings.get_document_type_code(doc_type)
if status and status != '':
status = settings.get_document_status_code(status)
logger.debug(f"Filters - Type: {doc_type}, Status: {status}, Search: {search_text}")
# Get documents
from CDocs.controllers.document_controller import get_documents
result = get_documents(
user=self.user,
doc_type=doc_type if doc_type and doc_type != '' else None,
status=status if status and status != '' else None,
date_from=date_from.isoformat() if date_from else None,
date_to=date_to.isoformat() if date_to else None,
search_text=search_text if search_text else None
)
documents = result.get('documents', [])
logger.debug(f"Retrieved {len(documents)} documents")
if not documents:
self.document_list_container.append(
pn.pane.Markdown("No documents found matching your criteria.")
)
self.notification_area.object = ""
return
# Debug the Neo4j document structure
if documents and len(documents) > 0:
logger.debug(f"First document sample: {documents[0].keys()}")
# Format documents for display with Neo4j field names
data = []
from CDocs.config import settings
for doc in documents:
# Get status code for workflow checks
status_code = doc.get('status')
# Create action buttons based on status
actions = f"""<button class='btn btn-sm btn-primary view-btn' data-uid='{doc.get('UID')}'>View</button>"""
# For documents in review, add review button
if status_code == 'IN_REVIEW':
# Get latest review cycle if any
review_cycles = doc.get('review_cycles', [])
if review_cycles:
latest_review = review_cycles[0] # Assuming most recent first
review_uid = latest_review.get('UID')
actions += f""" <button class='btn btn-sm btn-info review-btn' data-uid='{review_uid}'>Review</button>"""
# For documents in approval, add approval button
if status_code == 'IN_APPROVAL':
# Get latest approval cycle if any
approval_cycles = doc.get('approval_cycles', [])
if approval_cycles:
latest_approval = approval_cycles[0] # Assuming most recent first
approval_uid = latest_approval.get('UID')
actions += f""" <button class='btn btn-sm btn-warning approval-btn' data-uid='{approval_uid}'>Approve</button>"""
# Add row data
data.append({
'UID': doc.get('UID', ''),
'doc_number': doc.get('doc_number', doc.get('docNumber', 'N/A')),
'title': doc.get('title', 'Untitled'),
'doc_type': settings.get_document_type_name(doc.get('doc_type', doc.get('docType', ''))),
'status': settings.get_document_status_name(status_code),
'last_updated': doc.get('updated_at', doc.get('last_updated', '')),
'action': actions
})
# Create DataFrame
df = pd.DataFrame(data)
# Format date columns
date_columns = ['last_updated']
for col in date_columns:
if col in df.columns:
df[col] = pd.to_datetime(df[col], errors='coerce').dt.strftime('%Y-%m-%d %H:%M')
# Create Tabulator widget for Panel 1.6.1
doc_table = pn.widgets.Tabulator(
df,
formatters={
'action': {
'type': 'html',
'label': 'Action'
}
},
selectable='checkbox',
pagination='local',
page_size=10,
sizing_mode='stretch_width',
hidden_columns=['UID'],
selection=[]
)
# Update reference and set up handlers - ONCE
self.document_list = doc_table
# Setup event handlers - register click events
self.setup_event_handlers()
# Add table to container - IMPORTANT: Add to container BEFORE setting up JS callbacks
self.document_list_container.append(doc_table)
# Set up JS button handling AFTER adding to container
self.setup_view_buttons()
# Show document count
self.document_list_container.append(pn.pane.Markdown(f"*Showing {len(documents)} document(s)*"))
# Clear notification
self.notification_area.object = ""
except Exception as e:
logger.error(f"Error updating document list: {str(e)}")
logger.error(f"Traceback: {traceback.format_exc()}")
self.notification_area.object = f"**Error loading documents:** {str(e)}"
# Safely create or clear the container
if not hasattr(self, 'document_list_container'):
self.document_list_container = pn.Column(sizing_mode='stretch_width')
else:
self.document_list_container.clear()
self.document_list_container.append(pn.pane.Markdown("Error loading documents. Please try again."))
def _show_sample_documents(self):
"""Show sample documents when database access fails."""
try:
# Create sample data
sample_data = [
{
"doc_number": "SOP-QA-001",
"title": "Document Control Procedure",
"doc_type": "SOP",
"status": "Published",
"created_date": "2025-01-15",
"action": "<button class='btn btn-sm btn-primary'>View</button>"
},
{
"doc_number": "POL-QA-001",
"title": "Quality Management System Manual",
"doc_type": "Policy",
"status": "Published",
"created_date": "2025-02-01",
"action": "<button class='btn btn-sm btn-primary'>View</button>"
},
{
"doc_number": "FORM-QA-001",
"title": "Change Control Request Form",
"doc_type": "Form",
"status": "Draft",
"created_date": "2025-03-10",
"action": "<button class='btn btn-sm btn-primary'>View</button>"
},
{
"doc_number": "WI-MFG-001",
"title": "Product Assembly Instructions",
"doc_type": "Work Instruction",
"status": "In Review",
"created_date": "2025-03-05",
"action": "<button class='btn btn-sm btn-primary'>View</button>"
},
{
"doc_number": "TMPL-RES-001",
"title": "Research Report Template",
"doc_type": "Template",
"status": "Approved",
"created_date": "2025-02-20",
"action": "<button class='btn btn-sm btn-primary'>View</button>"
}
]
# Create document list if it doesn't exist
if not self.document_list:
self._create_document_list()
# Convert to pandas DataFrame for Tabulator
try:
import pandas as pd
sample_df = pd.DataFrame(sample_data)
# Update the document list with sample data
if isinstance(self.document_list, pn.widgets.Tabulator):
self.document_list.value = sample_df
elif isinstance(self.document_list, pn.pane.Markdown):
# If we have the markdown fallback
self.document_list.object = self._format_documents_as_markdown(sample_data)
except ImportError:
# If pandas is not available, use markdown fallback
self.document_list = pn.pane.Markdown(self._format_documents_as_markdown(sample_data))
except Exception as e:
logger.error(f"Error in _show_sample_documents: {e}")
if hasattr(self, 'notification_area'):
self.notification_area.object = f"Error showing sample documents: {str(e)}"
def _format_documents_as_markdown(self, documents):
"""Format documents as markdown table for fallback display."""
header = """
| Document Number | Title | Type | Status | Created |
|---|---|---|---|---|
"""
if not documents:
return header + "| No documents found | | | | |"
rows = []
for doc in documents:
row = f"| {doc.get('doc_number', 'N/A')} | {doc.get('title', 'Untitled')} | " \
f"{doc.get('doc_type', 'General')} | {doc.get('status', 'Draft')} | " \
f"{doc.get('created_date', '')} |"
rows.append(row)
return header + "\n".join(rows)
#@guard_execution()
def document_selected(self, event):
"""Handle document selection from table"""
logger.debug("Document selection event triggered")
try:
logger.debug(f"Event type: {type(event).__name__}")
# With Panel 1.6.1, the event structure is different
# We need to access the document_list directly instead of relying on the event
# Get the currently selected document table
if not hasattr(self, 'document_list') or self.document_list is None:
logger.error("No document list available")
self.notification_area.object = "**Error:** Document list not initialized"
return
table = self.document_list # Use the current document_list
# Extract row information from the event
row_index = None
# Handle different event types
if hasattr(event, 'row') and event.row is not None: # CellClickEvent
row_index = event.row
logger.debug(f"Using row from cell click: {row_index}")
# Also check if this is a click on the action column (View button)
if hasattr(event, 'column') and event.column == 'action':
logger.debug("Click detected on action (View) button")
elif hasattr(event, 'selected') and event.selected: # Selection event
row_index = event.selected[0] if isinstance(event.selected, list) else event.selected
logger.debug(f"Using row from selection event: {row_index}")
elif hasattr(event, 'new') and event.new: # Alternative selection format
row_index = event.new[0] if isinstance(event.new, list) else event.new
logger.debug(f"Using row from event.new: {row_index}")
if row_index is None:
logger.warning("Could not determine row index from event")
# Try a fallback method - check if there's a selection on the table
if hasattr(table, 'selection') and table.selection:
row_index = table.selection[0] if isinstance(table.selection, list) else table.selection
logger.debug(f"Using row from table selection: {row_index}")
else:
self.notification_area.object = "**Error:** Could not determine which document was selected"
return
# Get the DataFrame from the table
df = None
if hasattr(table, 'value'):
df = table.value
logger.debug(f"Got DataFrame from table.value with {len(df)} rows")
else:
logger.error("Table has no value attribute")
self.notification_area.object = "**Error:** Could not access table data"
return
if df is None or len(df) == 0 or row_index >= len(df):
logger.error(f"Invalid DataFrame or row index: df={df is not None}, len(df)={len(df) if df is not None else 0}, idx={row_index}")
self.notification_area.object = "**Error:** Could not access selected document data"
return
# Debug the DataFrame columns and the selected row
logger.debug(f"DataFrame columns: {df.columns.tolist()}")
logger.debug(f"Selected row data: {df.iloc[row_index].to_dict()}")
# Check for 'uid' column and extract document UID
if 'UID' in df.columns:
doc_uid = df.iloc[row_index]['UID']
logger.debug(f"Found document UID in 'UID' column: {doc_uid}")
# Call parent app's load_document method
if hasattr(self, 'parent_app') and self.parent_app and hasattr(self.parent_app, 'load_document'):
logger.debug(f"Calling parent_app.load_document with UID: {doc_uid}")
self.parent_app.load_document(doc_uid)
else:
logger.error("No parent_app reference available to load document")
self.notification_area.object = "**Error:** Cannot load document (missing parent app reference)"
return
# Fallback: Try to find the document by doc_number
if 'doc_number' in df.columns:
doc_number = df.iloc[row_index]['doc_number']
logger.debug(f"Looking up document by doc_number: {doc_number}")
# Import within function to avoid circular imports
from CDocs.controllers.document_controller import get_documents
result = get_documents(
user=self.user,
doc_number=doc_number
)
documents = result.get('documents', [])
if documents and len(documents) > 0:
# Try to get the UID using the same helper method we use when building the table
doc_uid = self._get_field_case_insensitive(documents[0], ['UID', 'uid'])
if doc_uid:
logger.debug(f"Found UID by doc_number lookup: {doc_uid}")
# Call parent app's load_document method
if hasattr(self, 'parent_app') and self.parent_app and hasattr(self.parent_app, 'load_document'):
self.parent_app.load_document(doc_uid)
else:
logger.error("No parent_app reference available to load document")
self.notification_area.object = "**Error:** Cannot load document (missing parent app reference)"
return
# If we got here, we couldn't find a document UID
logger.error("Could not determine document UID from row data")
self.notification_area.object = "**Error:** Could not determine document ID"
except Exception as e:
logger.error(f"Error selecting document: {str(e)}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
self.notification_area.object = f"**Error:** {str(e)}"
def show_create_document_form(self, event=None):
"""Show the create document form"""
logger.debug("Showing create document form")
try:
# Clear document list container
self.document_list_container.clear()
# Set notification
self.notification_area.object = "Creating new document"
# Create form widgets
title_input = pn.widgets.TextInput(name="Title", placeholder="Enter document title", width=400)
# Get document types from settings
from CDocs.config import settings
# Use full names as options but store the mapping to codes
doc_type_options = list(settings.DOCUMENT_TYPES.keys()) # Full names as options
logger.debug(f"Document type dropdown options: {doc_type_options}")
# Create the dropdown with full names as options
doc_type_select = pn.widgets.Select(
name="Document Type",
options=doc_type_options,
width=200
)
# Department selector - use full names as options
dept_options = list(settings.DEPARTMENTS.keys()) # Full names as options
department_select = pn.widgets.Select(name="Department", options=dept_options, width=200)
# Content textarea
content_area = pn.widgets.TextAreaInput(
name="Content",
placeholder="Enter document content or upload a file",
rows=10,
width=600
)
# File upload widget
file_upload = pn.widgets.FileInput(name="Upload File", accept=".docx,.pdf,.txt,.md", width=300)
# Buttons
create_btn = pn.widgets.Button(name="Create Document", button_type="success", width=150)
cancel_btn = pn.widgets.Button(name="Cancel", button_type="default", width=100)
# Create form layout
create_form = pn.Column(
pn.pane.Markdown("# Create New Document"),
title_input,
pn.Row(doc_type_select, department_select),
pn.Row(pn.Column(pn.pane.Markdown("**Upload File:**"), file_upload),
pn.Column(pn.pane.Markdown("**Or enter content directly:**"))),
content_area,
pn.Row(
cancel_btn,
create_btn,
align='end'
),
width=700,
styles={'background':'#f8f9fa'},
css_classes=['p-4', 'border', 'rounded']
)
# Define file upload handler
def handle_file_upload(event):
if file_upload.value is not None:
filename = file_upload.filename
# Extract file content based on type
if filename.endswith('.txt') or filename.endswith('.md'):
# Text files can be displayed directly
try:
content = file_upload.value.decode('utf-8')
content_area.value = content
except:
self.notification_area.object = "**Error:** Failed to read file as text"
else:
# For other file types, just note that the file will be stored
content_area.value = f"[File content from: {filename}]"
# Add file upload handler
file_upload.param.watch(handle_file_upload, 'value')
# Define button handlers
def handle_create(event):
logger.debug("Create button clicked")
# Validate form
if not title_input.value:
self.notification_area.object = "**Error:** Document title is required"
return
if not doc_type_select.value:
self.notification_area.object = "**Error:** Document type is required"
return
# Create document
try:
from CDocs.controllers.document_controller import create_document
# Extract content from file or text area
content = content_area.value or ""
if file_upload.value and not content_area.value:
try:
# Try to extract text if it's a text file
content = file_upload.value.decode('utf-8')
except:
# For binary files, save the reference
content = f"[File content from: {file_upload.filename}]"
# Convert full names to codes for storage in Neo4j
# Get the document type code from the selected full name
doc_type_full_name = doc_type_select.value
doc_type_code = settings.get_document_type_code(doc_type_full_name)
# Get the department code from the selected full name
dept_full_name = department_select.value
dept_code = settings.get_department_code(dept_full_name)
logger.info(f"Creating document with type: {doc_type_full_name} (code: {doc_type_code})")
logger.info(f"Department: {dept_full_name} (code: {dept_code})")
result = create_document(
user=self.user,
title=title_input.value,
doc_text=content,
doc_type=doc_type_code, # Send the code, not the full name
department=dept_code, # Send the code, not the full name
status='DRAFT'
)
# Show success message if result is valid
if result and isinstance(result, dict) and 'document' in result:
doc_number = result['document'].get('doc_number', 'Unknown')
self.notification_area.object = f"**Success:** Document {doc_number} created"
# Update document list
self.update_document_list()
else:
self.notification_area.object = "**Error:** Document creation failed - invalid result"
except Exception as e:
logger.error(f"Error creating document: {e}")
import traceback
logger.error(traceback.format_exc())
self.notification_area.object = f"**Error creating document:** {str(e)}"
def handle_cancel(event):
# Reset form and return to document list
self.notification_area.object = ""
self.update_document_list()
# Bind handlers
create_btn.on_click(handle_create)
cancel_btn.on_click(handle_cancel)
# Add form to document list container
self.document_list_container.append(create_form)
except Exception as e:
logger.error(f"Error showing create document form: {e}")
import traceback
logger.error(traceback.format_exc())
self.notification_area.object = f"**Error showing create document form:** {str(e)}"
# Return to document list
self.update_document_list()
def _apply_filters(self, event=None):
"""Apply the selected filters to the document list."""
try:
# Since our filters now have full names but the backend expects codes,
# we need to convert them before updating the document list
from CDocs.config import settings
# Store original values
orig_doc_type = self.doc_type_select.value
orig_department = self.department_select.value
orig_status = self.status_select.value
# Convert to codes for the backend query if values are not empty
if orig_doc_type:
self.doc_type_select.value = settings.get_document_type_code(orig_doc_type)
if orig_department:
self.department_select.value = settings.get_department_code(orig_department)
if orig_status:
self.status_select.value = settings.get_document_status_code(orig_status)
# Update the document list with the codes
self.update_document_list()
# Restore original display values for the UI
self.doc_type_select.value = orig_doc_type
self.department_select.value = orig_department
self.status_select.value = orig_status
except Exception as e:
logger.error(f"Error applying filters: {e}")
self.notification_area.object = f"Error applying filters: {str(e)}"
def _clear_filters(self, event=None):
"""Clear all filters and reset the document list."""
try:
# Reset filter values
self.search_input.value = ""
self.doc_type_select.value = ""
self.status_select.value = ""
self.department_select.value = ""
if hasattr(self, 'date_from_picker'):
self.date_from_picker.value = None
if hasattr(self, 'date_to_picker'):
self.date_to_picker.value = None
# Update document list with no filters
self.update_document_list()
except Exception as e:
logger.error(f"Error clearing filters: {e}")
self.notification_area.object = f"Error clearing filters: {str(e)}"
def setup_view_buttons(self):
"""Set up HTML click handling for action buttons in the document table."""
try:
# Check if we have a table to work with
if not self.document_list or not isinstance(self.document_list, pn.widgets.Tabulator):
logger.warning("Document list not available or not a Tabulator")
return
# Instead of using complex JavaScript callbacks, we'll use URL-based navigation
# by modifying the action column to include complete HTML links
if hasattr(self.document_list, 'value') and isinstance(self.document_list.value, pd.DataFrame):
df = self.document_list.value.copy()
# Modify the 'action' column to use standard HTML links
if 'action' in df.columns and 'UID' in df.columns:
new_actions = []
for i, row in df.iterrows():
doc_uid = row['UID']
status_code = row.get('status_code', '') # Get status code if available
# Create view button with direct link
actions = f'<a href="#document/{doc_uid}" class="btn btn-sm btn-primary">View</a>'
# Get workflow UIDs if they exist in the data
review_uid = row.get('review_uid', '')
if review_uid and status_code == 'IN_REVIEW':
actions += f' <a href="#review/{review_uid}" class="btn btn-sm btn-info">Review</a>'
approval_uid = row.get('approval_uid', '')
if approval_uid and status_code == 'IN_APPROVAL':
actions += f' <a href="#approval/{approval_uid}" class="btn btn-sm btn-warning">Approve</a>'
new_actions.append(actions)
df['action'] = new_actions
self.document_list.value = df
# Set up a catch-all JavaScript listener at the document level to handle hash changes
js_code = """
// Initialize hash change handler
window.onhashchange = function() {
var hash = window.location.hash;
// Parse the hash to get the action and UID
var parts = hash.substring(1).split('/');
if (parts.length !== 2) return;
var action = parts[0];
var uid = parts[1];
// Based on the action, navigate appropriately
if (action === 'document') {
// Navigate to document view
console.log('Loading document: ' + uid);
window.parent.postMessage({type: 'load_document', uid: uid}, '*');
} else if (action === 'review') {
// Navigate to review view
console.log('Loading review: ' + uid);
window.parent.postMessage({type: 'load_review', uid: uid}, '*');
} else if (action === 'approval') {
// Navigate to approval view
console.log('Loading approval: ' + uid);
window.parent.postMessage({type: 'load_approval', uid: uid}, '*');
}
};
"""
# Add the JavaScript code to the page once
pn.config.js_files['action_handler'] = {'raw': js_code}
logger.debug("Set up HTML-based action buttons")
except Exception as e:
logger.error(f"Error setting up view buttons: {e}")
logger.error(traceback.format_exc())
def _get_field_case_insensitive(self, doc_dict, field_names):
"""
Get a field value from a document dictionary using case-insensitive matching
and multiple possible field names.
Parameters
----------
doc_dict : dict
Document dictionary
field_names : list
List of possible field names
Returns
-------
str
Field value if found, empty string otherwise
"""
if not doc_dict or not field_names:
return ''
# Convert dictionary keys to lowercase for case-insensitive comparison
lower_dict = {k.lower(): v for k, v in doc_dict.items()}
# Try each field name in order
for name in field_names:
if name.lower() in lower_dict and lower_dict[name.lower()]:
return str(lower_dict[name.lower()])
# If no match found, try direct access as fallback
for name in field_names:
if name in doc_dict and doc_dict[name]:
return str(doc_dict[name])
# Return empty string if no match found
return ''
def setup_event_handlers(self):
"""Set up event handlers for the current document list."""
try:
# Register document selection handler for the current document list
if hasattr(self, 'document_list') and self.document_list:
# To avoid multiple event bindings, we use a special handler
# that checks if this particular table instance already has our handler
table_id = id(self.document_list)
# Track if this specific table instance has been set up
if hasattr(self, '_event_handler_table_id') and self._event_handler_table_id == table_id:
logger.debug(f"Event handlers already registered for table {table_id}")
return
# Store the ID of the table we're setting up
self._event_handler_table_id = table_id
# Register click event handler
self.document_list.on_click(self.document_selected)
logger.debug(f"Registered document_selected handler for table {table_id}")
except Exception as e:
logger.error(f"Error setting up event handlers: {e}")
import traceback
logger.error(traceback.format_exc())
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
param.Parameterized | - |
Parameter Details
bases: Parameter of type param.Parameterized
Return Value
Returns unspecified type
Class Interface
Methods
__init__(self, parent_app)
Purpose: Internal method: init
Parameters:
parent_app: Parameter
Returns: None
set_user(self, user)
Purpose: Set the current user from parent application
Parameters:
user: Parameter
Returns: None
_update_ui_for_user(self)
Purpose: Update UI components based on current user permissions
Returns: None
_setup_sidebar(self)
Purpose: Set up the sidebar filters and actions.
Returns: None
get_dashboard_view(self)
Purpose: Get the dashboard view with document list and actions.
Returns: None
_create_document_list(self)
Purpose: Create the document list component.
Returns: None
update_document_list(self, event)
Purpose: Update the document list with current filters.
Parameters:
event: Parameter
Returns: None
_show_sample_documents(self)
Purpose: Show sample documents when database access fails.
Returns: None
_format_documents_as_markdown(self, documents)
Purpose: Format documents as markdown table for fallback display.
Parameters:
documents: Parameter
Returns: None
document_selected(self, event)
Purpose: Handle document selection from table
Parameters:
event: Parameter
Returns: None
show_create_document_form(self, event)
Purpose: Show the create document form
Parameters:
event: Parameter
Returns: None
_apply_filters(self, event)
Purpose: Apply the selected filters to the document list.
Parameters:
event: Parameter
Returns: None
_clear_filters(self, event)
Purpose: Clear all filters and reset the document list.
Parameters:
event: Parameter
Returns: None
setup_view_buttons(self)
Purpose: Set up HTML click handling for action buttons in the document table.
Returns: None
_get_field_case_insensitive(self, doc_dict, field_names)
Purpose: Get a field value from a document dictionary using case-insensitive matching and multiple possible field names. Parameters ---------- doc_dict : dict Document dictionary field_names : list List of possible field names Returns ------- str Field value if found, empty string otherwise
Parameters:
doc_dict: Parameterfield_names: Parameter
Returns: See docstring for return details
setup_event_handlers(self)
Purpose: Set up event handlers for the current document list.
Returns: None
Required Imports
import logging
from typing import Dict
from typing import Any
from typing import List
from typing import Optional
Usage Example
# Example usage:
# result = DocumentDashboard(bases)
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
class DocumentDashboard 97.8% similar
-
class ControlledDocument_v1 71.1% similar
-
class ControlledDocument_v1 69.0% similar
-
class ControlledDocument 64.7% similar
-
class ControlledDocApp 64.3% similar