diff --git a/Common-UI b/Common-UI index 6bb2b20..0c71b24 160000 --- a/Common-UI +++ b/Common-UI @@ -1 +1 @@ -Subproject commit 6bb2b204b7381300cb9d0436c8f57c3fe86cb0e0 +Subproject commit 0c71b24ce2567446a260c53017c7b304b77c5457 diff --git a/pom.xml b/pom.xml index b9fddb4..9202d94 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.tm-ui tm-ui - 3.4.0 + 3.5.0 TM-UI Piramal - tm: Module ui war diff --git a/src/app/app-modules/core/components/beneficiary-details/beneficiary-details.component.html b/src/app/app-modules/core/components/beneficiary-details/beneficiary-details.component.html index 3f01716..9d09575 100644 --- a/src/app/app-modules/core/components/beneficiary-details/beneficiary-details.component.html +++ b/src/app/app-modules/core/components/beneficiary-details/beneficiary-details.component.html @@ -19,34 +19,6 @@ {{ beneficiary?.beneficiaryName }} - - - - {{ current_language_set?.bendetails?.fatherName }}: - - - - {{ beneficiary?.fatherName }} - - - - - - {{ current_language_set?.bendetails?.lastName }}: - - - - {{ beneficiary?.lastName }} - - - - - {{ current_language_set?.bendetails?.phoneNo }}: - - - {{ beneficiary?.preferredPhoneNum }} - - {{ current_language_set?.bendetails?.gender }} / diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.html b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.html index ca914ed..baa741c 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.html +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.html @@ -22,12 +22,15 @@

{{ current_language_set?.casesheet?.provisionalDiag }}*

