@@ -43,6 +43,7 @@ export class DentistryRecordComponent implements OnChanges {
4343 readonly SURFACE_CODE_VESTIBULAR = '62579006' ;
4444 readonly SURFACE_CODE_COMPLETE = '302214001' ;
4545 readonly SURFACE_CODE_PERIODONTAL = '8711009' ;
46+ private readonly TOOTH_ABSENT_FINDING_CODE = '234948008' ;
4647 private readonly FINDING_SITE_ATTRIBUTE_CODE = '363698007' ;
4748 private readonly FINDING_SITE_ATTRIBUTE_DISPLAY = 'Finding site (attribute)' ;
4849 private readonly PROCEDURE_SITE_ATTRIBUTE_CODE = '405813007' ;
@@ -59,6 +60,7 @@ export class DentistryRecordComponent implements OnChanges {
5960
6061 dentalFindingList : DentalFindingListItem [ ] = [ ] ;
6162 savedSurfaceByToothId : Record < string , Record < string , SurfaceVisualType > > = { } ;
63+ private absentToothIds = new Set < string > ( ) ;
6264
6365 readonly surfaceOptions : SnomedConceptOption [ ] = DENTAL_SURFACE_OPTIONS ;
6466 readonly findingOptions : SnomedConceptOption [ ] = DENTAL_FINDING_OPTIONS ;
@@ -79,6 +81,7 @@ export class DentistryRecordComponent implements OnChanges {
7981 readonly getLinePathsFn = ( tooth : OdontogramTooth ) => this . getLinePaths ( tooth ) ;
8082 readonly hasSurfaceVisualFn = ( toothId : string , surfaceCode : string ) => this . hasSurfaceVisual ( toothId , surfaceCode ) ;
8183 readonly getSurfaceVisualTypeFn = ( toothId : string , surfaceCode : string ) => this . getSurfaceVisualType ( toothId , surfaceCode ) ;
84+ readonly isToothAbsentFn = ( toothId : string ) => this . isToothAbsent ( toothId ) ;
8285 readonly getSurfaceOverlayClassFn = ( surfaceCode : string , tooth : OdontogramTooth , quadrantPrefix : string ) =>
8386 this . getSurfaceOverlayClass ( surfaceCode , tooth , quadrantPrefix ) ;
8487 readonly isSurfacePreviewFn = ( toothId : string , surfaceCode : string ) => this . isSurfacePreview ( toothId , surfaceCode ) ;
@@ -87,6 +90,7 @@ export class DentistryRecordComponent implements OnChanges {
8790 this . getSurfaceFill ( surfaceCode , tooth , quadrantPrefix ) ;
8891 readonly getSurfaceStrokeFn = ( surfaceCode : string ) => this . getSurfaceStroke ( surfaceCode ) ;
8992 readonly getSurfaceStrokeWidthFn = ( surfaceCode : string ) => this . getSurfaceStrokeWidth ( surfaceCode ) ;
93+ readonly getToothTooltipLinesFn = ( toothId : string ) => this . getToothTooltipLines ( toothId ) ;
9094
9195 constructor (
9296 private patientService : PatientService ,
@@ -175,6 +179,9 @@ export class DentistryRecordComponent implements OnChanges {
175179 }
176180
177181 hasSurfaceVisual ( toothId : string , surfaceCode : string ) : boolean {
182+ if ( this . isToothAbsent ( toothId ) ) {
183+ return false ;
184+ }
178185 return this . hasSavedSurface ( toothId , surfaceCode ) || this . isSurfacePreview ( toothId , surfaceCode ) ;
179186 }
180187
@@ -188,12 +195,19 @@ export class DentistryRecordComponent implements OnChanges {
188195 }
189196
190197 getSurfaceVisualType ( toothId : string , surfaceCode : string ) : SurfaceVisualType | null {
198+ if ( this . isToothAbsent ( toothId ) ) {
199+ return null ;
200+ }
191201 if ( this . isSurfacePreview ( toothId , surfaceCode ) ) {
192202 return this . getPinnedEntryType ( ) === 'procedure' ? 'procedure-planned' : 'finding' ;
193203 }
194204 return this . getSavedSurfaceVisualType ( toothId , surfaceCode ) ;
195205 }
196206
207+ isToothAbsent ( toothId : string ) : boolean {
208+ return this . absentToothIds . has ( toothId ) ;
209+ }
210+
197211 isPinnedSiteSelected ( siteCode : string ) : boolean {
198212 return this . getPinnedSiteCodes ( ) . includes ( siteCode ) ;
199213 }
@@ -992,6 +1006,7 @@ export class DentistryRecordComponent implements OnChanges {
9921006 } , { } as Record < string , BodyStructure > ) ;
9931007
9941008 this . savedSurfaceByToothId = { } ;
1009+ this . absentToothIds = new Set < string > ( ) ;
9951010
9961011 const conditionItems = conditions
9971012 . filter ( ( condition ) => this . isDentalCondition ( condition ) )
@@ -1011,8 +1026,14 @@ export class DentistryRecordComponent implements OnChanges {
10111026 const clinicalStatusCode = this . getConditionClinicalStatusCode ( condition ) ;
10121027 const clinicalStatusDisplay = this . getConditionClinicalStatusDisplay ( condition , clinicalStatusCode ) ;
10131028 const isResolved = clinicalStatusCode === 'resolved' ;
1029+ const findingCode = condition . code ?. coding ?. [ 0 ] ?. code || '' ;
1030+ const isToothAbsentFinding = findingCode === this . TOOTH_ABSENT_FINDING_CODE ;
10141031
1015- if ( ! isResolved ) {
1032+ if ( ! isResolved && toothId && isToothAbsentFinding ) {
1033+ this . absentToothIds . add ( toothId ) ;
1034+ }
1035+
1036+ if ( ! isResolved && ! isToothAbsentFinding ) {
10161037 siteCodes . forEach ( ( siteCode ) => {
10171038 if ( ! toothId ) {
10181039 return ;
@@ -1026,7 +1047,6 @@ export class DentistryRecordComponent implements OnChanges {
10261047 . map ( ( siteCode ) => this . surfaceOptions . find ( ( option ) => option . code === siteCode ) ?. display || siteCode )
10271048 . filter ( Boolean ) ;
10281049
1029- const findingCode = condition . code ?. coding ?. [ 0 ] ?. code || '' ;
10301050 const findingDisplay = condition . code ?. coding ?. [ 0 ] ?. display || condition . code ?. text || 'Not specified' ;
10311051
10321052 return {
@@ -1193,6 +1213,51 @@ export class DentistryRecordComponent implements OnChanges {
11931213 this . tooltipY = event . clientY - offset ;
11941214 }
11951215
1216+ private getToothTooltipLines ( toothId : string ) : string [ ] {
1217+ const items = this . dentalFindingList . filter ( ( item ) => item . toothId === toothId ) ;
1218+ if ( ! items . length ) {
1219+ return [ ] ;
1220+ }
1221+
1222+ const findingLabels = this . toUniqueCompactLabels (
1223+ items
1224+ . filter ( ( item ) => item . entryType === 'finding' && ! item . isResolved )
1225+ . map ( ( item ) => item . findingDisplay )
1226+ ) ;
1227+ const procedureLabels = this . toUniqueCompactLabels (
1228+ items
1229+ . filter ( ( item ) => item . entryType === 'procedure' )
1230+ . map ( ( item ) => `${ this . toTooltipProcedureName ( item . findingDisplay ) } (${ item . isResolved ? 'completed' : 'planned' } )` )
1231+ ) ;
1232+
1233+ const lines : string [ ] = [ ] ;
1234+ if ( findingLabels . length ) {
1235+ lines . push ( this . compactTooltipList ( findingLabels , 2 ) ) ;
1236+ }
1237+ if ( procedureLabels . length ) {
1238+ lines . push ( this . compactTooltipList ( procedureLabels , 2 ) ) ;
1239+ }
1240+ return lines ;
1241+ }
1242+
1243+ private toUniqueCompactLabels ( values : string [ ] ) : string [ ] {
1244+ const cleaned = values
1245+ . map ( ( value ) => ( value || '' ) . trim ( ) )
1246+ . filter ( ( value ) => ! ! value ) ;
1247+ return Array . from ( new Set ( cleaned ) ) ;
1248+ }
1249+
1250+ private toTooltipProcedureName ( display : string ) : string {
1251+ return ( display || '' ) . replace ( / \s * \( p r o c e d u r e \) \s * $ / i, '' ) . trim ( ) ;
1252+ }
1253+
1254+ private compactTooltipList ( values : string [ ] , maxItems : number ) : string {
1255+ if ( values . length <= maxItems ) {
1256+ return values . join ( ', ' ) ;
1257+ }
1258+ return `${ values . slice ( 0 , maxItems ) . join ( ', ' ) } +${ values . length - maxItems } ` ;
1259+ }
1260+
11961261 private clearToothDraft ( toothId : string ) : void {
11971262 delete this . toothDraftById [ toothId ] ;
11981263 delete this . findingQueryByToothId [ toothId ] ;
0 commit comments