import { Component, inject, signal, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { debounceTime, distinctUntilChanged, Subject, switchMap, of } from 'rxjs'; import { CodesService, CodeEntry, PagedResult } from '../../core/services/codes.service'; type Tab = 'all' | 'icd10' | 'cpt'; @Component({ selector: 'app-codes-search', standalone: true, imports: [CommonModule, FormsModule], template: `
@for (tab of tabs; track tab.id) { }
@if (loading()) {
Searching codes...
} @if (!loading() && activeTab() === 'all' && query) {
ICD-10 {{ globalResults()?.icd10?.length || 0 }} results
@for (code of globalResults()?.icd10; track code._id) {
{{ code.code }}

{{ code.description }}

@if (code.category) {

{{ code.category }}

}
{{ code.billable ? 'Billable' : 'Non-billable' }}
} @if (!globalResults()?.icd10?.length) {
No ICD-10 results
}
CPT {{ globalResults()?.cpt?.length || 0 }} results
@for (code of globalResults()?.cpt; track code._id) {
{{ code.code }}

{{ code.description }}

@if (code.category) {

{{ code.category }}

}
{{ code.billable ? 'Billable' : 'Non-billable' }}
} @if (!globalResults()?.cpt?.length) {
No CPT results
}
} @if (!loading() && activeTab() === 'icd10') {
@for (code of icd10Results()?.data; track code._id) {
{{ code.code }}

{{ code.description }}

@if (code.category) {

{{ code.category }}

}
{{ code.billable ? 'Billable' : 'Non-billable' }}
} @if (icd10Results()?.pages && icd10Results()!.pages > 1) { }
} @if (!loading() && activeTab() === 'cpt') {
@for (code of cptResults()?.data; track code._id) {
{{ code.code }}

{{ code.description }}

@if (code.category) {

{{ code.category }}

}
{{ code.billable ? 'Billable' : 'Non-billable' }}
} @if (cptResults()?.pages && cptResults()!.pages > 1) { }
} @if (!loading() && !query) {

Start searching

Enter a code like J18.9 or keyword like pneumonia

} @if (selectedCode()) { }
`, styles: [` .codes-page { padding:2rem; max-width:1200px; font-family:'Inter',system-ui,sans-serif; } .page-header { margin-bottom:1.5rem; } .page-header h1 { font-size:1.75rem; font-weight:700; color:#0f172a; margin:0 0 0.25rem; } .page-header p { color:#64748b; margin:0; } .search-section { margin-bottom:1.5rem; } .search-box { position:relative; margin-bottom:1rem; } .search-icon { position:absolute; left:1rem; top:50%; transform:translateY(-50%); width:20px; height:20px; color:#94a3b8; } .search-input { width:100%; padding:0.9rem 3rem; border:1px solid #e2e8f0; border-radius:12px; font-size:1rem; background:white; box-sizing:border-box; outline:none; transition:border-color 0.15s, box-shadow 0.15s; box-shadow:0 1px 3px rgba(0,0,0,0.06); } .search-input:focus { border-color:#0EA5E9; box-shadow:0 0 0 3px rgba(14,165,233,0.15); } .clear-btn { position:absolute; right:1rem; top:50%; transform:translateY(-50%); background:none; border:none; cursor:pointer; color:#94a3b8; font-size:1rem; } .tabs { display:flex; gap:0.5rem; } .tab { padding:0.5rem 1.25rem; border:1px solid #e2e8f0; border-radius:8px; background:white; font-size:0.875rem; font-weight:500; color:#64748b; cursor:pointer; display:flex; align-items:center; gap:0.5rem; transition:all 0.15s; } .tab.active { background:#0EA5E9; color:white; border-color:#0EA5E9; } .badge { background:rgba(255,255,255,0.3); border-radius:999px; padding:0.1rem 0.5rem; font-size:0.75rem; } .tab:not(.active) .badge { background:#e0f2fe; color:#0369a1; } .loading-state { display:flex; align-items:center; gap:0.75rem; padding:3rem; color:#64748b; justify-content:center; } .spinner { width:24px; height:24px; border:2px solid #e2e8f0; border-top-color:#0EA5E9; border-radius:50%; animation:spin 0.6s linear infinite; } @keyframes spin { to { transform:rotate(360deg); } } .results-split { display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; } .results-col { display:flex; flex-direction:column; gap:0.5rem; } .col-header { display:flex; align-items:center; gap:0.75rem; margin-bottom:0.5rem; } .col-count { font-size:0.8rem; color:#94a3b8; } .col-badge { padding:0.25rem 0.75rem; border-radius:999px; font-size:0.75rem; font-weight:700; } .col-badge.icd10 { background:#eff6ff; color:#1d4ed8; } .col-badge.cpt { background:#f0fdf4; color:#15803d; } .empty-col { color:#94a3b8; font-size:0.875rem; padding:1rem; text-align:center; background:white; border-radius:8px; border:1px dashed #e2e8f0; } .results-list { display:flex; flex-direction:column; gap:0.5rem; } .code-card { background:white; border:1px solid #e2e8f0; border-radius:10px; padding:1rem; display:flex; align-items:center; gap:1rem; cursor:pointer; transition:border-color 0.15s, box-shadow 0.15s; } .code-card:hover { border-color:#0EA5E9; box-shadow:0 0 0 3px rgba(14,165,233,0.08); } .code-tag { padding:0.35rem 0.75rem; border-radius:6px; font-size:0.85rem; font-weight:700; white-space:nowrap; font-family:'JetBrains Mono',monospace; } .code-tag.icd10 { background:#eff6ff; color:#1d4ed8; } .code-tag.cpt { background:#f0fdf4; color:#15803d; } .code-body { flex:1; min-width:0; } .code-desc { margin:0; font-size:0.9rem; color:#1e293b; font-weight:500; } .code-cat { margin:0.2rem 0 0; font-size:0.775rem; color:#94a3b8; } .code-badge { padding:0.2rem 0.6rem; border-radius:999px; font-size:0.7rem; font-weight:600; background:#f1f5f9; color:#64748b; white-space:nowrap; } .code-badge.billable { background:#f0fdf4; color:#16a34a; } .pagination { display:flex; align-items:center; justify-content:center; gap:1rem; padding:1rem; } .pagination button { padding:0.5rem 1rem; border:1px solid #e2e8f0; border-radius:6px; background:white; cursor:pointer; font-size:0.875rem; } .pagination button:disabled { opacity:0.4; cursor:not-allowed; } .pagination span { color:#64748b; font-size:0.875rem; } .empty-state { display:flex; flex-direction:column; align-items:center; padding:5rem 2rem; color:#94a3b8; } .empty-state svg { width:80px; height:80px; margin-bottom:1.5rem; } .empty-state h3 { font-size:1.1rem; color:#475569; margin:0 0 0.5rem; } .empty-state p { margin:0; font-size:0.9rem; text-align:center; } .empty-state strong { color:#475569; } .modal-overlay { position:fixed; inset:0; background:rgba(15,23,42,0.5); display:flex; align-items:center; justify-content:center; z-index:100; } .modal { background:white; border-radius:16px; width:100%; max-width:480px; box-shadow:0 25px 50px rgba(0,0,0,0.25); overflow:hidden; } .modal-header { display:flex; align-items:center; justify-content:space-between; padding:1.25rem 1.5rem; border-bottom:1px solid #f1f5f9; } .modal-close { background:none; border:none; cursor:pointer; color:#94a3b8; font-size:1.2rem; } .modal-body { padding:1.5rem; } .modal-body h2 { font-size:1.1rem; font-weight:600; color:#0f172a; margin:0 0 1.25rem; } .detail-row { display:flex; justify-content:space-between; padding:0.6rem 0; border-bottom:1px solid #f8fafc; font-size:0.875rem; } .detail-row span { color:#64748b; } .modal-footer { padding:1rem 1.5rem; border-top:1px solid #f1f5f9; display:flex; justify-content:flex-end; } .btn-copy { padding:0.6rem 1.5rem; background:#0EA5E9; color:white; border:none; border-radius:8px; font-weight:600; cursor:pointer; font-size:0.9rem; } .btn-copy:hover { background:#0284c7; } @media (max-width: 768px) { .results-split { grid-template-columns:1fr; } } `], }) export class CodesSearchComponent implements OnInit { private codesService = inject(CodesService); private search$ = new Subject(); query = ''; activeTab = signal('all'); loading = signal(false); globalResults = signal(null); icd10Results = signal | null>(null); cptResults = signal | null>(null); icd10Page = signal(1); cptPage = signal(1); selectedCode = signal(null); copied = signal(false); tabs = [ { id: 'all' as Tab, label: 'All' }, { id: 'icd10' as Tab, label: 'ICD-10' }, { id: 'cpt' as Tab, label: 'CPT' }, ]; ngOnInit() { this.search$.pipe( debounceTime(300), distinctUntilChanged(), switchMap(q => { if (!q.trim()) return of(null); this.loading.set(true); if (this.activeTab() === 'all') return this.codesService.globalSearch(q); if (this.activeTab() === 'icd10') return this.codesService.searchICD10(q, this.icd10Page()); return this.codesService.searchCPT(q, this.cptPage()); }), ).subscribe(res => { this.loading.set(false); if (!res) return; if (this.activeTab() === 'all') this.globalResults.set(res); else if (this.activeTab() === 'icd10') this.icd10Results.set(res as any); else this.cptResults.set(res as any); }); // Load all ICD-10 and CPT on startup for browsing this.codesService.searchICD10('', 1).subscribe(r => this.icd10Results.set(r)); this.codesService.searchCPT('', 1).subscribe(r => this.cptResults.set(r)); } onSearch(q: string) { this.search$.next(q); } switchTab(tab: Tab) { this.activeTab.set(tab); if (this.query) this.search$.next(this.query); } changePage(type: 'icd10' | 'cpt', delta: number) { if (type === 'icd10') { this.icd10Page.update(p => p + delta); this.codesService.searchICD10(this.query, this.icd10Page()).subscribe(r => this.icd10Results.set(r)); } else { this.cptPage.update(p => p + delta); this.codesService.searchCPT(this.query, this.cptPage()).subscribe(r => this.cptResults.set(r)); } } selectCode(code: CodeEntry) { this.selectedCode.set(code); } clear() { this.query = ''; this.globalResults.set(null); this.icd10Results.set(null); this.cptResults.set(null); } copyCode(code: string) { navigator.clipboard.writeText(code); this.copied.set(true); setTimeout(() => this.copied.set(false), 2000); } }