HH/apps/dashboard/views.py

1015 lines
38 KiB
Python

"""
Dashboard views - PX Command Center and analytics dashboards
"""
import json
from datetime import timedelta, datetime
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db.models import Avg, Count, Q, Sum
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from django.views.generic import TemplateView
from django.utils.translation import gettext_lazy as _
class CommandCenterView(LoginRequiredMixin, TemplateView):
"""
PX Command Center Dashboard - Real-time control panel.
Shows:
- Top KPI cards (complaints, actions, surveys, etc.)
- Charts (trends, satisfaction, leaderboards)
- Live feed (latest complaints, actions, events)
- Filters (date range, hospital, department)
"""
template_name = 'dashboard/command_center.html'
def dispatch(self, request, *args, **kwargs):
"""Check PX Admin has selected a hospital before processing request"""
# Only check hospital selection for authenticated users
if request.user.is_authenticated and request.user.is_px_admin() and not request.tenant_hospital:
return redirect('core:select_hospital')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
# Import models
from apps.complaints.models import Complaint
from apps.px_action_center.models import PXAction
from apps.surveys.models import SurveyInstance
from apps.social.models import SocialMediaComment
from apps.callcenter.models import CallCenterInteraction
from apps.integrations.models import InboundEvent
from apps.physicians.models import PhysicianMonthlyRating
from apps.organizations.models import Staff
# Date filters
now = timezone.now()
last_24h = now - timedelta(hours=24)
last_7d = now - timedelta(days=7)
last_30d = now - timedelta(days=30)
# Base querysets (filtered by user role and tenant_hospital)
if user.is_px_admin():
# PX Admins use their selected hospital from session
hospital = self.request.tenant_hospital
complaints_qs = Complaint.objects.filter(hospital=hospital) if hospital else Complaint.objects.none()
actions_qs = PXAction.objects.filter(hospital=hospital) if hospital else PXAction.objects.none()
surveys_qs = SurveyInstance.objects.all() # Surveys can be viewed across hospitals
social_qs = SocialMediaComment.objects.all() # Social media is organization-wide, not hospital-specific
calls_qs = CallCenterInteraction.objects.filter(hospital=hospital) if hospital else CallCenterInteraction.objects.none()
elif user.is_hospital_admin() and user.hospital:
complaints_qs = Complaint.objects.filter(hospital=user.hospital)
actions_qs = PXAction.objects.filter(hospital=user.hospital)
surveys_qs = SurveyInstance.objects.filter(survey_template__hospital=user.hospital)
social_qs = SocialMediaComment.objects.all() # Social media is organization-wide, not hospital-specific
calls_qs = CallCenterInteraction.objects.filter(hospital=user.hospital)
elif user.is_department_manager() and user.department:
complaints_qs = Complaint.objects.filter(department=user.department)
actions_qs = PXAction.objects.filter(department=user.department)
surveys_qs = SurveyInstance.objects.filter(journey_stage_instance__department=user.department)
social_qs = SocialMediaComment.objects.all() # Social media is organization-wide, not department-specific
calls_qs = CallCenterInteraction.objects.filter(department=user.department)
else:
complaints_qs = Complaint.objects.none()
actions_qs = PXAction.objects.none()
surveys_qs = SurveyInstance.objects.none()
social_qs = SocialMediaComment.objects.all() # Show all social media comments
calls_qs = CallCenterInteraction.objects.none()
# Top KPI Stats
context['stats'] = [
{
'label': _("Active Complaints"),
'value': complaints_qs.filter(status__in=['open', 'in_progress']).count(),
'icon': 'exclamation-triangle',
'color': 'danger'
},
{
'label': _('Overdue Complaints'),
'value': complaints_qs.filter(is_overdue=True, status__in=['open', 'in_progress']).count(),
'icon': 'clock-history',
'color': 'warning'
},
{
'label': _('Open PX Actions'),
'value': actions_qs.filter(status__in=['open', 'in_progress']).count(),
'icon': 'clipboard-check',
'color': 'primary'
},
{
'label': _('Overdue Actions'),
'value': actions_qs.filter(is_overdue=True, status__in=['open', 'in_progress']).count(),
'icon': 'alarm',
'color': 'danger'
},
{
'label': _('Negative Surveys (24h)'),
'value': surveys_qs.filter(is_negative=True, completed_at__gte=last_24h).count(),
'icon': 'emoji-frown',
'color': 'warning'
},
{
'label': _('Negative Social Mentions'),
'value': sum(
1 for comment in social_qs.filter(published_at__gte=last_7d)
if comment.ai_analysis and
comment.ai_analysis.get('sentiment', {}).get('classification', {}).get('en') == 'negative'
),
'icon': 'chat-dots',
'color': 'danger'
},
{
'label': _('Low Call Center Ratings'),
'value': calls_qs.filter(is_low_rating=True, call_started_at__gte=last_7d).count(),
'icon': 'telephone',
'color': 'warning'
},
{
'label': _('Avg Survey Score'),
'value': f"{surveys_qs.filter(completed_at__gte=last_30d).aggregate(Avg('total_score'))['total_score__avg'] or 0:.1f}",
'icon': 'star',
'color': 'success'
},
]
# Latest high severity complaints
context['latest_complaints'] = complaints_qs.filter(
severity__in=['high', 'critical']
).select_related('patient', 'hospital', 'department').order_by('-created_at')[:5]
# Latest escalated actions
context['latest_actions'] = actions_qs.filter(
escalation_level__gt=0
).select_related('hospital', 'assigned_to').order_by('-escalated_at')[:5]
# Latest integration events
context['latest_events'] = InboundEvent.objects.filter(
status='processed'
).select_related().order_by('-processed_at')[:10]
# Staff ratings data
current_month_ratings = PhysicianMonthlyRating.objects.filter(
year=now.year,
month=now.month
).select_related('staff', 'staff__hospital', 'staff__department')
# Filter by user role
if user.is_hospital_admin() and user.hospital:
current_month_ratings = current_month_ratings.filter(staff__hospital=user.hospital)
elif user.is_department_manager() and user.department:
current_month_ratings = current_month_ratings.filter(staff__department=user.department)
# Top 5 staff this month
context['top_physicians'] = current_month_ratings.order_by('-average_rating')[:5]
# Staff stats
physician_stats = current_month_ratings.aggregate(
total_physicians=Count('id'),
avg_rating=Avg('average_rating'),
total_surveys=Count('total_surveys')
)
context['physician_stats'] = physician_stats
# Chart data (simplified for now)
import json
context['chart_data'] = {
'complaints_trend': json.dumps(self.get_complaints_trend(complaints_qs, last_30d)),
'survey_satisfaction': self.get_survey_satisfaction(surveys_qs, last_30d),
}
# Add hospital context
context['current_hospital'] = self.request.tenant_hospital
context['is_px_admin'] = user.is_px_admin()
return context
def get_complaints_trend(self, queryset, start_date):
"""Get complaints trend data for chart"""
# Group by day for last 30 days
data = []
for i in range(30):
date = start_date + timedelta(days=i)
count = queryset.filter(
created_at__date=date.date()
).count()
data.append({
'date': date.strftime('%Y-%m-%d'),
'count': count
})
return data
def get_survey_satisfaction(self, queryset, start_date):
"""Get survey satisfaction averages"""
return queryset.filter(
completed_at__gte=start_date,
total_score__isnull=False
).aggregate(Avg('total_score'))['total_score__avg'] or 0
@login_required
def my_dashboard(request):
"""
My Dashboard - Personal view of all assigned items.
Shows:
- Summary cards with statistics
- Tabbed interface for 6 model types:
* Complaints
* Inquiries
* Observations
* PX Actions
* Tasks (QI Project Tasks)
* Feedback
- Date range filtering
- Search and filter controls
- Export functionality (CSV/Excel)
- Bulk actions support
- Charts showing trends
"""
user = request.user
# Get date range filter
date_range_days = int(request.GET.get('date_range', 30))
if date_range_days == -1: # All time
start_date = None
else:
start_date = timezone.now() - timedelta(days=date_range_days)
# Get active tab
active_tab = request.GET.get('tab', 'complaints')
# Get search query
search_query = request.GET.get('search', '')
# Get status filter
status_filter = request.GET.get('status', '')
# Get priority/severity filter
priority_filter = request.GET.get('priority', '')
# Build querysets for all models
querysets = {}
# 1. Complaints
from apps.complaints.models import Complaint
complaints_qs = Complaint.objects.filter(assigned_to=user)
if start_date:
complaints_qs = complaints_qs.filter(created_at__gte=start_date)
if search_query:
complaints_qs = complaints_qs.filter(
Q(title__icontains=search_query) |
Q(description__icontains=search_query)
)
if status_filter:
complaints_qs = complaints_qs.filter(status=status_filter)
if priority_filter:
complaints_qs = complaints_qs.filter(severity=priority_filter)
querysets['complaints'] = complaints_qs.select_related(
'patient', 'hospital', 'department', 'source', 'created_by'
).order_by('-created_at')
# 2. Inquiries
from apps.complaints.models import Inquiry
inquiries_qs = Inquiry.objects.filter(assigned_to=user)
if start_date:
inquiries_qs = inquiries_qs.filter(created_at__gte=start_date)
if search_query:
inquiries_qs = inquiries_qs.filter(
Q(subject__icontains=search_query) |
Q(message__icontains=search_query)
)
if status_filter:
inquiries_qs = inquiries_qs.filter(status=status_filter)
querysets['inquiries'] = inquiries_qs.select_related(
'patient', 'hospital', 'department'
).order_by('-created_at')
# 3. Observations
from apps.observations.models import Observation
observations_qs = Observation.objects.filter(assigned_to=user)
if start_date:
observations_qs = observations_qs.filter(created_at__gte=start_date)
if search_query:
observations_qs = observations_qs.filter(
Q(title__icontains=search_query) |
Q(description__icontains=search_query)
)
if status_filter:
observations_qs = observations_qs.filter(status=status_filter)
if priority_filter:
observations_qs = observations_qs.filter(severity=priority_filter)
querysets['observations'] = observations_qs.select_related(
'hospital', 'assigned_department'
).order_by('-created_at')
# 4. PX Actions
from apps.px_action_center.models import PXAction
actions_qs = PXAction.objects.filter(assigned_to=user)
if start_date:
actions_qs = actions_qs.filter(created_at__gte=start_date)
if search_query:
actions_qs = actions_qs.filter(
Q(title__icontains=search_query) |
Q(description__icontains=search_query)
)
if status_filter:
actions_qs = actions_qs.filter(status=status_filter)
if priority_filter:
actions_qs = actions_qs.filter(severity=priority_filter)
querysets['actions'] = actions_qs.select_related(
'hospital', 'department', 'approved_by'
).order_by('-created_at')
# 5. QI Project Tasks
from apps.projects.models import QIProjectTask
tasks_qs = QIProjectTask.objects.filter(assigned_to=user)
if start_date:
tasks_qs = tasks_qs.filter(created_at__gte=start_date)
if search_query:
tasks_qs = tasks_qs.filter(
Q(title__icontains=search_query) |
Q(description__icontains=search_query)
)
if status_filter:
tasks_qs = tasks_qs.filter(status=status_filter)
querysets['tasks'] = tasks_qs.select_related('project').order_by('-created_at')
# 6. Feedback
from apps.feedback.models import Feedback
feedback_qs = Feedback.objects.filter(assigned_to=user)
if start_date:
feedback_qs = feedback_qs.filter(created_at__gte=start_date)
if search_query:
feedback_qs = feedback_qs.filter(
Q(title__icontains=search_query) |
Q(message__icontains=search_query)
)
if status_filter:
feedback_qs = feedback_qs.filter(status=status_filter)
if priority_filter:
feedback_qs = feedback_qs.filter(priority=priority_filter)
querysets['feedback'] = feedback_qs.select_related(
'hospital', 'department', 'patient'
).order_by('-created_at')
# Calculate statistics
stats = {}
total_stats = {
'total': 0,
'open': 0,
'in_progress': 0,
'resolved': 0,
'closed': 0,
'overdue': 0
}
# Complaints stats
complaints_open = querysets['complaints'].filter(status='open').count()
complaints_in_progress = querysets['complaints'].filter(status='in_progress').count()
complaints_resolved = querysets['complaints'].filter(status='resolved').count()
complaints_closed = querysets['complaints'].filter(status='closed').count()
complaints_overdue = querysets['complaints'].filter(is_overdue=True).count()
stats['complaints'] = {
'total': querysets['complaints'].count(),
'open': complaints_open,
'in_progress': complaints_in_progress,
'resolved': complaints_resolved,
'closed': complaints_closed,
'overdue': complaints_overdue
}
total_stats['total'] += stats['complaints']['total']
total_stats['open'] += complaints_open
total_stats['in_progress'] += complaints_in_progress
total_stats['resolved'] += complaints_resolved
total_stats['closed'] += complaints_closed
total_stats['overdue'] += complaints_overdue
# Inquiries stats
inquiries_open = querysets['inquiries'].filter(status='open').count()
inquiries_in_progress = querysets['inquiries'].filter(status='in_progress').count()
inquiries_resolved = querysets['inquiries'].filter(status='resolved').count()
inquiries_closed = querysets['inquiries'].filter(status='closed').count()
stats['inquiries'] = {
'total': querysets['inquiries'].count(),
'open': inquiries_open,
'in_progress': inquiries_in_progress,
'resolved': inquiries_resolved,
'closed': inquiries_closed,
'overdue': 0
}
total_stats['total'] += stats['inquiries']['total']
total_stats['open'] += inquiries_open
total_stats['in_progress'] += inquiries_in_progress
total_stats['resolved'] += inquiries_resolved
total_stats['closed'] += inquiries_closed
# Observations stats
observations_open = querysets['observations'].filter(status='open').count()
observations_in_progress = querysets['observations'].filter(status='in_progress').count()
observations_closed = querysets['observations'].filter(status='closed').count()
# Observations don't have is_overdue field - set to 0
observations_overdue = 0
stats['observations'] = {
'total': querysets['observations'].count(),
'open': observations_open,
'in_progress': observations_in_progress,
'resolved': 0,
'closed': observations_closed,
'overdue': observations_overdue
}
total_stats['total'] += stats['observations']['total']
total_stats['open'] += observations_open
total_stats['in_progress'] += observations_in_progress
total_stats['closed'] += observations_closed
total_stats['overdue'] += observations_overdue
# PX Actions stats
actions_open = querysets['actions'].filter(status='open').count()
actions_in_progress = querysets['actions'].filter(status='in_progress').count()
actions_closed = querysets['actions'].filter(status='closed').count()
actions_overdue = querysets['actions'].filter(is_overdue=True).count()
stats['actions'] = {
'total': querysets['actions'].count(),
'open': actions_open,
'in_progress': actions_in_progress,
'resolved': 0,
'closed': actions_closed,
'overdue': actions_overdue
}
total_stats['total'] += stats['actions']['total']
total_stats['open'] += actions_open
total_stats['in_progress'] += actions_in_progress
total_stats['closed'] += actions_closed
total_stats['overdue'] += actions_overdue
# Tasks stats
tasks_open = querysets['tasks'].filter(status='open').count()
tasks_in_progress = querysets['tasks'].filter(status='in_progress').count()
tasks_closed = querysets['tasks'].filter(status='closed').count()
stats['tasks'] = {
'total': querysets['tasks'].count(),
'open': tasks_open,
'in_progress': tasks_in_progress,
'resolved': 0,
'closed': tasks_closed,
'overdue': 0
}
total_stats['total'] += stats['tasks']['total']
total_stats['open'] += tasks_open
total_stats['in_progress'] += tasks_in_progress
total_stats['closed'] += tasks_closed
# Feedback stats
feedback_open = querysets['feedback'].filter(status='submitted').count()
feedback_in_progress = querysets['feedback'].filter(status='reviewed').count()
feedback_acknowledged = querysets['feedback'].filter(status='acknowledged').count()
feedback_closed = querysets['feedback'].filter(status='closed').count()
stats['feedback'] = {
'total': querysets['feedback'].count(),
'open': feedback_open,
'in_progress': feedback_in_progress,
'resolved': feedback_acknowledged,
'closed': feedback_closed,
'overdue': 0
}
total_stats['total'] += stats['feedback']['total']
total_stats['open'] += feedback_open
total_stats['in_progress'] += feedback_in_progress
total_stats['resolved'] += feedback_acknowledged
total_stats['closed'] += feedback_closed
# Paginate all querysets
page_size = int(request.GET.get('page_size', 25))
paginated_data = {}
for tab_name, qs in querysets.items():
paginator = Paginator(qs, page_size)
page_number = request.GET.get(f'page_{tab_name}', 1)
paginated_data[tab_name] = paginator.get_page(page_number)
# Get chart data
chart_data = get_dashboard_chart_data(user, start_date)
context = {
'stats': stats,
'total_stats': total_stats,
'paginated_data': paginated_data,
'active_tab': active_tab,
'date_range': date_range_days,
'search_query': search_query,
'status_filter': status_filter,
'priority_filter': priority_filter,
'chart_data': chart_data,
}
return render(request, 'dashboard/my_dashboard.html', context)
def get_dashboard_chart_data(user, start_date=None):
"""
Get chart data for dashboard trends.
Returns JSON-serializable data for ApexCharts.
"""
from apps.complaints.models import Complaint
from apps.px_action_center.models import PXAction
from apps.observations.models import Observation
from apps.feedback.models import Feedback
from apps.complaints.models import Inquiry
from apps.projects.models import QIProjectTask
# Default to last 30 days if no start_date
if not start_date:
start_date = timezone.now() - timedelta(days=30)
# Get completion trends
completion_data = []
labels = []
# Group by day for last 30 days
for i in range(30):
date = start_date + timedelta(days=i)
date_str = date.strftime('%Y-%m-%d')
labels.append(date.strftime('%b %d'))
completed_count = 0
# Check each model for completions on this date
completed_count += Complaint.objects.filter(
assigned_to=user,
status='closed',
closed_at__date=date.date()
).count()
completed_count += Inquiry.objects.filter(
assigned_to=user,
status='closed',
updated_at__date=date.date()
).count()
completed_count += Observation.objects.filter(
assigned_to=user,
status='closed',
updated_at__date=date.date()
).count()
completed_count += PXAction.objects.filter(
assigned_to=user,
status='closed',
closed_at__date=date.date()
).count()
completed_count += QIProjectTask.objects.filter(
assigned_to=user,
status='closed',
completed_date=date.date()
).count()
completed_count += Feedback.objects.filter(
assigned_to=user,
status='closed',
closed_at__date=date.date()
).count()
completion_data.append(completed_count)
return {
'completion_trend': {
'labels': labels,
'data': completion_data
}
}
@login_required
def dashboard_bulk_action(request):
"""
Handle bulk actions on dashboard items.
Supported actions:
- bulk_assign: Assign to user
- bulk_status: Change status
"""
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'POST required'}, status=405)
import json
try:
data = json.loads(request.body)
action = data.get('action')
tab_name = data.get('tab')
item_ids = data.get('item_ids', [])
if not action or not tab_name:
return JsonResponse({'success': False, 'error': 'Missing required fields'}, status=400)
# Route to appropriate handler based on tab
if tab_name == 'complaints':
from apps.complaints.models import Complaint
queryset = Complaint.objects.filter(id__in=item_ids, assigned_to=request.user)
elif tab_name == 'inquiries':
from apps.complaints.models import Inquiry
queryset = Inquiry.objects.filter(id__in=item_ids, assigned_to=request.user)
elif tab_name == 'observations':
from apps.observations.models import Observation
queryset = Observation.objects.filter(id__in=item_ids, assigned_to=request.user)
elif tab_name == 'actions':
from apps.px_action_center.models import PXAction
queryset = PXAction.objects.filter(id__in=item_ids, assigned_to=request.user)
elif tab_name == 'tasks':
from apps.projects.models import QIProjectTask
queryset = QIProjectTask.objects.filter(id__in=item_ids, assigned_to=request.user)
elif tab_name == 'feedback':
from apps.feedback.models import Feedback
queryset = Feedback.objects.filter(id__in=item_ids, assigned_to=request.user)
else:
return JsonResponse({'success': False, 'error': 'Invalid tab'}, status=400)
# Apply bulk action
if action == 'bulk_status':
new_status = data.get('new_status')
if not new_status:
return JsonResponse({'success': False, 'error': 'Missing new_status'}, status=400)
count = queryset.update(status=new_status)
return JsonResponse({'success': True, 'updated_count': count})
elif action == 'bulk_assign':
user_id = data.get('user_id')
if not user_id:
return JsonResponse({'success': False, 'error': 'Missing user_id'}, status=400)
from apps.accounts.models import User
assignee = User.objects.get(id=user_id)
count = queryset.update(assigned_to=assignee, assigned_at=timezone.now())
return JsonResponse({'success': True, 'updated_count': count})
else:
return JsonResponse({'success': False, 'error': 'Invalid action'}, status=400)
except json.JSONDecodeError:
return JsonResponse({'success': False, 'error': 'Invalid JSON'}, status=400)
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=500)
@login_required
def admin_evaluation(request):
"""
Admin Evaluation Dashboard - Staff performance analysis.
Shows:
- Performance metrics for all staff members
- Complaints: Source breakdown, status distribution, response time, activation time
- Inquiries: Status distribution, response time
- Multi-staff comparison
- Date range filtering
- Hospital/department filtering
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
from apps.accounts.models import User
from apps.organizations.models import Hospital, Department
user = request.user
# Get date range filter
date_range = request.GET.get('date_range', '30d')
custom_start = request.GET.get('custom_start')
custom_end = request.GET.get('custom_end')
# Parse custom dates if provided
if custom_start:
from datetime import datetime
custom_start = datetime.fromisoformat(custom_start)
if custom_end:
from datetime import datetime
custom_end = datetime.fromisoformat(custom_end)
# Get hospital and department filters
hospital_id = request.GET.get('hospital_id')
department_id = request.GET.get('department_id')
# Get selected staff IDs for comparison
selected_staff_ids = request.GET.getlist('staff_ids')
# Get available hospitals (for PX Admins)
if user.is_px_admin():
hospitals = Hospital.objects.filter(status='active')
elif user.is_hospital_admin() and user.hospital:
hospitals = Hospital.objects.filter(id=user.hospital.id)
hospital_id = hospital_id or user.hospital.id # Default to user's hospital
else:
hospitals = Hospital.objects.none()
# Get available departments based on hospital filter
if hospital_id:
departments = Department.objects.filter(hospital_id=hospital_id, status='active')
elif user.hospital:
departments = Department.objects.filter(hospital=user.hospital, status='active')
else:
departments = Department.objects.none()
# Get staff performance metrics
performance_data = UnifiedAnalyticsService.get_staff_performance_metrics(
user=user,
date_range=date_range,
hospital_id=hospital_id,
department_id=department_id,
staff_ids=selected_staff_ids if selected_staff_ids else None,
custom_start=custom_start,
custom_end=custom_end
)
# Get all staff for the dropdown
staff_queryset = User.objects.all()
if user.is_px_admin() and hospital_id:
staff_queryset = staff_queryset.filter(hospital_id=hospital_id)
elif not user.is_px_admin() and user.hospital:
staff_queryset = staff_queryset.filter(hospital=user.hospital)
hospital_id = hospital_id or user.hospital.id
if department_id:
staff_queryset = staff_queryset.filter(department_id=department_id)
# Only staff with assigned complaints or inquiries
staff_queryset = staff_queryset.filter(
Q(assigned_complaints__isnull=False) | Q(assigned_inquiries__isnull=False)
).distinct().select_related('hospital', 'department')
context = {
'hospitals': hospitals,
'departments': departments,
'staff_list': staff_queryset,
'selected_hospital_id': hospital_id,
'selected_department_id': department_id,
'selected_staff_ids': selected_staff_ids,
'date_range': date_range,
'custom_start': custom_start,
'custom_end': custom_end,
'performance_data': performance_data,
}
return render(request, 'dashboard/admin_evaluation.html', context)
@login_required
def admin_evaluation_chart_data(request):
"""
API endpoint to get chart data for admin evaluation dashboard.
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
if request.method != 'GET':
return JsonResponse({'success': False, 'error': 'GET required'}, status=405)
user = request.user
chart_type = request.GET.get('chart_type')
date_range = request.GET.get('date_range', '30d')
hospital_id = request.GET.get('hospital_id')
department_id = request.GET.get('department_id')
staff_ids = request.GET.getlist('staff_ids')
# Parse custom dates if provided
custom_start = request.GET.get('custom_start')
custom_end = request.GET.get('custom_end')
if custom_start:
from datetime import datetime
custom_start = datetime.fromisoformat(custom_start)
if custom_end:
from datetime import datetime
custom_end = datetime.fromisoformat(custom_end)
try:
if chart_type == 'staff_performance':
data = UnifiedAnalyticsService.get_staff_performance_metrics(
user=user,
date_range=date_range,
hospital_id=hospital_id,
department_id=department_id,
staff_ids=staff_ids if staff_ids else None,
custom_start=custom_start,
custom_end=custom_end
)
else:
data = {'error': f'Unknown chart type: {chart_type}'}
return JsonResponse({'success': True, 'data': data})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=500)
# ============================================================================
# ENHANCED ADMIN EVALUATION VIEWS
# ============================================================================
@login_required
def staff_performance_detail(request, staff_id):
"""
Detailed performance view for a single staff member.
Shows:
- Performance score with breakdown
- Daily workload trends
- Recent complaints and inquiries
- Performance metrics
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
from apps.accounts.models import User
user = request.user
# Get date range
date_range = request.GET.get('date_range', '30d')
try:
staff = User.objects.select_related('hospital', 'department').get(id=staff_id)
# Check permissions
if not user.is_px_admin():
if user.hospital and staff.hospital != user.hospital:
messages.error(request, "You don't have permission to view this staff member's performance.")
return redirect('dashboard:admin_evaluation')
# Get detailed performance
performance = UnifiedAnalyticsService.get_staff_detailed_performance(
staff_id=staff_id,
user=user,
date_range=date_range
)
# Get trends
trends = UnifiedAnalyticsService.get_staff_performance_trends(
staff_id=staff_id,
user=user,
months=6
)
context = {
'staff': performance['staff'],
'performance': performance,
'trends': trends,
'date_range': date_range
}
return render(request, 'dashboard/staff_performance_detail.html', context)
except User.DoesNotExist:
messages.error(request, "Staff member not found.")
return redirect('dashboard:admin_evaluation')
except PermissionError:
messages.error(request, "You don't have permission to view this staff member.")
return redirect('dashboard:admin_evaluation')
@login_required
def staff_performance_trends(request, staff_id):
"""
API endpoint to get staff performance trends as JSON.
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
from apps.accounts.models import User
user = request.user
months = int(request.GET.get('months', 6))
try:
trends = UnifiedAnalyticsService.get_staff_performance_trends(
staff_id=staff_id,
user=user,
months=months
)
return JsonResponse({'success': True, 'trends': trends})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=400)
@login_required
def department_benchmarks(request):
"""
Department benchmarking view comparing all staff.
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
user = request.user
# Get filters
department_id = request.GET.get('department_id')
date_range = request.GET.get('date_range', '30d')
# If user is department manager, use their department
if user.is_department_manager() and user.department and not department_id:
department_id = str(user.department.id)
try:
benchmarks = UnifiedAnalyticsService.get_department_benchmarks(
user=user,
department_id=department_id,
date_range=date_range
)
context = {
'benchmarks': benchmarks,
'date_range': date_range
}
return render(request, 'dashboard/department_benchmarks.html', context)
except Exception as e:
messages.error(request, f"Error loading benchmarks: {str(e)}")
return redirect('dashboard:admin_evaluation')
@login_required
def export_staff_performance(request):
"""
Export staff performance report in various formats.
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
import csv
import json
from django.http import HttpResponse
user = request.user
if request.method != 'POST':
return JsonResponse({'error': 'POST required'}, status=405)
try:
data = json.loads(request.body)
staff_ids = data.get('staff_ids', [])
date_range = data.get('date_range', '30d')
format_type = data.get('format', 'csv')
# Generate report
report = UnifiedAnalyticsService.export_staff_performance_report(
staff_ids=staff_ids,
user=user,
date_range=date_range,
format_type=format_type
)
if format_type == 'csv':
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="staff_performance_{timezone.now().strftime("%Y%m%d")}.csv"'
if report['data']:
writer = csv.DictWriter(response, fieldnames=report['data'][0].keys())
writer.writeheader()
writer.writerows(report['data'])
return response
elif format_type == 'json':
return JsonResponse(report)
else:
return JsonResponse({'error': f'Unsupported format: {format_type}'}, status=400)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
@login_required
def performance_analytics_api(request):
"""
API endpoint for various performance analytics.
"""
from apps.analytics.services.analytics_service import UnifiedAnalyticsService
user = request.user
chart_type = request.GET.get('chart_type')
try:
if chart_type == 'staff_trends':
staff_id = request.GET.get('staff_id')
months = int(request.GET.get('months', 6))
data = UnifiedAnalyticsService.get_staff_performance_trends(
staff_id=staff_id,
user=user,
months=months
)
elif chart_type == 'department_benchmarks':
department_id = request.GET.get('department_id')
date_range = request.GET.get('date_range', '30d')
data = UnifiedAnalyticsService.get_department_benchmarks(
user=user,
department_id=department_id,
date_range=date_range
)
else:
return JsonResponse({'error': f'Unknown chart type: {chart_type}'}, status=400)
return JsonResponse({'success': True, 'data': data})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)}, status=500)