Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5b9705c
#525 Generalize BMS cell data model for config-driven counts
bracyw Feb 22, 2026
c68574c
#525 Extract BMS_CONFIG to own file to fix circular import
bracyw Feb 22, 2026
e959516
#527 Combine segment summary and heat map into unified rows
bracyw Feb 22, 2026
2ea9599
#527 Extract heatmap and overview components, style controls
bracyw Feb 22, 2026
34fd2a3
#527 Style heatmap cells and segment row controls
bracyw Feb 22, 2026
afc49d9
#527 Fix cell dialog numbering and live update
bracyw Feb 22, 2026
c508468
#527 Make segment row sizing fully dynamic
bracyw Feb 22, 2026
b904a10
#527 Port hex heatmap to segment detail page
bracyw Feb 22, 2026
bb09fff
#527 Widen dynamic sizing for 70-150% zoom range
bracyw Feb 22, 2026
4239d1c
#527 Center overview stats and reduce row height
bracyw Feb 22, 2026
b75ff62
#527 Refactor CellReading to single cell model
bracyw Feb 22, 2026
65bcd5c
#527 Revert BMS config to 26-cell counts
bracyw Feb 22, 2026
33a444e
#527 Remove cell pairing, one tile per CellReading
bracyw Feb 22, 2026
7d26125
#527 Shift hex offset to top row
bracyw Feb 22, 2026
4a2343c
#527 Reduce heatmap panel padding and row height
bracyw Feb 22, 2026
77d3b86
#527 Tighten segment row spacing
bracyw Feb 22, 2026
15c0048
#527 Improve cell selection glow and reduce row height
bracyw Feb 22, 2026
ea29fd7
#527 Add temperature view with merged double-hex tiles
bracyw Feb 22, 2026
0938719
#527 Add multi-cell selection with comparison dialog
bracyw Feb 22, 2026
bf9857c
#527 Add multi-cell selection with comparison dialog
bracyw Feb 22, 2026
91f3497
#527 Add temperature option to segment view selector
bracyw Feb 22, 2026
adb7861
#527 Expand therm group to individual columns in dialog
bracyw Feb 22, 2026
7059adc
#527 Sync row selectors with Set ALL Maps via effect
bracyw Feb 22, 2026
43f5671
#527 Unify default view from service globalView
bracyw Feb 22, 2026
3fc68c6
#527 Add AI-gen ack, CSS comments, and code comments
bracyw Mar 9, 2026
a7db85b
#527 Fix SelectDropdown init, HexTile Record maps, DisplayCell renaming
bracyw Mar 9, 2026
a84a6c7
#527 Extract SectionHeader, abstract SegmentOverview and CellView, ad…
bracyw Mar 9, 2026
b1cac8b
#527 Replace @HostListener with host object
bracyw Mar 9, 2026
5687a7e
#527 Manual adjustments to templates and styles
bracyw Mar 9, 2026
a7fee3b
#527 Update segment overview labels and add total voltage topic
bracyw Mar 10, 2026
70e647a
#527 fmt
bracyw Mar 10, 2026
af8a9b9
#527 Fix segment overview panel to consistent fixed width
bracyw Mar 10, 2026
616e5d0
#527 Refactor DisplayCell to readings[], selectedCells to Map, add se…
bracyw Mar 10, 2026
d402a98
#527 - add any selected function to cell service
bracyw Mar 10, 2026
c23e8a2
#527 - move dialog ref opening to heat map service
bracyw Mar 10, 2026
c390c97
#527 - ai-gen comment fix
bracyw Mar 10, 2026
8799201
#527 Abstract ConfigTableComponent from cell-view
bracyw Mar 10, 2026
3d4bcc2
#527 - delete unused cell-by-cell map, and depricated event
bracyw Mar 10, 2026
bfbead7
#527 - non dialog modal for cell view, fix reversed label for beta cells
bracyw Mar 10, 2026
3f602e5
#527 - fmt
bracyw Mar 10, 2026
43761da
#527 Extract reusable StatSummaryComponent from segment-overview
bracyw Mar 10, 2026
c739cba
#527 - segment overview abstraction fix
bracyw Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions angular-client/src/assets/icons/battery.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, input, OnInit, ViewChild } from '@angular/core';
import { Component, effect, input, ViewChild } from '@angular/core';
import { SelectChangeEvent, Select } from 'primeng/select';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';

