import { Component, inject, OnInit, signal, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { PatientsService } from '../../core/services/patients.service';
import { ReportsService } from '../../core/services/reports.service';
import { AuthService } from '../../core/services/auth.service';
import {ChatBotComponent} from '../chat/chat-bot.component' ;
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule, RouterLink, ChatBotComponent],
template: `
Total Patients
{{ stats()?.totalPatients ?? '—' }}
Total Encounters
{{ stats()?.totalEncounters ?? '—' }}
@for (s of statusStats(); track s._id) {
{{ s._id | titlecase }}
{{ s.count }}
}
Encounters by Month
@if (encountersByMonth().length) {
@for (m of encountersByMonth(); track m._id) {
}
} @else {
No data yet
}
Top ICD-10 Diagnoses
@if (topDiagnoses().length) {
@for (d of topDiagnoses().slice(0,6); track d._id; let i = $index) {
{{ i + 1 }}
{{ d._id }}
{{ d.description }}
{{ d.count }}
}
} @else {
No data yet
}
Top CPT Procedures
@if (topProcedures().length) {
@for (p of topProcedures().slice(0,6); track p._id; let i = $index) {
{{ i + 1 }}
{{ p._id }}
{{ p.description }}
{{ p.count }}
}
} @else {
No data yet
}
Quick Actions
`,
styles: [`
.dashboard { padding:2rem; max-width:1400px; font-family:'Inter',system-ui,sans-serif; }
.page-header { display:flex; align-items:flex-start; justify-content:space-between; margin-bottom:2rem; }
.page-header h1 { font-size:1.75rem; font-weight:700; color:#0f172a; margin:0 0 0.25rem; }
.page-header p { color:#64748b; margin:0; }
.header-actions { display:flex; gap:0.75rem; }
.btn-primary { padding:0.65rem 1.25rem; background:#0EA5E9; color:white; border-radius:8px; font-weight:600; font-size:0.9rem; text-decoration:none; border:none; cursor:pointer; }
.btn-primary:hover { background:#0284c7; }
.btn-outline { padding:0.6rem 1.25rem; background:white; color:#374151; border:1px solid #d1d5db; border-radius:8px; font-weight:600; font-size:0.9rem; text-decoration:none; }
.btn-outline:hover { border-color:#0EA5E9; color:#0EA5E9; }
.stats-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:1rem; margin-bottom:2rem; }
.stat-card { background:white; border-radius:12px; padding:1.25rem; display:flex; align-items:center; gap:1rem; box-shadow:0 1px 3px rgba(0,0,0,0.07); border:1px solid #f1f5f9; }
.stat-card.highlight { border-color:#e0f2fe; }
.stat-icon { width:44px; height:44px; border-radius:10px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.stat-icon svg { width:22px; height:22px; }
.stat-icon.blue { background:#eff6ff; color:#3b82f6; }
.stat-icon.green { background:#f0fdf4; color:#22c55e; }
.stat-icon.yellow { background:#fefce8; color:#eab308; }
.stat-icon.purple { background:#faf5ff; color:#a855f7; }
.stat-icon.teal { background:#f0fdfa; color:#14b8a6; }
.stat-body { display:flex; flex-direction:column; }
.stat-label { font-size:0.78rem; color:#64748b; font-weight:500; }
.stat-value { font-size:1.75rem; font-weight:700; color:#0f172a; line-height:1.1; }
.charts-row { display:grid; grid-template-columns:1.2fr 1fr 1fr; gap:1rem; margin-bottom:2rem; }
.chart-card { background:white; border-radius:12px; padding:1.25rem; border:1px solid #e2e8f0; }
.chart-title { font-size:0.875rem; font-weight:600; color:#374151; margin:0 0 1.25rem; }
.chart-empty { color:#94a3b8; font-size:0.85rem; text-align:center; padding:2rem 0; }
/* Bar chart */
.bar-chart { display:flex; align-items:flex-end; gap:0.5rem; height:140px; padding-bottom:1.5rem; position:relative; }
.bar-col { display:flex; flex-direction:column; align-items:center; flex:1; height:100%; }
.bar-wrap { flex:1; display:flex; align-items:flex-end; width:100%; }
.bar { width:100%; background:linear-gradient(to top,#0EA5E9,#38bdf8); border-radius:4px 4px 0 0; min-height:4px; position:relative; transition:height 0.3s; cursor:pointer; }
.bar:hover { background:linear-gradient(to top,#0284c7,#0EA5E9); }
.bar-val { position:absolute; top:-18px; left:50%; transform:translateX(-50%); font-size:0.65rem; font-weight:700; color:#0f172a; white-space:nowrap; }
.bar-label { font-size:0.65rem; color:#94a3b8; margin-top:0.25rem; white-space:nowrap; }
/* Rank list */
.rank-list { display:flex; flex-direction:column; gap:0.5rem; }
.rank-item { display:flex; align-items:center; gap:0.5rem; }
.rank-num { width:18px; font-size:0.72rem; font-weight:700; color:#94a3b8; text-align:right; flex-shrink:0; }
.rank-info { width:100px; flex-shrink:0; display:flex; flex-direction:column; gap:0.1rem; }
.rank-code { font-size:0.72rem; font-weight:700; padding:0.1rem 0.4rem; border-radius:4px; }
.rank-code.icd10 { background:#eff6ff; color:#1d4ed8; }
.rank-code.cpt { background:#f0fdf4; color:#15803d; }
.rank-desc { font-size:0.68rem; color:#64748b; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rank-bar-wrap { flex:1; height:6px; background:#f1f5f9; border-radius:3px; overflow:hidden; }
.rank-bar { height:100%; border-radius:3px; }
.rank-bar.icd10 { background:#3b82f6; }
.rank-bar.cpt { background:#10b981; }
.rank-count { width:24px; font-size:0.72rem; font-weight:700; color:#374151; text-align:right; flex-shrink:0; }
.section-title { font-size:1rem; font-weight:600; color:#374151; margin-bottom:1rem; }
.quick-actions { display:grid; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); gap:1rem; }
.action-card { background:white; border-radius:12px; padding:1.5rem; display:flex; align-items:center; gap:1.25rem; text-decoration:none; border:1px solid #e2e8f0; transition:border-color 0.15s,box-shadow 0.15s; }
.action-card:hover { border-color:#0EA5E9; box-shadow:0 0 0 3px rgba(14,165,233,0.1); }
.action-icon { width:48px; height:48px; border-radius:10px; display:flex; align-items:center; justify-content:center; flex-shrink:0; }
.action-icon svg { width:24px; height:24px; }
.action-icon.blue { background:#f0f9ff; color:#0EA5E9; }
.action-icon.green { background:#f0fdf4; color:#10b981; }
.action-icon.purple { background:#faf5ff; color:#8b5cf6; }
.action-card h3 { font-size:0.95rem; font-weight:600; color:#0f172a; margin:0 0 0.25rem; }
.action-card p { font-size:0.825rem; color:#64748b; margin:0; }
@media (max-width:900px) { .charts-row { grid-template-columns:1fr; } }
`],
})
export class DashboardComponent implements OnInit {
auth = inject(AuthService);
private patientsService = inject(PatientsService);
private reportsService = inject(ReportsService);
stats = signal(null);
statusStats = signal([]);
encountersByMonth = signal([]);
topDiagnoses = signal([]);
topProcedures = signal([]);
ngOnInit() {
this.patientsService.getStats().subscribe(data => {
this.stats.set(data);
this.statusStats.set(data.byStatus || []);
});
this.reportsService.getEncountersByMonth().subscribe(data => this.encountersByMonth.set(data || []));
this.reportsService.getTopDiagnoses(6).subscribe(data => this.topDiagnoses.set(data || []));
this.reportsService.getTopProcedures(6).subscribe(data => this.topProcedures.set(data || []));
}
statusColor(status: string) {
const map: Record = { draft: 'yellow', coded: 'blue', billed: 'purple', paid: 'teal' };
return map[status] || 'blue';
}
barHeight(count: number): number {
const max = Math.max(...this.encountersByMonth().map(m => m.count), 1);
return (count / max) * 100;
}
monthLabel(id: string): string {
if (!id) return '';
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const parts = id.split('-');
return months[parseInt(parts[1]) - 1] || id;
}
rankWidth(count: number, list: any[]): number {
const max = Math.max(...list.map(i => i.count), 1);
return (count / max) * 100;
}
}