Major additions: - Web UI: Jinja2 templates in FastAPI (login, dashboard, catalog, corporate memory, admin) - API: catalog profiles/metrics, telegram verify/unlink/status, admin table registry CRUD - Corporate memory governance: approve/reject/mandate/revoke/edit/batch + audit log - Sync: real DataSyncManager trigger, sync-settings, table-subscriptions - CLI: setup (init/test/deploy/verify), server (logs/restart/deploy/backup), explore - Instance config integration (instance.yaml loaded at startup) - 140 tests passing (25 new)
2571 lines
94 KiB
HTML
2571 lines
94 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Activity Center - Data Analyst Portal</title>
|
|
{% if not config.THEME_FONT_URL %}
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
{% endif %}
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style-custom.css') }}">
|
|
<style>
|
|
/* Activity Center Page Styles */
|
|
.container-activity {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: var(--space-6);
|
|
}
|
|
|
|
/* Top Header Bar (matches Data Catalog) */
|
|
.top-header {
|
|
background: var(--surface);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0 32px;
|
|
height: 72px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
}
|
|
|
|
.top-header-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
|
|
.header-back {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 6px;
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.header-back:hover {
|
|
background: var(--border-light);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.header-logo-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.header-logo svg {
|
|
display: block;
|
|
}
|
|
|
|
.header-subtitle {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
letter-spacing: 0.4px;
|
|
text-transform: uppercase;
|
|
margin-top: 2px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.top-header-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.demo-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-bold);
|
|
border-radius: var(--radius-full);
|
|
background: rgba(245, 159, 10, 0.15);
|
|
color: #b45309;
|
|
border: 1px solid rgba(245, 159, 10, 0.4);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
/* Executive Pulse Stats Bar */
|
|
.exec-stats-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-6);
|
|
padding: var(--space-4) var(--space-6);
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.exec-stat {
|
|
text-align: center;
|
|
flex: 1;
|
|
}
|
|
|
|
.exec-stat .value {
|
|
font-size: var(--text-lg);
|
|
font-weight: var(--font-bold);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.exec-stat .value .suffix {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.exec-stat .value.success {
|
|
color: var(--success);
|
|
}
|
|
|
|
.exec-stat .label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.exec-divider {
|
|
width: 1px;
|
|
height: 36px;
|
|
background: var(--border);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.exec-summary-sentence {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
text-align: center;
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
/* ========== Tab Navigation ========== */
|
|
.tab-nav {
|
|
display: flex;
|
|
gap: var(--space-1);
|
|
border-bottom: 2px solid var(--border);
|
|
margin-bottom: var(--space-6);
|
|
padding: 0;
|
|
}
|
|
|
|
.tab-btn {
|
|
padding: var(--space-3) var(--space-5);
|
|
font-size: var(--text-base);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-secondary);
|
|
background: none;
|
|
border: none;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tab-btn:hover {
|
|
color: var(--text-primary);
|
|
background: var(--background);
|
|
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
|
}
|
|
|
|
.tab-btn.active {
|
|
color: var(--primary);
|
|
border-bottom-color: var(--primary);
|
|
font-weight: var(--font-semibold);
|
|
}
|
|
|
|
.tab-btn .tab-count {
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-medium);
|
|
background: var(--border);
|
|
color: var(--text-secondary);
|
|
padding: 1px 7px;
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.tab-btn.active .tab-count {
|
|
background: var(--primary-light);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.tab-panel {
|
|
display: none;
|
|
}
|
|
|
|
.tab-panel.active {
|
|
display: block;
|
|
}
|
|
|
|
/* ========== Section titles ========== */
|
|
.section {
|
|
margin-bottom: var(--space-8);
|
|
}
|
|
|
|
.section-header {
|
|
margin-bottom: var(--space-5);
|
|
}
|
|
|
|
.section-header h2 {
|
|
font-size: var(--text-lg);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.section-header p {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ========== Business Process Map ========== */
|
|
.process-category {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
margin-bottom: var(--space-4);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.process-category-header {
|
|
padding: var(--space-4) var(--space-5);
|
|
background: var(--background);
|
|
border-bottom: 1px solid var(--border);
|
|
font-size: var(--text-md);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.process-category-count {
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-secondary);
|
|
background: var(--border);
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.process-row {
|
|
padding: var(--space-4) var(--space-5);
|
|
border-bottom: 1px solid var(--border-light);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.process-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.process-row:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.process-status-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: var(--radius-full);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.process-status-dot.optimized { background: var(--success); }
|
|
.process-status-dot.mature { background: var(--primary); }
|
|
.process-status-dot.developing { background: var(--warning); }
|
|
.process-status-dot.early {
|
|
background: var(--border);
|
|
border: 2px solid var(--text-secondary);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.process-name {
|
|
font-size: var(--text-base);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-primary);
|
|
flex: 1;
|
|
}
|
|
|
|
.maturity-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 8px;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-medium);
|
|
border-radius: var(--radius-full);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.maturity-badge.optimized { background: rgba(16, 183, 127, 0.1); color: var(--success); }
|
|
.maturity-badge.mature { background: var(--primary-light); color: var(--primary); }
|
|
.maturity-badge.developing { background: rgba(245, 159, 10, 0.1); color: #b45309; }
|
|
.maturity-badge.early { background: var(--background); color: var(--text-secondary); }
|
|
|
|
.process-queries {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.process-teams {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.process-expand-icon {
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.process-expand-icon.expanded {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.process-detail {
|
|
display: none;
|
|
padding: var(--space-4) var(--space-5) var(--space-4) calc(var(--space-5) + 22px);
|
|
background: var(--background);
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.process-detail.visible {
|
|
display: block;
|
|
}
|
|
|
|
.process-detail-section {
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.process-detail-section:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.process-detail-label {
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.process-detail-text {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.process-detail-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.process-detail-tag {
|
|
font-size: var(--text-xs);
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* ========== Team Maturity Leaderboard ========== */
|
|
.leaderboard-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
padding: var(--space-5);
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.leaderboard-card h3 {
|
|
font-size: var(--text-md);
|
|
font-weight: var(--font-semibold);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.maturity-distribution {
|
|
display: flex;
|
|
height: 8px;
|
|
border-radius: var(--radius-full);
|
|
overflow: hidden;
|
|
margin-bottom: var(--space-4);
|
|
background: var(--background);
|
|
}
|
|
|
|
.maturity-segment { height: 100%; transition: width 0.3s ease; }
|
|
.maturity-segment.optimized { background: var(--success); }
|
|
.maturity-segment.mature { background: var(--primary); }
|
|
.maturity-segment.developing { background: var(--warning); }
|
|
.maturity-segment.early { background: var(--border); }
|
|
|
|
.maturity-legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-3);
|
|
margin-bottom: var(--space-5);
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.maturity-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.maturity-legend-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.maturity-legend-dot.optimized { background: var(--success); }
|
|
.maturity-legend-dot.mature { background: var(--primary); }
|
|
.maturity-legend-dot.developing { background: var(--warning); }
|
|
.maturity-legend-dot.early { background: var(--border); }
|
|
|
|
/* Team rows in leaderboard - 2-column grid for full-width layout */
|
|
.team-rows-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0 var(--space-8);
|
|
}
|
|
|
|
.team-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
padding: var(--space-2) 0;
|
|
border-bottom: 1px solid var(--border-light);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.team-row:hover {
|
|
background: var(--background);
|
|
margin: 0 calc(-1 * var(--space-3));
|
|
padding: var(--space-2) var(--space-3);
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.team-row-name {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-primary);
|
|
min-width: 140px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.team-row-bar-wrap {
|
|
flex: 1;
|
|
height: 6px;
|
|
background: var(--background);
|
|
border-radius: var(--radius-full);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.team-row-bar {
|
|
height: 100%;
|
|
border-radius: var(--radius-full);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.team-row-bar.optimized { background: var(--success); }
|
|
.team-row-bar.mature { background: var(--primary); }
|
|
.team-row-bar.developing { background: var(--warning); }
|
|
.team-row-bar.early { background: var(--border); }
|
|
|
|
.team-row-score {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-primary);
|
|
min-width: 28px;
|
|
text-align: right;
|
|
}
|
|
|
|
.team-row-trend {
|
|
font-size: var(--text-sm);
|
|
min-width: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.team-row-trend.up { color: var(--success); }
|
|
.team-row-trend.stable { color: var(--text-secondary); }
|
|
.team-row-trend.down { color: var(--error); }
|
|
|
|
/* ========== Live Activity Feed ========== */
|
|
.feed-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.feed-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: var(--space-5);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.feed-header h3 {
|
|
font-size: var(--text-md);
|
|
font-weight: var(--font-semibold);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.feed-filters {
|
|
display: flex;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.feed-filter-btn {
|
|
padding: var(--space-1) var(--space-3);
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.feed-filter-btn:hover { border-color: var(--text-secondary); }
|
|
|
|
.feed-filter-btn.active {
|
|
background: var(--primary-light);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.feed-list {
|
|
padding: var(--space-3);
|
|
}
|
|
|
|
/* Feed items in 2-column grid for full-width layout */
|
|
.feed-list-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0 var(--space-4);
|
|
}
|
|
|
|
.feed-item {
|
|
display: flex;
|
|
gap: var(--space-3);
|
|
padding: var(--space-3);
|
|
border-radius: var(--radius-md);
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.feed-item:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.feed-avatar {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-full);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: white;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.feed-body {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.feed-person {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.feed-team {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-left: var(--space-1);
|
|
}
|
|
|
|
.feed-process {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.feed-process.optimized { color: var(--success); }
|
|
.feed-process.mature { color: var(--primary); }
|
|
.feed-process.developing { color: #b45309; }
|
|
.feed-process.early { color: var(--text-secondary); }
|
|
|
|
.feed-query {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: 2px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.feed-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
margin-top: var(--space-1);
|
|
}
|
|
|
|
.feed-time {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.feed-status {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: var(--radius-full);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.feed-status.success { background: var(--success); }
|
|
.feed-status.partial { background: var(--warning); }
|
|
|
|
/* ========== Data Opportunities ========== */
|
|
.opportunity-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
padding: var(--space-5);
|
|
margin-bottom: var(--space-4);
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.opportunity-card:hover {
|
|
border-color: var(--primary);
|
|
box-shadow: var(--shadow-md);
|
|
}
|
|
|
|
.opportunity-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-3);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.priority-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 8px;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-bold);
|
|
border-radius: var(--radius-full);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.priority-badge.high { background: rgba(234, 88, 12, 0.1); color: var(--error); }
|
|
.priority-badge.medium { background: rgba(245, 159, 10, 0.1); color: #b45309; }
|
|
.priority-badge.low { background: var(--background); color: var(--text-secondary); }
|
|
|
|
.opportunity-title {
|
|
font-size: var(--text-md);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.opportunity-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-3);
|
|
margin-bottom: var(--space-3);
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.opportunity-meta-label {
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.opportunity-team-pills {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
.team-pill {
|
|
font-size: var(--text-xs);
|
|
background: var(--primary-light);
|
|
color: var(--primary);
|
|
padding: 1px 6px;
|
|
border-radius: var(--radius-full);
|
|
font-weight: var(--font-medium);
|
|
}
|
|
|
|
.opportunity-business-case {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.opportunity-card {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.opportunity-expand-icon {
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s;
|
|
flex-shrink: 0;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.opportunity-expand-icon.expanded {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/* Opportunity Detail Panel */
|
|
.opportunity-detail {
|
|
display: none;
|
|
margin-top: var(--space-4);
|
|
padding-top: var(--space-4);
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.opportunity-detail.visible {
|
|
display: block;
|
|
}
|
|
|
|
.opp-detail-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: var(--space-5);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.opp-detail-section {
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.opp-detail-section:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.opp-detail-label {
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: var(--space-2);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.opp-detail-label svg {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.opp-detail-text {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Mermaid diagram container */
|
|
.opp-mermaid-wrap {
|
|
background: var(--background);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-4);
|
|
margin-bottom: var(--space-4);
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.opp-mermaid-wrap .mermaid {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.opp-mermaid-wrap svg {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
.opp-diagram-legend {
|
|
display: flex;
|
|
gap: var(--space-4);
|
|
justify-content: center;
|
|
margin-top: var(--space-2);
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.opp-legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
.opp-legend-dot {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.opp-legend-dot.existing {
|
|
background: #dbeafe;
|
|
border: 1px solid #2563eb;
|
|
}
|
|
|
|
.opp-legend-dot.new {
|
|
background: #fed7aa;
|
|
border: 1px solid #ea580c;
|
|
}
|
|
|
|
/* Join keys table */
|
|
.opp-join-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: var(--text-sm);
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.opp-join-table th {
|
|
text-align: left;
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.3px;
|
|
padding: var(--space-2) var(--space-3);
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--background);
|
|
}
|
|
|
|
.opp-join-table td {
|
|
padding: var(--space-2) var(--space-3);
|
|
border-bottom: 1px solid var(--border-light);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.opp-join-table .col-name {
|
|
font-family: var(--font-mono);
|
|
font-size: var(--text-xs);
|
|
background: var(--background);
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
color: var(--primary-dark);
|
|
}
|
|
|
|
.opp-join-table .tbl-new {
|
|
color: #ea580c;
|
|
font-weight: var(--font-medium);
|
|
}
|
|
|
|
.opp-join-table .tbl-existing {
|
|
color: #2563eb;
|
|
font-weight: var(--font-medium);
|
|
}
|
|
|
|
.opp-join-arrow {
|
|
color: var(--text-secondary);
|
|
text-align: center;
|
|
}
|
|
|
|
/* Team impact list */
|
|
.opp-beneficiary {
|
|
display: flex;
|
|
gap: var(--space-3);
|
|
padding: var(--space-3) 0;
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.opp-beneficiary:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.opp-beneficiary-team {
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--primary);
|
|
background: var(--primary-light);
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-full);
|
|
white-space: nowrap;
|
|
height: fit-content;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.opp-beneficiary-impact {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-primary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Enabled queries */
|
|
.opp-query-example {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-2);
|
|
padding: var(--space-2) 0;
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.opp-query-example::before {
|
|
content: ">";
|
|
font-family: var(--font-mono);
|
|
color: var(--success);
|
|
font-weight: var(--font-bold);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.opp-detail-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* ========== Team Detail Accordion ========== */
|
|
.team-accordion {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-xl);
|
|
box-shadow: var(--shadow-sm);
|
|
margin-bottom: var(--space-3);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.team-detail-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
padding: var(--space-4) var(--space-5);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.team-detail-header:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.team-detail-name {
|
|
font-size: var(--text-base);
|
|
font-weight: var(--font-semibold);
|
|
color: var(--text-primary);
|
|
min-width: 140px;
|
|
}
|
|
|
|
.team-detail-stat {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.team-detail-stat strong {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.team-detail-highlight {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
flex: 1;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.team-detail-chevron {
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.team-detail-chevron.expanded {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.team-detail-body {
|
|
display: none;
|
|
border-top: 1px solid var(--border);
|
|
padding: var(--space-4) var(--space-5);
|
|
}
|
|
|
|
.team-detail-body.visible {
|
|
display: block;
|
|
}
|
|
|
|
.member-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-3);
|
|
padding: var(--space-3) 0;
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.member-row:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.member-status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: var(--radius-full);
|
|
margin-top: 5px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.member-status-dot.active { background: var(--success); }
|
|
.member-status-dot.idle { background: var(--warning); }
|
|
.member-status-dot.offline { background: var(--border); }
|
|
|
|
.member-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.member-name {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.member-role {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.member-stats {
|
|
display: flex;
|
|
gap: var(--space-4);
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.member-recent-queries {
|
|
margin-top: var(--space-2);
|
|
}
|
|
|
|
.member-query-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-1) 0;
|
|
font-size: var(--text-xs);
|
|
}
|
|
|
|
.member-query-process {
|
|
padding: 1px 6px;
|
|
border-radius: var(--radius-sm);
|
|
font-weight: var(--font-medium);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.member-query-process.optimized { background: rgba(16, 183, 127, 0.1); color: var(--success); }
|
|
.member-query-process.mature { background: var(--primary-light); color: var(--primary); }
|
|
.member-query-process.developing { background: rgba(245, 159, 10, 0.1); color: #b45309; }
|
|
.member-query-process.early { background: var(--background); color: var(--text-secondary); }
|
|
|
|
.member-query-text {
|
|
color: var(--text-secondary);
|
|
flex: 1;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.member-query-time {
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.member-query-status {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: var(--radius-full);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.member-query-status.success { background: var(--success); }
|
|
.member-query-status.partial { background: var(--warning); }
|
|
|
|
/* ========== Footer ========== */
|
|
.page-footer {
|
|
margin-top: var(--space-8);
|
|
padding-top: var(--space-5);
|
|
border-top: 1px solid var(--border);
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
/* ========== Overview Tab ========== */
|
|
.overview-hero {
|
|
display: grid;
|
|
grid-template-columns: auto 1fr auto;
|
|
gap: var(--space-6);
|
|
align-items: center;
|
|
padding: var(--space-6);
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.overview-score-ring {
|
|
position: relative;
|
|
width: 120px;
|
|
height: 120px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.overview-score-ring svg {
|
|
transform: rotate(-90deg);
|
|
width: 120px;
|
|
height: 120px;
|
|
}
|
|
|
|
.overview-score-ring .ring-bg {
|
|
fill: none;
|
|
stroke: var(--border);
|
|
stroke-width: 8;
|
|
}
|
|
|
|
.overview-score-ring .ring-fill {
|
|
fill: none;
|
|
stroke-width: 8;
|
|
stroke-linecap: round;
|
|
transition: stroke-dashoffset 0.8s ease;
|
|
}
|
|
|
|
.overview-score-ring .ring-fill.level-optimized { stroke: var(--success); }
|
|
.overview-score-ring .ring-fill.level-mature { stroke: var(--primary); }
|
|
.overview-score-ring .ring-fill.level-developing { stroke: var(--warning); }
|
|
|
|
.overview-score-value {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
}
|
|
|
|
.overview-score-value .number {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1;
|
|
}
|
|
|
|
.overview-score-value .label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.overview-level-counts {
|
|
display: flex;
|
|
gap: var(--space-5);
|
|
}
|
|
|
|
.overview-level-count {
|
|
text-align: center;
|
|
}
|
|
|
|
.overview-level-count .count {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
|
|
.overview-level-count .count.optimized { color: var(--success); }
|
|
.overview-level-count .count.mature { color: var(--primary); }
|
|
.overview-level-count .count.developing { color: var(--warning); }
|
|
|
|
.overview-level-count .level-label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.overview-total-value {
|
|
text-align: right;
|
|
padding-left: var(--space-4);
|
|
border-left: 1px solid var(--border);
|
|
}
|
|
|
|
.overview-total-value .value {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--success);
|
|
line-height: 1;
|
|
}
|
|
|
|
.overview-total-value .label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* Roadmap Lanes */
|
|
.roadmap-section {
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.roadmap-section-title {
|
|
font-size: var(--text-lg);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.roadmap-lanes {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.roadmap-lane {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-4);
|
|
min-height: 120px;
|
|
}
|
|
|
|
.roadmap-lane-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
margin-bottom: var(--space-3);
|
|
padding-bottom: var(--space-3);
|
|
border-bottom: 2px solid var(--border);
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.roadmap-lane-header.developing { border-bottom-color: var(--warning); color: var(--warning); }
|
|
.roadmap-lane-header.mature { border-bottom-color: var(--primary); color: var(--primary); }
|
|
.roadmap-lane-header.optimized { border-bottom-color: var(--success); color: var(--success); }
|
|
|
|
.roadmap-card {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--space-3);
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
margin-bottom: var(--space-2);
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.roadmap-card:hover {
|
|
border-color: var(--primary);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
}
|
|
|
|
.roadmap-card:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.roadmap-card-name {
|
|
font-size: var(--text-sm);
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.roadmap-card-score {
|
|
font-size: var(--text-xs);
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.roadmap-card-arrow {
|
|
color: var(--text-tertiary);
|
|
margin-left: var(--space-2);
|
|
font-size: var(--text-xs);
|
|
}
|
|
|
|
/* Category Detail Cards */
|
|
.overview-details-title {
|
|
font-size: var(--text-lg);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--space-4);
|
|
}
|
|
|
|
.overview-category-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
margin-bottom: var(--space-3);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.overview-category-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
padding: var(--space-4);
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
}
|
|
|
|
.overview-category-header:hover {
|
|
background: var(--bg);
|
|
}
|
|
|
|
.overview-cat-level {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
font-size: var(--text-xs);
|
|
font-weight: 600;
|
|
border-radius: var(--radius-full);
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
.overview-cat-level.optimized { background: rgba(16, 185, 129, 0.12); color: var(--success); }
|
|
.overview-cat-level.mature { background: rgba(59, 130, 246, 0.12); color: var(--primary); }
|
|
.overview-cat-level.developing { background: rgba(245, 159, 10, 0.12); color: var(--warning); }
|
|
|
|
.overview-cat-name {
|
|
font-size: var(--text-base);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
flex: 1;
|
|
}
|
|
|
|
.overview-cat-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-4);
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.overview-cat-chevron {
|
|
transition: transform 0.2s ease;
|
|
color: var(--text-tertiary);
|
|
}
|
|
|
|
.overview-cat-chevron.expanded {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.overview-category-body {
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
transition: max-height 0.3s ease;
|
|
}
|
|
|
|
.overview-category-body.visible {
|
|
max-height: 800px;
|
|
}
|
|
|
|
.overview-category-content {
|
|
padding: 0 var(--space-4) var(--space-4);
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.overview-processes-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 var(--space-3) 0;
|
|
}
|
|
|
|
.overview-processes-list li {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: 4px 0;
|
|
font-size: var(--text-sm);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.overview-process-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.overview-process-dot.optimized { background: var(--success); }
|
|
.overview-process-dot.mature { background: var(--primary); }
|
|
.overview-process-dot.developing { background: var(--warning); }
|
|
.overview-process-dot.early { background: var(--text-tertiary); }
|
|
|
|
.overview-process-status {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-left: auto;
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
.overview-actions-section h4,
|
|
.overview-left-col h4 {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
margin: 0 0 var(--space-2) 0;
|
|
}
|
|
|
|
.overview-actions-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 var(--space-3) 0;
|
|
}
|
|
|
|
.overview-actions-list li {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-2);
|
|
padding: 4px 0;
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.overview-actions-list li::before {
|
|
content: "\2610";
|
|
color: var(--text-tertiary);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.overview-effort-timeline {
|
|
display: flex;
|
|
gap: var(--space-3);
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.overview-effort-timeline span {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
background: var(--bg);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
/* Timeline Comparison Widget */
|
|
.timeline-compare {
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-3) var(--space-4);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.timeline-compare-header {
|
|
font-size: var(--text-xs);
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.timeline-compare-row {
|
|
display: grid;
|
|
grid-template-columns: 100px 1fr 80px;
|
|
align-items: center;
|
|
gap: var(--space-3);
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.timeline-compare-row:last-of-type {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.timeline-compare-label {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.timeline-bar-track {
|
|
height: 22px;
|
|
background: var(--surface);
|
|
border-radius: var(--radius-sm);
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.timeline-bar-fill {
|
|
height: 100%;
|
|
border-radius: var(--radius-sm);
|
|
transition: width 0.6s ease;
|
|
}
|
|
|
|
.timeline-bar-fill.traditional {
|
|
background: linear-gradient(90deg, rgba(245, 159, 10, 0.3), rgba(245, 159, 10, 0.15));
|
|
border: 1px solid rgba(245, 159, 10, 0.4);
|
|
}
|
|
|
|
.timeline-bar-fill.ai-accelerated {
|
|
background: linear-gradient(90deg, rgba(99, 91, 255, 0.35), rgba(99, 91, 255, 0.15));
|
|
border: 1px solid rgba(99, 91, 255, 0.5);
|
|
}
|
|
|
|
.timeline-compare-value {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
text-align: right;
|
|
}
|
|
|
|
.timeline-compare-value.traditional {
|
|
color: var(--warning);
|
|
}
|
|
|
|
.timeline-compare-value.ai-accelerated {
|
|
color: #635BFF;
|
|
}
|
|
|
|
.timeline-ai-note {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-2);
|
|
margin-top: var(--space-3);
|
|
padding-top: var(--space-2);
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.timeline-ai-note svg {
|
|
flex-shrink: 0;
|
|
margin-top: 1px;
|
|
}
|
|
|
|
.timeline-ai-note-text {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.timeline-ai-note-text strong {
|
|
color: #635BFF;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.timeline-speedup-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
padding: 1px 6px;
|
|
background: rgba(99, 91, 255, 0.1);
|
|
color: #635BFF;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
border-radius: var(--radius-full);
|
|
margin-left: var(--space-2);
|
|
}
|
|
|
|
.overview-blockers {
|
|
font-size: var(--text-xs);
|
|
color: var(--text-secondary);
|
|
padding: var(--space-2) var(--space-3);
|
|
background: rgba(245, 159, 10, 0.06);
|
|
border-radius: var(--radius-sm);
|
|
border-left: 3px solid var(--warning);
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.overview-business-value {
|
|
padding: var(--space-3);
|
|
background: rgba(16, 185, 129, 0.06);
|
|
border-radius: var(--radius-md);
|
|
border-left: 3px solid var(--success);
|
|
}
|
|
|
|
.overview-business-value h4 {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--success);
|
|
margin: 0 0 4px 0;
|
|
}
|
|
|
|
.overview-business-value p {
|
|
font-size: var(--text-sm);
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* ========== Responsive ========== */
|
|
@media (max-width: 1024px) {
|
|
.team-rows-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.feed-list-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.roadmap-lanes {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.overview-hero {
|
|
grid-template-columns: auto 1fr;
|
|
}
|
|
.overview-total-value {
|
|
grid-column: 1 / -1;
|
|
text-align: left;
|
|
border-left: none;
|
|
padding-left: 0;
|
|
padding-top: var(--space-3);
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.container-activity {
|
|
padding: var(--space-4);
|
|
}
|
|
|
|
.page-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: var(--space-3);
|
|
}
|
|
|
|
.exec-stats-bar {
|
|
flex-wrap: wrap;
|
|
gap: var(--space-4);
|
|
padding: var(--space-4);
|
|
}
|
|
|
|
.exec-stat {
|
|
min-width: calc(50% - var(--space-4));
|
|
}
|
|
|
|
.exec-divider {
|
|
display: none;
|
|
}
|
|
|
|
.tab-nav {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.process-row {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.process-teams {
|
|
width: 100%;
|
|
margin-top: var(--space-1);
|
|
}
|
|
|
|
.team-detail-header {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.member-stats {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.feed-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: var(--space-3);
|
|
}
|
|
|
|
.opportunity-header {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.overview-hero {
|
|
grid-template-columns: 1fr;
|
|
text-align: center;
|
|
}
|
|
.overview-score-ring {
|
|
margin: 0 auto;
|
|
}
|
|
.overview-level-counts {
|
|
justify-content: center;
|
|
}
|
|
.overview-total-value {
|
|
text-align: center;
|
|
}
|
|
.overview-category-content {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
{% include '_theme.html' %}
|
|
</head>
|
|
<body>
|
|
<!-- Top Header Bar (matches Data Catalog pattern) -->
|
|
<header class="top-header">
|
|
<div class="top-header-left">
|
|
<a href="{{ url_for('dashboard') }}" class="header-back" title="Back to Dashboard">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
</svg>
|
|
</a>
|
|
<div class="header-logo-group">
|
|
<div class="header-logo">
|
|
{{ config.LOGO_SVG | safe }}
|
|
</div>
|
|
<span class="header-subtitle">Activity Center <span class="demo-badge">DEMO</span></span>
|
|
</div>
|
|
</div>
|
|
<div class="top-header-right">
|
|
{% if session.user.picture %}
|
|
<img src="{{ session.user.picture }}" alt="Profile" class="avatar-v2">
|
|
{% endif %}
|
|
{{ session.user.email }}
|
|
</div>
|
|
</header>
|
|
|
|
<div class="container-activity">
|
|
<!-- Executive Pulse - always visible -->
|
|
<div class="exec-stats-bar">
|
|
<div class="exec-stat">
|
|
<div class="value">{{ activity.executive_summary.active_today }}</div>
|
|
<div class="label">Active Today</div>
|
|
</div>
|
|
<div class="exec-divider"></div>
|
|
<div class="exec-stat">
|
|
<div class="value">{{ activity.executive_summary.business_processes_identified }}</div>
|
|
<div class="label">Business Processes</div>
|
|
</div>
|
|
<div class="exec-divider"></div>
|
|
<div class="exec-stat">
|
|
<div class="value">{{ activity.executive_summary.decisions_supported_this_week }}<span class="suffix">/wk</span></div>
|
|
<div class="label">Decisions Supported</div>
|
|
</div>
|
|
<div class="exec-divider"></div>
|
|
<div class="exec-stat">
|
|
<div class="value">{{ activity.executive_summary.avg_success_rate }}<span class="suffix">%</span></div>
|
|
<div class="label">Success Rate</div>
|
|
</div>
|
|
<div class="exec-divider"></div>
|
|
<div class="exec-stat">
|
|
<div class="value success">{{ activity.executive_summary.adoption_trend }}</div>
|
|
<div class="label">Adoption Trend</div>
|
|
</div>
|
|
</div>
|
|
<div class="exec-summary-sentence">
|
|
{{ activity.executive_summary.active_this_week }} analysts across {{ activity.executive_summary.teams_active }} teams powering {{ activity.executive_summary.business_processes_identified }} business processes with data-driven decisions
|
|
</div>
|
|
|
|
<!-- Tab Navigation -->
|
|
<nav class="tab-nav" role="tablist">
|
|
<button class="tab-btn active" role="tab" aria-selected="true" data-tab="overview" onclick="switchTab('overview', this)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 000 20 14.5 14.5 0 000-20"/><path d="M2 12h20"/></svg>
|
|
Overview
|
|
</button>
|
|
<button class="tab-btn" role="tab" aria-selected="false" data-tab="processes" onclick="switchTab('processes', this)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
|
Processes
|
|
<span class="tab-count">{{ activity.business_processes | length }}</span>
|
|
</button>
|
|
<button class="tab-btn" role="tab" aria-selected="false" data-tab="teams" onclick="switchTab('teams', this)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
|
|
Teams
|
|
<span class="tab-count">{{ activity.teams | length }}</span>
|
|
</button>
|
|
<button class="tab-btn" role="tab" aria-selected="false" data-tab="activity" onclick="switchTab('activity', this)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
Activity
|
|
<span class="tab-count">{{ activity.activity_feed | length }}</span>
|
|
</button>
|
|
<button class="tab-btn" role="tab" aria-selected="false" data-tab="opportunities" onclick="switchTab('opportunities', this)">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
Opportunities
|
|
<span class="tab-count">{{ activity.data_opportunities | length }}</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Tab Panel: Overview -->
|
|
<div class="tab-panel active" id="tab-overview" role="tabpanel">
|
|
<div class="section">
|
|
<!-- Hero: Score + Counts + Value -->
|
|
<div class="overview-hero">
|
|
<div class="overview-score-ring">
|
|
{% set score = activity.maturity_roadmap.summary.overall_score %}
|
|
{% set circumference = 2 * 3.14159 * 48 %}
|
|
{% set dash_offset = circumference * (1 - score / 100) %}
|
|
{% if score >= 80 %}{% set ring_class = "level-optimized" %}
|
|
{% elif score >= 60 %}{% set ring_class = "level-mature" %}
|
|
{% else %}{% set ring_class = "level-developing" %}{% endif %}
|
|
<svg viewBox="0 0 120 120">
|
|
<circle class="ring-bg" cx="60" cy="60" r="48"/>
|
|
<circle class="ring-fill {{ ring_class }}" cx="60" cy="60" r="48"
|
|
stroke-dasharray="{{ circumference }}"
|
|
stroke-dashoffset="{{ dash_offset }}"/>
|
|
</svg>
|
|
<div class="overview-score-value">
|
|
<div class="number">{{ score }}</div>
|
|
<div class="label">Maturity</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overview-level-counts">
|
|
<div class="overview-level-count">
|
|
<div class="count optimized">{{ activity.maturity_roadmap.summary.optimized_count }}</div>
|
|
<div class="level-label">Optimized</div>
|
|
</div>
|
|
<div class="overview-level-count">
|
|
<div class="count mature">{{ activity.maturity_roadmap.summary.mature_count }}</div>
|
|
<div class="level-label">Mature</div>
|
|
</div>
|
|
<div class="overview-level-count">
|
|
<div class="count developing">{{ activity.maturity_roadmap.summary.developing_count }}</div>
|
|
<div class="level-label">Developing</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overview-total-value">
|
|
<div class="value">{{ activity.maturity_roadmap.summary.total_potential_value }}</div>
|
|
<div class="label">Estimated annual value</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Roadmap Lanes -->
|
|
<div class="roadmap-section">
|
|
<h2 class="roadmap-section-title">Maturity Roadmap</h2>
|
|
<div class="roadmap-lanes">
|
|
<div class="roadmap-lane">
|
|
<div class="roadmap-lane-header developing">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
Developing
|
|
</div>
|
|
{% for cat in activity.maturity_roadmap.categories %}
|
|
{% if cat.current_level == 'developing' %}
|
|
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
|
|
<div>
|
|
<div class="roadmap-card-name">{{ cat.name }}</div>
|
|
<div class="roadmap-card-score">{{ cat.score }}/100</div>
|
|
</div>
|
|
<span class="roadmap-card-arrow">→</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
<div class="roadmap-lane">
|
|
<div class="roadmap-lane-header mature">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
Mature
|
|
</div>
|
|
{% for cat in activity.maturity_roadmap.categories %}
|
|
{% if cat.current_level == 'mature' %}
|
|
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
|
|
<div>
|
|
<div class="roadmap-card-name">{{ cat.name }}</div>
|
|
<div class="roadmap-card-score">{{ cat.score }}/100</div>
|
|
</div>
|
|
<span class="roadmap-card-arrow">→</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
<div class="roadmap-lane">
|
|
<div class="roadmap-lane-header optimized">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
|
|
Optimized
|
|
</div>
|
|
{% for cat in activity.maturity_roadmap.categories %}
|
|
{% if cat.current_level == 'optimized' %}
|
|
<div class="roadmap-card" onclick="scrollToOverviewCategory('{{ cat.id }}')">
|
|
<div>
|
|
<div class="roadmap-card-name">{{ cat.name }}</div>
|
|
<div class="roadmap-card-score">{{ cat.score }}/100</div>
|
|
</div>
|
|
<span class="roadmap-card-arrow">★</span>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Details -->
|
|
<div class="overview-details-section">
|
|
<h2 class="overview-details-title">Category Details</h2>
|
|
{% for cat in activity.maturity_roadmap.categories %}
|
|
<div class="overview-category-card" id="overview-cat-{{ cat.id }}">
|
|
<div class="overview-category-header" onclick="toggleOverviewCategory('{{ cat.id }}')">
|
|
<span class="overview-cat-level {{ cat.current_level }}">{{ cat.current_level }}</span>
|
|
<span class="overview-cat-name">{{ cat.name }}</span>
|
|
<span class="overview-cat-meta">
|
|
<span>{{ cat.score }}/100</span>
|
|
<span>{{ cat.process_count }} processes</span>
|
|
</span>
|
|
<svg class="overview-cat-chevron" id="overview-chevron-{{ cat.id }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
|
</div>
|
|
<div class="overview-category-body" id="overview-body-{{ cat.id }}">
|
|
<div class="overview-category-content">
|
|
<div class="overview-left-col">
|
|
<h4>Current Processes</h4>
|
|
<ul class="overview-processes-list">
|
|
{% for proc in cat.processes %}
|
|
<li>
|
|
<span class="overview-process-dot {{ proc.status }}"></span>
|
|
{{ proc.name }}
|
|
<span class="overview-process-status">{{ proc.status }}</span>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
|
|
{% if cat.advancement %}
|
|
<h4>Actions to reach {{ cat.advancement.target_level|capitalize }}</h4>
|
|
<ul class="overview-actions-list">
|
|
{% for action in cat.advancement.actions %}
|
|
<li>{{ action }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
<div class="overview-effort-timeline">
|
|
<span>Effort: {{ cat.advancement.effort|capitalize }}</span>
|
|
</div>
|
|
{% if cat.advancement.blockers %}
|
|
<div class="overview-blockers">
|
|
{{ cat.advancement.blockers }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Timeline Comparison -->
|
|
<div class="timeline-compare">
|
|
<div class="timeline-compare-header">Implementation Timeline</div>
|
|
<div class="timeline-compare-row">
|
|
<span class="timeline-compare-label">Traditional</span>
|
|
<div class="timeline-bar-track">
|
|
<div class="timeline-bar-fill traditional" style="width: 100%"></div>
|
|
</div>
|
|
<span class="timeline-compare-value traditional">{{ cat.advancement.timeline }}</span>
|
|
</div>
|
|
<div class="timeline-compare-row">
|
|
<span class="timeline-compare-label">With AI Data Analyst</span>
|
|
<div class="timeline-bar-track">
|
|
<div class="timeline-bar-fill ai-accelerated" style="width: 8%"></div>
|
|
</div>
|
|
<span class="timeline-compare-value ai-accelerated">
|
|
{{ cat.advancement.ai_timeline }}
|
|
<span class="timeline-speedup-badge">
|
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10"/></svg>
|
|
Fast
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div class="timeline-ai-note">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#635BFF" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
|
<span class="timeline-ai-note-text">
|
|
<strong>AI Data Platform:</strong> {{ cat.advancement.ai_description }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="overview-right-col">
|
|
<div class="overview-business-value">
|
|
<h4>Business Value</h4>
|
|
<p>{{ cat.business_value }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Panel: Processes -->
|
|
<div class="tab-panel" id="tab-processes" role="tabpanel">
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2>Business Process Intelligence</h2>
|
|
<p>Mapping data queries to active business processes across the organization</p>
|
|
</div>
|
|
|
|
{% set ns = namespace(cat_processes={}) %}
|
|
{% for process in activity.business_processes %}
|
|
{% if process.category not in ns.cat_processes %}
|
|
{% set _ = ns.cat_processes.update({process.category: []}) %}
|
|
{% endif %}
|
|
{% set _ = ns.cat_processes[process.category].append(process) %}
|
|
{% endfor %}
|
|
|
|
{% for category, processes in ns.cat_processes.items() %}
|
|
{% set cat_idx = loop.index %}
|
|
<div class="process-category">
|
|
<div class="process-category-header">
|
|
{{ category }}
|
|
<span class="process-category-count">{{ processes | length }}</span>
|
|
</div>
|
|
{% for process in processes %}
|
|
<div class="process-row" onclick="toggleProcessDetail('process-{{ cat_idx }}-{{ loop.index }}')">
|
|
<span class="process-status-dot {{ process.status }}"></span>
|
|
<span class="process-name">{{ process.name }}</span>
|
|
<span class="maturity-badge {{ process.status }}">{{ process.status }}</span>
|
|
<span class="process-queries">{{ process.queries_this_week }} queries/wk</span>
|
|
<span class="process-teams">{{ process.teams_involved | join(', ') }}</span>
|
|
<svg class="process-expand-icon" id="icon-process-{{ cat_idx }}-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M6 9l6 6 6-6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="process-detail" id="process-{{ cat_idx }}-{{ loop.index }}">
|
|
{% if process.description %}
|
|
<div class="process-detail-section">
|
|
<div class="process-detail-label">Description</div>
|
|
<div class="process-detail-text">{{ process.description }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if process.sample_queries %}
|
|
<div class="process-detail-section">
|
|
<div class="process-detail-label">Sample Queries</div>
|
|
<div class="process-detail-tags">
|
|
{% for query in process.sample_queries %}
|
|
<span class="process-detail-tag">{{ query }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if process.data_sources %}
|
|
<div class="process-detail-section">
|
|
<div class="process-detail-label">Data Sources</div>
|
|
<div class="process-detail-tags">
|
|
{% for source in process.data_sources %}
|
|
<span class="process-detail-tag">{{ source }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if process.impact %}
|
|
<div class="process-detail-section">
|
|
<div class="process-detail-label">Impact</div>
|
|
<div class="process-detail-text">{{ process.impact }}</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Panel: Teams -->
|
|
<div class="tab-panel" id="tab-teams" role="tabpanel">
|
|
<!-- Team Maturity Leaderboard -->
|
|
<div class="leaderboard-card">
|
|
<h3>Team Maturity</h3>
|
|
|
|
{% set optimized_count = activity.teams | selectattr('maturity', 'equalto', 'optimized') | list | length %}
|
|
{% set mature_count = activity.teams | selectattr('maturity', 'equalto', 'mature') | list | length %}
|
|
{% set developing_count = activity.teams | selectattr('maturity', 'equalto', 'developing') | list | length %}
|
|
{% set early_count = activity.teams | selectattr('maturity', 'equalto', 'early') | list | length %}
|
|
{% set total_teams = activity.teams | length %}
|
|
|
|
<div class="maturity-distribution">
|
|
{% if optimized_count > 0 %}
|
|
<div class="maturity-segment optimized" style="width: {{ (optimized_count / total_teams * 100) | round(1) }}%"></div>
|
|
{% endif %}
|
|
{% if mature_count > 0 %}
|
|
<div class="maturity-segment mature" style="width: {{ (mature_count / total_teams * 100) | round(1) }}%"></div>
|
|
{% endif %}
|
|
{% if developing_count > 0 %}
|
|
<div class="maturity-segment developing" style="width: {{ (developing_count / total_teams * 100) | round(1) }}%"></div>
|
|
{% endif %}
|
|
{% if early_count > 0 %}
|
|
<div class="maturity-segment early" style="width: {{ (early_count / total_teams * 100) | round(1) }}%"></div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="maturity-legend">
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot optimized"></span> Optimized ({{ optimized_count }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot mature"></span> Mature ({{ mature_count }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot developing"></span> Developing ({{ developing_count }})</span>
|
|
<span class="maturity-legend-item"><span class="maturity-legend-dot early"></span> Early ({{ early_count }})</span>
|
|
</div>
|
|
|
|
<div class="team-rows-grid">
|
|
{% for team in activity.teams | sort(attribute='maturity_score', reverse=True) %}
|
|
<div class="team-row" onclick="scrollToTeam('team-detail-{{ loop.index }}')">
|
|
<span class="team-row-name">{{ team.name }}</span>
|
|
<div class="team-row-bar-wrap">
|
|
<div class="team-row-bar {{ team.maturity }}" style="width: {{ team.maturity_score }}%"></div>
|
|
</div>
|
|
<span class="team-row-score">{{ team.maturity_score }}</span>
|
|
<span class="team-row-trend {% if team.trend == 'up' %}up{% elif team.trend == 'down' %}down{% else %}stable{% endif %}">
|
|
{% if team.trend == 'up' %}▲{% elif team.trend == 'down' %}▼{% else %}—{% endif %}
|
|
</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Team Detail Accordion -->
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2>Team Details</h2>
|
|
</div>
|
|
|
|
{% for team in activity.teams | sort(attribute='maturity_score', reverse=True) %}
|
|
<div class="team-accordion" id="team-detail-{{ loop.index }}">
|
|
<div class="team-detail-header" onclick="toggleTeam('team-detail-{{ loop.index }}')">
|
|
<span class="team-detail-name">{{ team.name }}</span>
|
|
<span class="maturity-badge {{ team.maturity }}">{{ team.maturity }}</span>
|
|
<span class="team-detail-stat"><strong>{{ team.members | length }}</strong> members</span>
|
|
<span class="team-detail-stat"><strong>{{ team.active_count }}</strong> active</span>
|
|
<span class="team-detail-stat">{{ team.success_rate }}% success</span>
|
|
<span class="team-detail-highlight">{{ team.highlight }}</span>
|
|
<svg class="team-detail-chevron" id="chevron-team-detail-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M6 9l6 6 6-6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="team-detail-body" id="body-team-detail-{{ loop.index }}">
|
|
{% for member in team.members %}
|
|
<div class="member-row">
|
|
<span class="member-status-dot {{ member.status | lower }}"></span>
|
|
<div class="member-info">
|
|
<div class="member-name">{{ member.name }}</div>
|
|
<div class="member-role">{{ member.role }}</div>
|
|
</div>
|
|
<div class="member-stats">
|
|
<span>Last active: {{ member.last_active }}</span>
|
|
<span>{{ member.queries_today }} queries today</span>
|
|
</div>
|
|
{% if member.recent_queries %}
|
|
<div class="member-recent-queries">
|
|
{% for query in member.recent_queries %}
|
|
<div class="member-query-item">
|
|
<span class="member-query-process">{{ query.process }}</span>
|
|
<span class="member-query-text" title="{{ query.text }}">{{ query.text }}</span>
|
|
<span class="member-query-time">{{ query.timestamp }}</span>
|
|
<span class="member-query-status {{ query.status | lower }}"></span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Panel: Activity -->
|
|
<div class="tab-panel" id="tab-activity" role="tabpanel">
|
|
<div class="feed-card">
|
|
<div class="feed-header">
|
|
<h3>
|
|
<span class="live-dot"></span>
|
|
Live Activity
|
|
</h3>
|
|
<div class="feed-filters">
|
|
<button class="feed-filter-btn active" onclick="filterActivity('all', this)">All</button>
|
|
<button class="feed-filter-btn" onclick="filterActivity('today', this)">Today</button>
|
|
<button class="feed-filter-btn" onclick="filterActivity('hour', this)">This Hour</button>
|
|
</div>
|
|
</div>
|
|
<div class="feed-list" id="activityFeed">
|
|
<div class="feed-list-grid">
|
|
{% for item in activity.activity_feed %}
|
|
<div class="feed-item" data-index="{{ loop.index }}">
|
|
<div class="feed-avatar" style="background: {{ ['#0073D1', '#10B77F', '#F59F0A', '#EA580C', '#7c3aed', '#0d9668', '#b45309', '#005BA3'][loop.index0 % 8] }}">
|
|
{{ item.person_name[:2] | upper }}
|
|
</div>
|
|
<div class="feed-body">
|
|
<div>
|
|
<span class="feed-person">{{ item.person_name }}</span>
|
|
<span class="feed-team">{{ item.team }}</span>
|
|
</div>
|
|
<div class="feed-process">{{ item.process_name }}</div>
|
|
<div class="feed-query" title="{{ item.query_text }}">{{ item.query_text }}</div>
|
|
<div class="feed-meta">
|
|
<span class="feed-time">{{ item.timestamp }}</span>
|
|
<span class="feed-status {{ item.status | lower }}"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab Panel: Opportunities -->
|
|
<div class="tab-panel" id="tab-opportunities" role="tabpanel">
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<h2>Unmet Data Needs</h2>
|
|
<p>Strategic opportunities where additional data would unlock new capabilities. Click any card to see integration details.</p>
|
|
</div>
|
|
|
|
{% for opp in activity.data_opportunities %}
|
|
<div class="opportunity-card" onclick="toggleOpportunity('opp-{{ loop.index }}')">
|
|
<div class="opportunity-header">
|
|
<span class="priority-badge {{ opp.priority | lower }}">{{ opp.priority }}</span>
|
|
<span class="opportunity-title">{{ opp.title }}</span>
|
|
<svg class="opportunity-expand-icon" id="icon-opp-{{ loop.index }}" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M6 9l6 6 6-6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="opportunity-meta">
|
|
<div>
|
|
<span class="opportunity-meta-label">Requested by:</span>
|
|
<span class="opportunity-team-pills">
|
|
{% for team in opp.requested_by %}
|
|
<span class="team-pill">{{ team }}</span>
|
|
{% endfor %}
|
|
</span>
|
|
</div>
|
|
{% if opp.potential_processes %}
|
|
<div>
|
|
<span class="opportunity-meta-label">Related:</span>
|
|
{{ opp.potential_processes | join(', ') }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="opportunity-business-case">{{ opp.business_case }}</div>
|
|
|
|
<!-- Expandable detail panel -->
|
|
<div class="opportunity-detail" id="opp-{{ loop.index }}" onclick="event.stopPropagation()">
|
|
|
|
{% if opp.mermaid %}
|
|
<div class="opp-detail-section">
|
|
<div class="opp-detail-label">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/></svg>
|
|
Data Integration Map
|
|
</div>
|
|
<div class="opp-mermaid-wrap">
|
|
<pre class="mermaid" data-opp-id="opp-{{ loop.index }}">{{ opp.mermaid }}</pre>
|
|
<div class="opp-diagram-legend">
|
|
<span class="opp-legend-item"><span class="opp-legend-dot existing"></span> Existing table</span>
|
|
<span class="opp-legend-item"><span class="opp-legend-dot new"></span> New data source</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="opp-detail-grid">
|
|
<!-- Left column: Integration + Join Keys -->
|
|
<div>
|
|
{% if opp.integration_description %}
|
|
<div class="opp-detail-section">
|
|
<div class="opp-detail-label">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
Integration Path
|
|
</div>
|
|
<div class="opp-detail-text">{{ opp.integration_description }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if opp.join_keys %}
|
|
<div class="opp-detail-section">
|
|
<div class="opp-detail-label">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
|
|
Join Keys
|
|
</div>
|
|
<table class="opp-join-table">
|
|
<thead>
|
|
<tr>
|
|
<th>New Table</th>
|
|
<th></th>
|
|
<th>Column</th>
|
|
<th></th>
|
|
<th>Existing Table</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for jk in opp.join_keys %}
|
|
<tr>
|
|
<td><span class="tbl-new">{{ jk.new_table }}</span></td>
|
|
<td class="opp-join-arrow">.</td>
|
|
<td><span class="col-name">{{ jk.column }}</span></td>
|
|
<td class="opp-join-arrow">→</td>
|
|
<td><span class="tbl-existing">{{ jk.existing_table }}</span></td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Right column: Team Impact + Enabled Queries -->
|
|
<div>
|
|
{% if opp.beneficiaries %}
|
|
<div class="opp-detail-section">
|
|
<div class="opp-detail-label">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
|
|
Team Impact
|
|
</div>
|
|
{% for ben in opp.beneficiaries %}
|
|
<div class="opp-beneficiary">
|
|
<span class="opp-beneficiary-team">{{ ben.team }}</span>
|
|
<span class="opp-beneficiary-impact">{{ ben.impact }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if opp.enabled_queries %}
|
|
<div class="opp-detail-section">
|
|
<div class="opp-detail-label">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
Enabled Queries
|
|
</div>
|
|
{% for q in opp.enabled_queries %}
|
|
<div class="opp-query-example">{{ q }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="page-footer">
|
|
{{ config.INSTANCE_COPYRIGHT or 'AI Data Analyst' }} | Activity Center
|
|
</footer>
|
|
</div>
|
|
|
|
<script>
|
|
// Tab switching with URL hash support
|
|
function switchTab(tabId, btn) {
|
|
// Deactivate all tabs and panels
|
|
document.querySelectorAll('.tab-btn').forEach(function(b) {
|
|
b.classList.remove('active');
|
|
b.setAttribute('aria-selected', 'false');
|
|
});
|
|
document.querySelectorAll('.tab-panel').forEach(function(p) {
|
|
p.classList.remove('active');
|
|
});
|
|
|
|
// Activate selected tab and panel
|
|
btn.classList.add('active');
|
|
btn.setAttribute('aria-selected', 'true');
|
|
document.getElementById('tab-' + tabId).classList.add('active');
|
|
|
|
// Update URL hash without scrolling
|
|
history.replaceState(null, null, '#' + tabId);
|
|
}
|
|
|
|
// Restore tab from URL hash on page load
|
|
(function() {
|
|
var hash = window.location.hash.replace('#', '');
|
|
var validTabs = ['overview', 'processes', 'teams', 'activity', 'opportunities'];
|
|
if (hash && validTabs.indexOf(hash) !== -1) {
|
|
var btn = document.querySelector('.tab-btn[data-tab="' + hash + '"]');
|
|
if (btn) {
|
|
switchTab(hash, btn);
|
|
}
|
|
}
|
|
})();
|
|
|
|
// Filter activity feed by time period
|
|
function filterActivity(period, btn) {
|
|
document.querySelectorAll('.feed-filter-btn').forEach(function(b) {
|
|
b.classList.remove('active');
|
|
});
|
|
btn.classList.add('active');
|
|
|
|
var items = document.querySelectorAll('.feed-item');
|
|
items.forEach(function(item, index) {
|
|
if (period === 'hour') {
|
|
item.style.display = (index < 5) ? 'flex' : 'none';
|
|
} else {
|
|
item.style.display = 'flex';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Toggle team accordion
|
|
function toggleTeam(teamId) {
|
|
var body = document.getElementById('body-' + teamId);
|
|
var chevron = document.getElementById('chevron-' + teamId);
|
|
|
|
if (body.classList.contains('visible')) {
|
|
body.classList.remove('visible');
|
|
chevron.classList.remove('expanded');
|
|
} else {
|
|
body.classList.add('visible');
|
|
chevron.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
// Scroll from leaderboard to team detail
|
|
function scrollToTeam(teamId) {
|
|
var element = document.getElementById(teamId);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
// Expand the team if not already expanded
|
|
var body = document.getElementById('body-' + teamId);
|
|
var chevron = document.getElementById('chevron-' + teamId);
|
|
if (body && !body.classList.contains('visible')) {
|
|
body.classList.add('visible');
|
|
if (chevron) {
|
|
chevron.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
// Brief highlight effect
|
|
element.style.transition = 'box-shadow 0.3s ease';
|
|
element.style.boxShadow = '0 0 0 2px var(--primary)';
|
|
setTimeout(function() {
|
|
element.style.boxShadow = '';
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Toggle process detail expansion
|
|
function toggleProcessDetail(processId) {
|
|
var detail = document.getElementById(processId);
|
|
var icon = document.getElementById('icon-' + processId);
|
|
|
|
if (detail.classList.contains('visible')) {
|
|
detail.classList.remove('visible');
|
|
if (icon) icon.classList.remove('expanded');
|
|
} else {
|
|
detail.classList.add('visible');
|
|
if (icon) icon.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
// Toggle opportunity detail with lazy Mermaid rendering
|
|
var renderedOpps = {};
|
|
function toggleOpportunity(oppId) {
|
|
var detail = document.getElementById(oppId);
|
|
var icon = document.getElementById('icon-' + oppId);
|
|
|
|
if (detail.classList.contains('visible')) {
|
|
detail.classList.remove('visible');
|
|
if (icon) icon.classList.remove('expanded');
|
|
} else {
|
|
detail.classList.add('visible');
|
|
if (icon) icon.classList.add('expanded');
|
|
|
|
// Lazy-render Mermaid diagram on first expand
|
|
if (!renderedOpps[oppId] && typeof mermaid !== 'undefined') {
|
|
var mermaidEl = detail.querySelector('.mermaid');
|
|
if (mermaidEl && !mermaidEl.getAttribute('data-processed')) {
|
|
mermaid.run({ nodes: [mermaidEl] });
|
|
renderedOpps[oppId] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Toggle overview category detail
|
|
function toggleOverviewCategory(catId) {
|
|
var body = document.getElementById('overview-body-' + catId);
|
|
var chevron = document.getElementById('overview-chevron-' + catId);
|
|
|
|
if (body.classList.contains('visible')) {
|
|
body.classList.remove('visible');
|
|
chevron.classList.remove('expanded');
|
|
} else {
|
|
body.classList.add('visible');
|
|
chevron.classList.add('expanded');
|
|
}
|
|
}
|
|
|
|
// Scroll from roadmap lane card to category detail
|
|
function scrollToOverviewCategory(catId) {
|
|
var element = document.getElementById('overview-cat-' + catId);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
// Expand if not already expanded
|
|
var body = document.getElementById('overview-body-' + catId);
|
|
var chevron = document.getElementById('overview-chevron-' + catId);
|
|
if (body && !body.classList.contains('visible')) {
|
|
body.classList.add('visible');
|
|
if (chevron) chevron.classList.add('expanded');
|
|
}
|
|
|
|
// Brief highlight
|
|
element.style.transition = 'box-shadow 0.3s ease';
|
|
element.style.boxShadow = '0 0 0 2px var(--primary)';
|
|
setTimeout(function() {
|
|
element.style.boxShadow = '';
|
|
}, 1500);
|
|
}
|
|
}
|
|
</script>
|
|
<!-- Mermaid.js for data integration diagrams -->
|
|
<script type="module">
|
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: 'base',
|
|
themeVariables: {
|
|
fontFamily: 'Inter, system-ui, sans-serif',
|
|
fontSize: '13px',
|
|
primaryColor: '#dbeafe',
|
|
primaryBorderColor: '#2563eb',
|
|
primaryTextColor: '#1e40af',
|
|
lineColor: '#94a3b8',
|
|
secondaryColor: '#f1f5f9',
|
|
tertiaryColor: '#f5f7fa',
|
|
edgeLabelBackground: '#f1f5f9',
|
|
},
|
|
flowchart: {
|
|
curve: 'basis',
|
|
padding: 12,
|
|
htmlLabels: true,
|
|
useMaxWidth: true,
|
|
},
|
|
});
|
|
// Expose mermaid globally for lazy rendering
|
|
window.mermaid = mermaid;
|
|
</script>
|
|
</body>
|
|
</html>
|