Expand All @@ -19,8 +19,7 @@ export interface DropdownOption {
standalone: true,
imports: [Select, ReactiveFormsModule, FormsModule]
})
export class SelectDropdownComponent implements OnInit {
constructor() {}
export class SelectDropdownComponent {
options = input<DropdownOption[]>([
{
name: 'default',
Expand All @@ -37,13 +36,17 @@ export class SelectDropdownComponent implements OnInit {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ViewChild('dropdownRef') dropdownRef: any;

ngOnInit() {
if (this.defaultValue()) {
const defaultOption = this.options().find((option) => option.name === this.defaultValue());
if (defaultOption) {
this.selectedOption = defaultOption;
constructor() {
// React to defaultValue changes (e.g. from "Set ALL Maps" selector)
effect(() => {
const val = this.defaultValue();
if (val) {
const match = this.options().find((option) => option.name === val);
if (match) {
this.selectedOption = match;
}
}
}
});
}

handleChangedOption(changeEvent: SelectChangeEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,28 @@
mat-grid-tile {
overflow: visible !important; /* Allow dropdown to overflow */
}

/* ── Section header row ── */
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI gen ack and more comments

.section-header {
display: flex;
align-items: center;
padding: clamp(8px, 1.25vw, 24px) 0 clamp(4px, 0.63vw, 12px);
gap: clamp(6px, 0.94vw, 18px);
}

.section-title {
font-family: 'Roboto', sans-serif;
font-weight: 700;
font-size: clamp(18px, 2.66vw, 44px);
color: #efefef;
}

.section-title-right {
margin-left: auto;
}

.segment-rows {
display: flex;
flex-direction: column;
gap: clamp(2px, 0.35vw, 8px);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@
@if (isMobile) {
<!-- TODO: Add mobile display component -->
} @else {
<!-- TODO: Add components for the segment summary-->
<mat-grid-list cols="6" gutterSize="15px" rowHeight="1.5rem" style="margin-top: -20px">
<mat-grid-tile [colspan]="6" rowspan="3">
<bms-header style="width: 100%" [pageTitle]="this.windowSize < 1200 ? 'ACCU' : 'Accumulator'" />
</mat-grid-tile>
<mat-grid-tile [colspan]="6" rowspan="5">
<bms-at-a-glance style="height: 100%; width: 100%" />
</mat-grid-tile>
<mat-grid-tile [colspan]="windowSize > 1300 ? 1 : 2" [rowspan]="16">
<div style="display: flex; flex-direction: column; width: 100%; height: 100%; gap: 3%">
<acc-high-voltage style="width: 100%; min-height: 31%" />
<acc-low-voltage style="width: 100%; min-height: 31%" />
<acc-high-temp style="width: 100%; min-height: 32%" />
</div>
</mat-grid-tile>
@for (segment of segments; track segment) {
<mat-grid-tile [colspan]="windowSize > 1300 ? 1 : 2" rowspan="16">
<segment-summary [segmentNumber]="segment" style="height: 100%; width: 100%" />
</mat-grid-tile>
}
</mat-grid-list>

<!-- Section header with "Set ALL Maps" dropdown -->
<div class="section-header">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abstract section header to it's own re-usable component, with left and right title inputs and select drop down options (allow for multiple select dropdowns, via a list of selector configs.)

<span class="section-title">Cell-by-Cell Heat Map</span>
<select-dropdown
[options]="allSegSelectorConfig.options"
[placeholder]="allSegSelectorConfig.placeholder"
[defaultValue]="allSegSelectorConfig.defaultValue"
/>
<span class="section-title section-title-right">Segment Overview</span>
</div>

<!-- Unified segment rows -->
<div class="segment-rows">
@for (segment of segments; track segment) {
<mat-grid-tile colspan="6" rowspan="8">
<cell-by-cell-heat-map style="width: 100%; height: 100%" [currentSegment]="segment"></cell-by-cell-heat-map>
</mat-grid-tile>
<segment-row [segment]="segment" />
}
</mat-grid-list>
</div>
}
</div>
62 changes: 50 additions & 12 deletions angular-client/src/pages/bms-debug-page/bms-debug-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Component, HostListener } from '@angular/core';
import { Component, HostListener, inject, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { allSegments } from 'src/utils/bms.utils';
import { MatGridList, MatGridTile } from '@angular/material/grid-list';
import { BmsHeaderComponent } from './components/bms-header/bms-header.component';
import { BmsAtAGlanceComponent } from './components/bms-at-a-glance/bms-at-a-glance.component';
import { AccHighVoltageComponent } from './components/acc-high-voltage/acc-high-voltage.component';
import { AccLowVoltageComponent } from './components/acc-low-voltage/acc-low-voltage.component';
import { AccHighTempComponent } from './components/acc-high-temp/acc-high-temp.component';
import { SegmentSummaryComponent } from './components/segment-summary/segment-summary.component';
import { CellByCellHeatMapComponent } from './components/cell-by-cell-heat-map/cell-by-cell-heat-map.component';
import { SegmentRowComponent } from './components/segment-row/segment-row.component';
import { HeatMapService, HeatMapView } from 'src/services/heat-map.service';
import {
DropdownOption,
SelectorConfig,
SelectDropdownComponent
} from 'src/components/select-dropdown/select-dropdown.component';

const formatAllSelectorName = (name: string) => 'Set ALL Maps: ' + name;

@Component({
selector: 'app-bms-debug-page',
Expand All @@ -19,23 +24,56 @@ import { CellByCellHeatMapComponent } from './components/cell-by-cell-heat-map/c
MatGridTile,
BmsHeaderComponent,
BmsAtAGlanceComponent,
AccHighVoltageComponent,
AccLowVoltageComponent,
AccHighTempComponent,
SegmentSummaryComponent,
CellByCellHeatMapComponent
SegmentRowComponent,
SelectDropdownComponent
]
})
export class BmsDebugPageComponent {
export class BmsDebugPageComponent implements OnInit, OnDestroy {
private heatMapService = inject(HeatMapService);
private subscription?: Subscription;

time = new Date();
newRunIsLoading = false;
mobileThreshold = 768;
windowSize: number = window.innerWidth;
isMobile = window.innerWidth < this.mobileThreshold;
segments = allSegments;

/** "Set ALL Maps" dropdown — shown once at the section header level */
private allViewOptions: DropdownOption[] = [
{
name: formatAllSelectorName(HeatMapView.Voltage.toString()),
function: () => this.heatMapService.setAllSegViews(HeatMapView.Voltage)
},
{
name: formatAllSelectorName(HeatMapView.Balancing.toString()),
function: () => this.heatMapService.setAllSegViews(HeatMapView.Balancing)
},
{
name: formatAllSelectorName(HeatMapView.Temperature.toString()),
function: () => this.heatMapService.setAllSegViews(HeatMapView.Temperature)
}
];
allSegSelectorConfig: SelectorConfig = {
options: this.allViewOptions,
placeholder: 'Set ALL Maps'
};

constructor() {}

ngOnInit(): void {
this.subscription = this.heatMapService.globalView$.subscribe((view) => {
this.allSegSelectorConfig = {
...this.allSegSelectorConfig,
defaultValue: formatAllSelectorName(view)
};
});
}

ngOnDestroy(): void {
this.subscription?.unsubscribe();
}

@HostListener('window:resize', ['$event'])
onResize() {
this.isMobile = window.innerWidth <= this.mobileThreshold;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@
mat-grid-tile {
overflow: visible !important; /* Allow dropdown to overflow */
}

.segment-page-heatmap {
--hex-w: clamp(60px, 5.5vw, 85px);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comments with explanation for css

--hex-h: calc(var(--hex-w) * 1.155);
--hex-gap: 3px;
--row-offset: calc(var(--hex-w) / 2 + 3px);
margin: 0 auto;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@
<segment-at-a-glance style="width: 100%; height: 100%" [segmentNumber]="this.segmentId" />
</mat-grid-tile>
<mat-grid-tile colspan="6" rowspan="8">
<cell-by-cell-heat-map style="width: 100%; height: 100%" [currentSegment]="this.segmentId"></cell-by-cell-heat-map>
<info-background
style="width: 100%; height: 100%"
[title]="this.getHeatmapTitle()"
svgIcon="battery_charging_2"
[slicedLeftCorner]="true"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add option for percentage that the corner is sliced.

[selectorConfigs]="[this.currentSegmentSelectorConfig, this.allSegSelectorConfig]"
>
<div style="padding-top: 20px"></div>
<segment-heatmap class="segment-page-heatmap" [segment]="this.segmentId" />
</info-background>
</mat-grid-tile>
<mat-grid-tile colspan="3" rowspan="8">
<!-- Alpha -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { Component, HostListener, OnInit } from '@angular/core';
import { inject } from '@angular/core';
import { Component, HostListener, inject, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { allSegments, Chip, Segment } from 'src/utils/bms.utils';
import { MatGridList, MatGridTile } from '@angular/material/grid-list';
import { BmsHeaderComponent } from '../components/bms-header/bms-header.component';
import { SegmentAtAGlanceComponent } from '../components/segment-at-a-glance/segment-at-a-glance.component';
import { CellByCellHeatMapComponent } from '../components/cell-by-cell-heat-map/cell-by-cell-heat-map.component';
import { SegmentHeatmapComponent } from '../components/segment-heatmap/segment-heatmap.component';
import { ChipDiagnosticsComponent } from '../components/chip-diagnostics/chip-diagnostics.component';
import { ChipFaultsComponent } from '../components/chip-faults/chip-faults.component';
import { InfoBackgroundComponent } from '../../../components/info-background/info-background.component';
import { DropdownOption, SelectorConfig } from 'src/components/select-dropdown/select-dropdown.component';
import { HeatMapService, HeatMapView } from 'src/services/heat-map.service';

const formatAllSelectorName = (name: string) => {
return 'Set ALL Maps: ' + name;
};

@Component({
selector: 'bms-segment-view',
Expand All @@ -19,23 +26,84 @@ import { ChipFaultsComponent } from '../components/chip-faults/chip-faults.compo
MatGridTile,
BmsHeaderComponent,
SegmentAtAGlanceComponent,
CellByCellHeatMapComponent,
SegmentHeatmapComponent,
InfoBackgroundComponent,
ChipDiagnosticsComponent,
ChipFaultsComponent
]
})
export class BmsSegmentViewComponent implements OnInit {
export class BmsSegmentViewComponent implements OnInit, OnDestroy {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment

private readonly route = inject(ActivatedRoute);
private router = inject(Router);
private heatMapService = inject(HeatMapService);
private subscriptions: Subscription[] = [];
changeTitleSize = window.innerWidth < 1060;
segmentId!: Segment;
chipAlpha: Chip = Chip.Alpha;
chipBeta: Chip = Chip.Beta;

cellViewSelectOptions: DropdownOption[] = [
{
name: HeatMapView.Temperature.toString(),
function: () => {
this.heatMapService.setCurrentView(this.segmentId, HeatMapView.Temperature);
}
},
{
name: HeatMapView.Voltage.toString(),
function: () => {
this.heatMapService.setCurrentView(this.segmentId, HeatMapView.Voltage);
}
},
{
name: HeatMapView.Balancing.toString(),
function: () => {
this.heatMapService.setCurrentView(this.segmentId, HeatMapView.Balancing);
}
}
];

currentSegmentSelectorConfig: SelectorConfig = {
options: this.cellViewSelectOptions,
placeholder: 'Change View'
};

allSegSelectorConfig: SelectorConfig = {
options: this.cellViewSelectOptions.map((option) => ({
name: formatAllSelectorName(option.name),
function: () => {
this.heatMapService.setAllSegViews(option.name as HeatMapView);
}
})),
placeholder: 'Change ALL Segments'
};

ngOnInit(): void {
this.subscribeToSegmentID();
}

getHeatmapTitle(): string {
return 'Segment ' + (this.segmentId + 1) + ': Cell-by-Cell';
}

private subscribeToView(): void {
const viewSub = this.heatMapService.getCurrentView(this.segmentId);
if (viewSub) {
this.subscriptions.push(
viewSub.subscribe((view) => {
this.allSegSelectorConfig = {
...this.allSegSelectorConfig,
defaultValue: view !== undefined ? formatAllSelectorName(view.toString()) : 'Change ALL Segments'
};
this.currentSegmentSelectorConfig = {
...this.currentSegmentSelectorConfig,
defaultValue: view !== undefined ? view : 'Change View'
};
})
);
}
}

// Update view width
@HostListener('window:resize', ['$event'])
onResize() {
Expand All @@ -47,9 +115,14 @@ export class BmsSegmentViewComponent implements OnInit {
this.route.paramMap.subscribe((params) => {
const possibleSegId = Number(params.get('id')) - 1;
allSegments.indexOf(possibleSegId) !== -1 ? (this.segmentId = possibleSegId) : this.router.navigate(['bms']);
this.subscribeToView();
});
} else {
this.router.navigate(['bms']);
}
};

ngOnDestroy(): void {
this.subscriptions.forEach((s) => s.unsubscribe());
}
}
Loading