[matAutocomplete]="autoDiagnosis" (keyup)="onDiagnosisInputKeyup(diagnosisInput.value, i)" /> - - - {{ diag.term }} - - + + + {{ diag.term }} + + Loading… + End of results +
diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.ts b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.ts index 6f1bb21..737d02f 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.ts +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/general-opd-diagnosis/general-opd-diagnosis.component.ts @@ -48,6 +48,16 @@ export class GeneralOpdDiagnosisComponent { utils = new GeneralUtils(this.fb, this.sessionstorage); suggestedDiagnosisList: any = []; + private readonly PAGE_BASE = 0; + pageSize: number | undefined = undefined; + + private readonly BOOTSTRAP_MAX_PAGES = 3; // when first page can't scroll, prefill up to this many extra pages + + loadingMore: boolean[] = []; + noMore: boolean[] = []; + wantMore: boolean[] = []; + pageByIndex: number[] = []; + lastQueryByIndex: string[] = []; @Input() generalDiagnosisForm!: FormGroup; @@ -254,13 +264,22 @@ export class GeneralOpdDiagnosisComponent } onDiagnosisInputKeyup(value: string, index: number) { - if (value.length >= 3) { - this.masterdataService - .searchDiagnosisBasedOnPageNo(value, index) - .subscribe((results: any) => { - this.suggestedDiagnosisList[index] = results?.data?.sctMaster; - }); + const term = (value || '').trim(); + + if (term.length >= 3) { + if (this.lastQueryByIndex[index] !== term) { + this.lastQueryByIndex[index] = term; + this.pageByIndex[index] = 0; // logical 0th page + this.noMore[index] = false; + this.wantMore[index] = false; + this.suggestedDiagnosisList[index] = []; + } + this.fetchPage(index, false); } else { + this.lastQueryByIndex[index] = ''; + this.pageByIndex[index] = 0; + this.noMore[index] = false; + this.wantMore[index] = false; this.suggestedDiagnosisList[index] = []; } } @@ -272,7 +291,7 @@ export class GeneralOpdDiagnosisComponent onDiagnosisSelected(selected: any, index: number) { // this.patientQuickConsultForm.get(['provisionalDiagnosisList', index])?.setValue(selected); const diagnosisFormArray = this.generalDiagnosisForm.get( - 'provisionalDiagnosisList' + 'provisionalDiagnosisList', ) as FormArray; const diagnosisFormGroup = diagnosisFormArray.at(index) as FormGroup; @@ -283,4 +302,99 @@ export class GeneralOpdDiagnosisComponent term: selected?.term || null, }); } + + onPanelReady(index: number, panelEl: HTMLElement) { + if (panelEl.scrollHeight <= panelEl.clientHeight && !this.noMore[index]) { + } + } + + onAutoNearEnd(index: number) { + if (!this.loadingMore[index] && !this.noMore[index]) { + this.fetchPage(index, true); + } else if (this.loadingMore[index]) { + this.wantMore[index] = true; + } + } + + private bootstrapUntilScrollable(rowIndex: number, panelEl: HTMLElement) { + let fetched = 0; + + const tryFill = () => { + const scrollable = panelEl.scrollHeight > panelEl.clientHeight; + if ( + scrollable || + this.noMore[rowIndex] || + fetched >= this.BOOTSTRAP_MAX_PAGES + ) + return; + + if (this.loadingMore[rowIndex]) { + requestAnimationFrame(tryFill); + return; + } + + fetched++; + this.fetchPage(rowIndex, true); + + requestAnimationFrame(tryFill); + }; + + if (this.lastQueryByIndex[rowIndex]?.length >= 3) { + tryFill(); + } + } + + private fetchPage(index: number, append = false) { + const term = this.lastQueryByIndex[index]; + if (!term) return; + + const nextLogical = (this.pageByIndex[index] ?? 0) + (append ? 1 : 0); + const pageAtReq = nextLogical + this.PAGE_BASE; + + if (this.loadingMore[index]) return; + this.loadingMore[index] = true; + + const termAtReq = term; + + this.masterdataService + .searchDiagnosisBasedOnPageNo(termAtReq, pageAtReq) + .subscribe({ + next: (results: any) => { + if (this.lastQueryByIndex[index] !== termAtReq) return; + + const list = results?.data?.sctMaster ?? []; + + if (append) { + const existing = new Set( + (this.suggestedDiagnosisList[index] ?? []).map( + (d: any) => d.id ?? d.code ?? d.term, + ), + ); + this.suggestedDiagnosisList[index] = [ + ...(this.suggestedDiagnosisList[index] ?? []), + ...list.filter( + (d: any) => !existing.has(d.id ?? d.code ?? d.term), + ), + ]; + } else { + this.suggestedDiagnosisList[index] = list; + } + + this.pageByIndex[index] = nextLogical; + if (!list.length) { + this.noMore[index] = true; + } + }, + error: () => { + console.error('Error fetching diagnosis data'); + }, + complete: () => { + const wantChain = this.wantMore[index] && !this.noMore[index]; + this.loadingMore[index] = false; + this.wantMore[index] = false; + + if (wantChain) this.fetchPage(index, true); + }, + }); + } } diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.html b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.html index 207558c..a2b7b46 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.html +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.html @@ -133,12 +133,15 @@

{{ current_language_set?.casesheet?.provisionalDiag }}

[matAutocomplete]="autoDiagnosis" (keyup)="onDiagnosisInputKeyup(diagnosisInput.value, i)" /> - - - {{ diag.term }} - - + + + {{ diag.term }} + + Loading… + End of results +
diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.ts b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.ts index 0bc6ef3..b86fef5 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.ts +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-care-diagnosis/ncd-care-diagnosis.component.ts @@ -60,7 +60,16 @@ export class NcdCareDiagnosisComponent implements OnInit, DoCheck { attendantType: any; enableNCDCondition = false; suggestedDiagnosisList: any = []; + private readonly PAGE_BASE = 0; + pageSize: number | undefined = undefined; + private readonly BOOTSTRAP_MAX_PAGES = 3; // when first page can't scroll, prefill up to this many extra pages + + loadingMore: boolean[] = []; + noMore: boolean[] = []; + wantMore: boolean[] = []; + pageByIndex: number[] = []; + lastQueryByIndex: string[] = []; constructor( private fb: FormBuilder, @@ -270,13 +279,22 @@ export class NcdCareDiagnosisComponent implements OnInit, DoCheck { } onDiagnosisInputKeyup(value: string, index: number) { - if (value.length >= 3) { - this.masterdataService - .searchDiagnosisBasedOnPageNo(value, index) - .subscribe((results: any) => { - this.suggestedDiagnosisList[index] = results?.data?.sctMaster; - }); + const term = (value || '').trim(); + + if (term.length >= 3) { + if (this.lastQueryByIndex[index] !== term) { + this.lastQueryByIndex[index] = term; + this.pageByIndex[index] = 0; // logical 0th page + this.noMore[index] = false; + this.wantMore[index] = false; + this.suggestedDiagnosisList[index] = []; + } + this.fetchPage(index, false); } else { + this.lastQueryByIndex[index] = ''; + this.pageByIndex[index] = 0; + this.noMore[index] = false; + this.wantMore[index] = false; this.suggestedDiagnosisList[index] = []; } } @@ -288,7 +306,7 @@ export class NcdCareDiagnosisComponent implements OnInit, DoCheck { onDiagnosisSelected(selected: any, index: number) { // this.patientQuickConsultForm.get(['provisionalDiagnosisList', index])?.setValue(selected); const diagnosisFormArray = this.generalDiagnosisForm.get( - 'provisionalDiagnosisList' + 'provisionalDiagnosisList', ) as FormArray; const diagnosisFormGroup = diagnosisFormArray.at(index) as FormGroup; @@ -299,4 +317,100 @@ export class NcdCareDiagnosisComponent implements OnInit, DoCheck { term: selected?.term || null, }); } + + onPanelReady(index: number, panelEl: HTMLElement) { + if (panelEl.scrollHeight <= panelEl.clientHeight && !this.noMore[index]) { + this.bootstrapUntilScrollable(index, panelEl); + } + } + + onAutoNearEnd(index: number) { + if (!this.loadingMore[index] && !this.noMore[index]) { + this.fetchPage(index, true); + } else if (this.loadingMore[index]) { + this.wantMore[index] = true; + } + } + + private bootstrapUntilScrollable(rowIndex: number, panelEl: HTMLElement) { + let fetched = 0; + + const tryFill = () => { + const scrollable = panelEl.scrollHeight > panelEl.clientHeight; + if ( + scrollable || + this.noMore[rowIndex] || + fetched >= this.BOOTSTRAP_MAX_PAGES + ) + return; + + if (this.loadingMore[rowIndex]) { + requestAnimationFrame(tryFill); + return; + } + + fetched++; + this.fetchPage(rowIndex, true); + + requestAnimationFrame(tryFill); + }; + + if (this.lastQueryByIndex[rowIndex]?.length >= 3) { + tryFill(); + } + } + + private fetchPage(index: number, append = false) { + const term = this.lastQueryByIndex[index]; + if (!term) return; + + const nextLogical = (this.pageByIndex[index] ?? 0) + (append ? 1 : 0); + const pageAtReq = nextLogical + this.PAGE_BASE; + + if (this.loadingMore[index]) return; + this.loadingMore[index] = true; + + const termAtReq = term; + + this.masterdataService + .searchDiagnosisBasedOnPageNo(termAtReq, pageAtReq) + .subscribe({ + next: (results: any) => { + if (this.lastQueryByIndex[index] !== termAtReq) return; + + const list = results?.data?.sctMaster ?? []; + + if (append) { + const existing = new Set( + (this.suggestedDiagnosisList[index] ?? []).map( + (d: any) => d.id ?? d.code ?? d.term, + ), + ); + this.suggestedDiagnosisList[index] = [ + ...(this.suggestedDiagnosisList[index] ?? []), + ...list.filter( + (d: any) => !existing.has(d.id ?? d.code ?? d.term), + ), + ]; + } else { + this.suggestedDiagnosisList[index] = list; + } + + this.pageByIndex[index] = nextLogical; + if (!list.length) { + this.noMore[index] = true; + } + }, + error: () => { + console.error('Error fetching diagnosis data'); + }, + complete: () => { + const wantChain = this.wantMore[index] && !this.noMore[index]; + this.loadingMore[index] = false; + this.wantMore[index] = false; + + if (wantChain) this.fetchPage(index, true); + }, + }); + } } diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-screening-diagnosis/ncd-screening-diagnosis.component.html b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-screening-diagnosis/ncd-screening-diagnosis.component.html index bfdb32b..f341914 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-screening-diagnosis/ncd-screening-diagnosis.component.html +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/ncd-screening-diagnosis/ncd-screening-diagnosis.component.html @@ -18,7 +18,7 @@

" > -
+
{{ current_language_set?.casesheet?.provisionalDiag }} [matAutocomplete]="autoDiagnosis" (keyup)="onDiagnosisInputKeyup(diagnosisInput.value, i)" /> - - - {{ diag.term }} - - + + + {{ diag.term }} + + Loading… + End of results +
-
+
- -
+
+ +
@@ -75,40 +87,60 @@

{{ current_language_set?.common?.confirmDiagnosis }}

+ class="row m-t-20"> -
- +
+ + {{ + current_language_set?.common?.confirmDiagnosis + }} - search + maxlength="100" + required + #confirmDiagnosisInput + [matAutocomplete]="autoConfirmDiagnosis" + (keyup)=" + onDiagnosisInputKeyup( + 'confirmatory', + confirmDiagnosisInput.value, + i + ) + " /> + + + + {{ diag.term }} + + Loading… + End of results +
-
+

" (click)=" removeConfirmatoryDiagnosis(i, confirmatoryDiagnosis) - " - > + "> close
diff --git a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/pnc-diagnosis/pnc-diagnosis.component.ts b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/pnc-diagnosis/pnc-diagnosis.component.ts index e77880e..62e157e 100644 --- a/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/pnc-diagnosis/pnc-diagnosis.component.ts +++ b/src/app/app-modules/nurse-doctor/case-record/general-case-record/diagnosis/pnc-diagnosis/pnc-diagnosis.component.ts @@ -182,13 +182,11 @@ export class PncDiagnosisComponent ); } - getConfirmatoryDiagnosisList(): AbstractControl[] | null { - const confirmatoryDiagnosisListControl = this.generalDiagnosisForm.get( - 'confirmatoryDiagnosisList', + get confirmatoryDiagnosisControls(): AbstractControl[] { + return ( + (this.generalDiagnosisForm.get('confirmatoryDiagnosisList') as FormArray) + ?.controls || [] ); - return confirmatoryDiagnosisListControl instanceof FormArray - ? confirmatoryDiagnosisListControl.controls - : null; } beneficiaryDetailsSubscription: any; @@ -423,34 +421,179 @@ export class PncDiagnosisComponent } } - onDiagnosisInputKeyup(value: string, index: number) { - if (value.length >= 3) { - this.masterdataService - .searchDiagnosisBasedOnPageNo(value, index) - .subscribe((results: any) => { - this.suggestedDiagnosisList[index] = results?.data?.sctMaster; - }); - } else { - this.suggestedDiagnosisList[index] = []; - } + displayDiagnosis(diagnosis: any): string { + return typeof diagnosis === 'string' ? diagnosis : diagnosis?.term || ''; } - displayDiagnosis(diagnosis: any): string { + displayConfirmatoryDiagnosis(diagnosis: any): string { return typeof diagnosis === 'string' ? diagnosis : diagnosis?.term || ''; } - onDiagnosisSelected(selected: any, index: number) { - // this.patientQuickConsultForm.get(['provisionalDiagnosisList', index])?.setValue(selected); - const diagnosisFormArray = this.generalDiagnosisForm.get( - 'provisionalDiagnosisList' - ) as FormArray; - const diagnosisFormGroup = diagnosisFormArray.at(index) as FormGroup; - - // Set the nested and top-level fields - diagnosisFormGroup.patchValue({ - viewProvisionalDiagnosisProvided: selected, - conceptID: selected?.conceptID || null, - term: selected?.term || null, + // --- Shared scroll state for both provisional + confirmatory --- + private readonly PAGE_BASE = 0; + private readonly BOOTSTRAP_MAX_PAGES = 3; + + state: any = { + provisional: { + suggested: [] as any[][], + lastQueryByIndex: [] as string[], + pageByIndex: [] as number[], + loadingMore: [] as boolean[], + noMore: [] as boolean[], + wantMore: [] as boolean[], + }, + confirmatory: { + suggested: [] as any[][], + lastQueryByIndex: [] as string[], + pageByIndex: [] as number[], + loadingMore: [] as boolean[], + noMore: [] as boolean[], + wantMore: [] as boolean[], + }, + }; + + // --- Keyup handler (shared) --- + onDiagnosisInputKeyup( + type: 'provisional' | 'confirmatory', + value: string, + index: number, + ) { + const term = (value || '').trim(); + const s = this.state[type]; + + if (term.length >= 3) { + if (s.lastQueryByIndex[index] !== term) { + s.lastQueryByIndex[index] = term; + s.pageByIndex[index] = 0; + s.noMore[index] = false; + s.wantMore[index] = false; + s.suggested[index] = []; + } + this.fetchPage(type, index, false); + } else { + s.lastQueryByIndex[index] = ''; + s.pageByIndex[index] = 0; + s.noMore[index] = false; + s.wantMore[index] = false; + s.suggested[index] = []; + } + } + + // --- When user picks an option --- + onDiagnosisSelected( + type: 'provisional' | 'confirmatory', + selected: any, + index: number, + ) { + const controlName = + type === 'provisional' + ? 'provisionalDiagnosisList' + : 'confirmatoryDiagnosisList'; + const formArray = this.generalDiagnosisForm.get(controlName) as FormArray; + const fg = formArray.at(index) as FormGroup; + + fg.patchValue({ + [type === 'provisional' + ? 'viewProvisionalDiagnosisProvided' + : 'confirmatoryDiagnosis']: selected, + conceptID: selected?.conceptID ?? null, + term: selected?.term ?? null, }); } + + // --- Autocomplete scroll hooks --- + onPanelReady( + type: 'provisional' | 'confirmatory', + index: number, + panelEl: HTMLElement, + ) { + const s = this.state[type]; + if (panelEl.scrollHeight <= panelEl.clientHeight && !s.noMore[index]) { + this.bootstrapUntilScrollable(type, index, panelEl); + } + } + + onAutoNearEnd(type: 'provisional' | 'confirmatory', index: number) { + const s = this.state[type]; + if (!s.loadingMore[index] && !s.noMore[index]) { + this.fetchPage(type, index, true); + } else if (s.loadingMore[index]) { + s.wantMore[index] = true; + } + } + + private bootstrapUntilScrollable( + type: 'provisional' | 'confirmatory', + rowIndex: number, + panelEl: HTMLElement, + ) { + const s = this.state[type]; + let fetched = 0; + const tryFill = () => { + const scrollable = panelEl.scrollHeight > panelEl.clientHeight; + if ( + scrollable || + s.noMore[rowIndex] || + fetched >= this.BOOTSTRAP_MAX_PAGES + ) + return; + if (s.loadingMore[rowIndex]) { + requestAnimationFrame(tryFill); + return; + } + fetched++; + this.fetchPage(type, rowIndex, true); + requestAnimationFrame(tryFill); + }; + if (s.lastQueryByIndex[rowIndex]?.length >= 3) tryFill(); + } + + private fetchPage( + type: 'provisional' | 'confirmatory', + index: number, + append = false, + ) { + const s = this.state[type]; + const term = s.lastQueryByIndex[index]; + if (!term) return; + + const nextLogical = (s.pageByIndex[index] ?? 0) + (append ? 1 : 0); + const pageAtReq = nextLogical + this.PAGE_BASE; + if (s.loadingMore[index]) return; + s.loadingMore[index] = true; + + this.masterdataService + .searchDiagnosisBasedOnPageNo(term, pageAtReq) + .subscribe({ + next: (results: any) => { + if (s.lastQueryByIndex[index] !== term) return; + const list = results?.data?.sctMaster ?? []; + + if (append) { + const existing = new Set( + (s.suggested[index] ?? []).map( + (d: any) => d.id ?? d.code ?? d.term, + ), + ); + s.suggested[index] = [ + ...(s.suggested[index] ?? []), + ...list.filter( + (d: any) => !existing.has(d.id ?? d.code ?? d.term), + ), + ]; + } else { + s.suggested[index] = list; + } + + s.pageByIndex[index] = nextLogical; + if (!list.length) s.noMore[index] = true; + }, + complete: () => { + const wantChain = s.wantMore[index] && !s.noMore[index]; + s.loadingMore[index] = false; + s.wantMore[index] = false; + if (wantChain) this.fetchPage(type, index, true); + }, + }); + } } diff --git a/src/app/app-modules/nurse-doctor/nurse-doctor.module.ts b/src/app/app-modules/nurse-doctor/nurse-doctor.module.ts index 41c782c..b6f7aa6 100644 --- a/src/app/app-modules/nurse-doctor/nurse-doctor.module.ts +++ b/src/app/app-modules/nurse-doctor/nurse-doctor.module.ts @@ -160,6 +160,7 @@ import { GeneralCaseSheetComponent } from './case-sheet/general-case-sheet/gener import { HistoryCaseSheetComponent } from './case-sheet/general-case-sheet/history-case-sheet/history-case-sheet.component'; import { PncCaseSheetComponent } from './case-sheet/general-case-sheet/pnc-case-sheet/pnc-case-sheet.component'; import { SharedModule } from '../core/components/shared/shared.module'; +import { AutocompleteScrollerDirective } from './shared/utility/autocomplete-scroller.directive'; @NgModule({ imports: [ @@ -291,6 +292,7 @@ import { SharedModule } from '../core/components/shared/shared.module'; NurseMmuTmReferredWorklistComponent, DiseaseconfirmationComponent, CovidVaccinationStatusComponent, + AutocompleteScrollerDirective, ], providers: [ diff --git a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html index 81a8c18..ab43773 100644 --- a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html +++ b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.html @@ -622,88 +622,83 @@

-
+
-
- - - search + +
+ + {{ currentLanguageSet?.DiagnosisDetails?.provisionaldiagnosis }} + -
-
- - + + {{ diag.term }} + + Loading… + End of results +
+ + +
+ + +
diff --git a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.ts b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.ts index d7954d5..5ea53cb 100644 --- a/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.ts +++ b/src/app/app-modules/nurse-doctor/quick-consult/quick-consult.component.ts @@ -169,6 +169,17 @@ export class QuickConsultComponent dataSource = new MatTableDataSource(); + suggestedDiagnosisList: any = []; + private readonly PAGE_BASE = 0; + + private readonly BOOTSTRAP_MAX_PAGES = 3; // when first page can't scroll, prefill up to this many extra pages + + loadingMore: boolean[] = []; + noMore: boolean[] = []; + wantMore: boolean[] = []; + pageByIndex: number[] = []; + lastQueryByIndex: string[] = []; + constructor( private fb: FormBuilder, private doctorService: DoctorService, @@ -1294,35 +1305,38 @@ export class QuickConsultComponent } } - removeDiagnosisFromList( - index: any, - diagnosisList: AbstractControl, - ) { - const diagnosisListForm = this.patientQuickConsultForm.controls[ + deleteDiagnosis(index: any, diagnosisListForm: AbstractControl) { + const diagnosisListArray = this.patientQuickConsultForm.controls[ 'provisionalDiagnosisList' ] as FormArray; - if (!diagnosisListForm.at(index).invalid) { + if (diagnosisListArray.at(index).valid) { this.confirmationService .confirm(`warn`, this.currentLanguageSet.alerts.info.warn) - .subscribe((result) => { + .subscribe(result => { if (result) { - if (diagnosisListForm.length > 1) { - diagnosisListForm.removeAt(index); + if (diagnosisListArray.length > 1) { + diagnosisListArray.removeAt(index); } else { - diagnosisListForm.removeAt(index); - diagnosisListForm.push(this.utils.initProvisionalDiagnosisList()); + diagnosisListForm.reset(); + (diagnosisListForm as FormGroup).controls[ + 'viewProvisionalDiagnosisProvided' + ].enable(); } + this.patientQuickConsultForm.markAsDirty(); } }); } else { - if (diagnosisListForm.length > 1) { - diagnosisListForm.removeAt(index); + if (diagnosisListArray.length > 1) { + diagnosisListArray.removeAt(index); } else { - diagnosisListForm.removeAt(index); - diagnosisListForm.push(this.utils.initProvisionalDiagnosisList()); + diagnosisListForm.reset(); + (diagnosisListForm as FormGroup).controls[ + 'viewProvisionalDiagnosisProvided' + ].enable(); } } } + checkProvisionalDiagnosisValidity(provisionalDiagnosis: any) { const temp = provisionalDiagnosis.value; if (temp.term && temp.conceptID) { @@ -1351,4 +1365,139 @@ export class QuickConsultComponent } return false; } + + onDiagnosisInputKeyup(value: string, index: number) { + const term = (value || '').trim(); + + if (term.length >= 3) { + if (this.lastQueryByIndex[index] !== term) { + this.lastQueryByIndex[index] = term; + this.pageByIndex[index] = 0; // logical 0th page + this.noMore[index] = false; + this.wantMore[index] = false; + this.suggestedDiagnosisList[index] = []; + } + this.fetchPage(index, false); + } else { + this.lastQueryByIndex[index] = ''; + this.pageByIndex[index] = 0; + this.noMore[index] = false; + this.wantMore[index] = false; + this.suggestedDiagnosisList[index] = []; + } + } + + displayDiagnosis(diagnosis: any): string { + return typeof diagnosis === 'string' ? diagnosis : diagnosis?.term || ''; + } + + onDiagnosisSelected(selected: any, index: number) { + const diagnosisFormArray = this.patientQuickConsultForm.get( + 'provisionalDiagnosisList', + ) as FormArray; + const diagnosisFormGroup = diagnosisFormArray.at(index) as FormGroup; + + // Set the nested and top-level fields + diagnosisFormGroup.patchValue({ + provisionalDiagnosis: selected, + conceptID: selected?.conceptID || null, + term: selected?.term || null, + }); + } + + onPanelReady(index: number, panelEl: HTMLElement) { + if (panelEl.scrollHeight <= panelEl.clientHeight && !this.noMore[index]) { + this.bootstrapUntilScrollable(index, panelEl); + } + } + + onAutoNearEnd(index: number) { + if (!this.loadingMore[index] && !this.noMore[index]) { + this.fetchPage(index, true); + } else if (this.loadingMore[index]) { + this.wantMore[index] = true; + } + } + + private bootstrapUntilScrollable(rowIndex: number, panelEl: HTMLElement) { + let fetched = 0; + + const tryFill = () => { + const scrollable = panelEl.scrollHeight > panelEl.clientHeight; + if ( + scrollable || + this.noMore[rowIndex] || + fetched >= this.BOOTSTRAP_MAX_PAGES + ) + return; + + if (this.loadingMore[rowIndex]) { + requestAnimationFrame(tryFill); + return; + } + + fetched++; + this.fetchPage(rowIndex, true); + + requestAnimationFrame(tryFill); + }; + + if (this.lastQueryByIndex[rowIndex]?.length >= 3) { + tryFill(); + } + } + + private fetchPage(index: number, append = false) { + const term = this.lastQueryByIndex[index]; + if (!term) return; + + const nextLogical = (this.pageByIndex[index] ?? 0) + (append ? 1 : 0); + const pageAtReq = nextLogical + this.PAGE_BASE; + + if (this.loadingMore[index]) return; + this.loadingMore[index] = true; + + const termAtReq = term; + + this.masterdataService + .searchDiagnosisBasedOnPageNo(termAtReq, pageAtReq) + .subscribe({ + next: (results: any) => { + if (this.lastQueryByIndex[index] !== termAtReq) return; + + const list = results?.data?.sctMaster ?? []; + + if (append) { + const existing = new Set( + (this.suggestedDiagnosisList[index] ?? []).map( + (d: any) => d.id ?? d.code ?? d.term + ) + ); + this.suggestedDiagnosisList[index] = [ + ...(this.suggestedDiagnosisList[index] ?? []), + ...list.filter( + (d: any) => !existing.has(d.id ?? d.code ?? d.term) + ), + ]; + } else { + this.suggestedDiagnosisList[index] = list; + } + + this.pageByIndex[index] = nextLogical; + if (!list.length) { + this.noMore[index] = true; + } + }, + error: () => { + console.error('Error fetching diagnosis data'); + }, + complete: () => { + const wantChain = this.wantMore[index] && !this.noMore[index]; + this.loadingMore[index] = false; + this.wantMore[index] = false; + + if (wantChain) this.fetchPage(index, true); + }, + }); + } } diff --git a/src/app/app-modules/nurse-doctor/shared/utility/autocomplete-scroller.directive.ts b/src/app/app-modules/nurse-doctor/shared/utility/autocomplete-scroller.directive.ts new file mode 100644 index 0000000..629a996 --- /dev/null +++ b/src/app/app-modules/nurse-doctor/shared/utility/autocomplete-scroller.directive.ts @@ -0,0 +1,119 @@ +/* + * AMRIT – Accessible Medical Records via Integrated Technology + * Integrated EHR (Electronic Health Records) Solution + * + * Copyright (C) "Piramal Swasthya Management and Research Institute" + * + * This file is part of AMRIT. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ +import { + Directive, + EventEmitter, + NgZone, + OnDestroy, + AfterViewInit, + Output, + Input, +} from '@angular/core'; +import { MatAutocomplete } from '@angular/material/autocomplete'; +import { Subscription } from 'rxjs'; + +@Directive({ + selector: '[appAutocompleteScroller]', +}) +export class AutocompleteScrollerDirective implements AfterViewInit, OnDestroy { + @Input() threshold = 0.6; // 60% down + @Output() nearEnd = new EventEmitter(); + @Output() panelReady = new EventEmitter(); // lets component bootstrap if no overflow + + private subs: Subscription[] = []; + private scrollListener?: (e: Event) => void; + + constructor( + private ac: MatAutocomplete, + private ngZone: NgZone, + ) {} + + ngAfterViewInit(): void { + // Fired when the overlay panel opens + const openedSub = this.ac.opened.subscribe(() => { + this.waitForPanel((panelEl) => { + this.panelReady.emit(panelEl); // tell component the panel is live (for bootstrap) + + // attach scroll listener + this.ngZone.runOutsideAngular(() => { + this.scrollListener = () => { + if (panelEl.scrollHeight <= panelEl.clientHeight) return; // no overflow yet + const ratio = + (panelEl.scrollTop + panelEl.clientHeight) / panelEl.scrollHeight; + if (ratio >= this.threshold) { + // hop back into Angular so change detection works + this.ngZone.run(() => this.nearEnd.emit()); + } + }; + panelEl.addEventListener('scroll', this.scrollListener!, { + passive: true, + }); + }); + }); + }); + + // Fired when the panel closes + const closedSub = this.ac.closed.subscribe(() => { + const panelEl = this.getPanelEl(); + if (panelEl && this.scrollListener) { + panelEl.removeEventListener('scroll', this.scrollListener); + this.scrollListener = undefined; + } + }); + + this.subs.push(openedSub, closedSub); + } + + ngOnDestroy(): void { + this.subs.forEach((s) => s.unsubscribe()); + const panelEl = this.getPanelEl(); + if (panelEl && this.scrollListener) { + panelEl.removeEventListener('scroll', this.scrollListener); + this.scrollListener = undefined; + } + } + + private getPanelEl(): HTMLElement | null { + // Newer Material exposes panel element + const anyAc = this.ac as any; + const el = anyAc?.panel?.nativeElement as HTMLElement | null | undefined; + if (el) return el; + + // Fallback by id + const id = anyAc?.id as string | undefined; + return id ? (document.getElementById(id) as HTMLElement | null) : null; + } + + private waitForPanel(cb: (panelEl: HTMLElement) => void) { + let tries = 0; + const maxTries = 10; + const tick = () => { + const el = this.getPanelEl(); + if (el) { + cb(el); + return; + } + if (tries++ < maxTries) requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); + } +}