Files
orion/app/modules/prospecting/templates/prospecting/admin/dashboard.html
Samir Boulahtit 2287f4597d
All checks were successful
CI / ruff (push) Successful in 10s
CI / pytest (push) Successful in 48m48s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Successful in 38s
CI / deploy (push) Successful in 51s
feat(hosting,prospecting): add hosting unit tests and fix template bugs
- Add 55 unit tests for hosting module (hosted site service, client
  service service, stats service) with full fixture setup
- Fix table_empty_state macro: add x_message param for dynamic Alpine.js
  expressions rendered via x-text instead of server-side Jinja
- Fix hosting templates (sites, clients) using message= with Alpine
  expressions that rendered as literal text
- Fix prospecting templates (leads, scan-jobs, prospects) using
  nonexistent subtitle= param, migrated to x_message=
- Align hosting and prospecting admin templates with shared design system

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:18:26 +01:00

132 lines
7.3 KiB
HTML

{% extends "admin/base.html" %}
{% from 'shared/macros/headers.html' import page_header, section_header %}
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
{% block title %}Prospecting Dashboard{% endblock %}
{% block alpine_data %}prospectingDashboard(){% endblock %}
{% block content %}
{{ page_header('Prospecting Dashboard') }}
{{ loading_state('Loading dashboard...') }}
{{ error_state('Error loading dashboard') }}
<div x-show="!loading && !error" class="space-y-6">
<!-- KPI Cards -->
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
<span x-html="$icon('globe', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Prospects</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total_prospects || 0"></p>
</div>
</div>
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
<span x-html="$icon('cursor-click', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Top Priority</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.top_priority || 0"></p>
</div>
</div>
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
<span x-html="$icon('chart-bar', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Avg Score</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.avg_score ? stats.avg_score.toFixed(1) : '—'"></p>
</div>
</div>
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
<span x-html="$icon('phone', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Offline Leads</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.offline_count || 0"></p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
{{ section_header('Quick Actions', icon='cursor-click') }}
<div class="flex flex-wrap gap-3">
<a href="/admin/prospecting/capture"
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
<span x-html="$icon('phone', 'w-4 h-4 mr-2')"></span>
Quick Capture
</a>
<button type="button" @click="runBatchScan()"
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-blue-600 border border-transparent rounded-lg hover:bg-blue-700 focus:outline-none">
<span x-html="$icon('globe-alt', 'w-4 h-4 mr-2')"></span>
Run Batch Scan
</button>
<button type="button" @click="showImportModal = true"
class="inline-flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-green-600 border border-transparent rounded-lg hover:bg-green-700 focus:outline-none">
<span x-html="$icon('upload', 'w-4 h-4 mr-2')"></span>
Import Domains
</button>
</div>
</div>
<!-- Leads by Tier + By Channel -->
<div class="grid gap-6 md:grid-cols-2">
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
{{ section_header('Leads by Tier', icon='chart-bar') }}
<div class="space-y-3">
<template x-for="tier in ['top_priority', 'quick_win', 'strategic', 'low_priority']" :key="tier">
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400 capitalize" x-text="tier.replace('_', ' ')"></span>
<span class="px-2.5 py-0.5 text-xs font-medium rounded-full"
:class="tierBadgeClass(tier)"
x-text="stats.leads_by_tier?.[tier] || 0"></span>
</div>
</template>
</div>
</div>
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
{{ section_header('By Channel', icon='globe') }}
<div class="space-y-3">
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Digital (Domain Scan)</span>
<span class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="stats.digital_count || 0"></span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Offline (Captured)</span>
<span class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="stats.offline_count || 0"></span>
</div>
</div>
</div>
</div>
<!-- Common Issues -->
<div class="p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
{{ section_header('Common Issues', icon='exclamation') }}
<div class="space-y-3">
<template x-for="issue in stats.common_issues || []" :key="issue.flag">
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400" x-text="issueLabel(issue.flag)"></span>
<div class="flex items-center">
<div class="w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700 mr-3">
<div class="h-2 bg-red-500 rounded-full transition-all duration-300"
:style="'width: ' + (issue.count / (stats.total_prospects || 1) * 100) + '%'"></div>
</div>
<span class="text-xs font-medium text-gray-500 dark:text-gray-400 w-8 text-right" x-text="issue.count"></span>
</div>
</div>
</template>
<p x-show="!stats.common_issues?.length" class="text-sm text-gray-400 text-center py-4">No issues data available</p>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/dashboard.js') }}"></script>
{% endblock %}