Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
"@jsverse/transloco": "^7.5.1",
"ag-grid-angular": "^33.1.1",
"ag-grid-community": "^33.1.1",
"chart.js": "^4.5.0",
"chartjs-plugin-annotation": "^3.1.0",
"chartjs-plugin-zoom": "^2.2.0",
"crypto-es": "^2.1.0",
"cspell": "^8.17.3",
"file-saver": "^2.0.5",
Expand Down
53 changes: 53 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/@seed/api/dataset/dataset.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type DataMappingRow = {
omit?: boolean; // optional, used for omitting columns
isExtraData?: boolean; // used internally, not part of the API
isNewColumn?: boolean; // used internally, not part of the API
hasDuplicate?: boolean; // used internally, not part of the API
}

export type MappedData = {
Expand Down
46 changes: 46 additions & 0 deletions src/@seed/api/filter-group/filter-group.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import { BehaviorSubject, catchError, map, take, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { naturalSort } from '@seed/utils'
import { UserService } from '../user'
import type { FilterGroup, FilterGroupInventoryType, FilterGroupsResponse } from './filter-group.types'

@Injectable({ providedIn: 'root' })
export class FilterGroupService {
private _errorService = inject(ErrorService)
private _filterGroups = new BehaviorSubject<FilterGroup[]>([])
private _httpClient = inject(HttpClient)
private _userService = inject(UserService)

filterGroups$ = this._filterGroups.asObservable()

constructor() {
this._userService.currentOrganizationId$
.pipe(
tap((orgId) => {
this.list(orgId, 'Property')
}),
)
.subscribe()
}

list(orgId: number, inventoryType: FilterGroupInventoryType = 'Property') {
const url = `/api/v3/filter_groups/?organization_id=${orgId}&inventory_type=${inventoryType}`
this._httpClient
.get<FilterGroupsResponse>(url)
.pipe(
take(1),
map(({ data }) => {
const filterGroups = data.toSorted((a, b) => naturalSort(a.name, b.name))
this._filterGroups.next(filterGroups)
return filterGroups
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching filter groups')
}),
)
.subscribe()
}
}
15 changes: 15 additions & 0 deletions src/@seed/api/filter-group/filter-group.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type FilterGroupInventoryType = 'Property' | 'Tax Lot'

// TODO there are more fields returned that could be added here
export type FilterGroup = {
id: number;
name: string;
organization_id: number;
inventory_type: FilterGroupInventoryType;
}

// TODO this has unhandled pagination
export type FilterGroupsResponse = {
status: string;
data: FilterGroup[];
}
2 changes: 2 additions & 0 deletions src/@seed/api/filter-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './filter-group.service'
export * from './filter-group.types'
2 changes: 2 additions & 0 deletions src/@seed/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './data-quality'
export * from './dataset'
export * from './derived-column'
export * from './geocode'
export * from './filter-group'
export * from './groups'
export * from './inventory'
export * from './label'
Expand All @@ -18,6 +19,7 @@ export * from './notes'
export * from './organization'
export * from './pairing'
export * from './postoffice'
export * from './program'
export * from './progress'
export * from './salesforce'
export * from './scenario'
Expand Down
17 changes: 17 additions & 0 deletions src/@seed/api/organization/organization.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type OrganizationUserSettings = {
profile?: UserSettingsProfiles;
crossCycles?: UserSettingsCrossCycles;
labels?: UserLabelSettings;
insights?: InsightsUserSettings;
}

type UserSettingsFilters = {
Expand All @@ -131,6 +132,22 @@ type UserSettingsCrossCycles = {

type UserLabelSettings = { ids: number[]; operator: LabelOperator }

export type InsightDatasetVisibility = 'compliant' | 'non-compliant' | 'unknown' | 'whisker'

export type PropertyInsightsUserSettings = {
programId?: number | null;
cycleId?: number | null;
metricType?: 0 | 1 | null;
xAxisColumnId?: number | null;
accessLevel?: string | null;
accessLevelInstanceId?: number | null;
datasetVisibility?: InsightDatasetVisibility[];
}

export type InsightsUserSettings = {
propertyInsights?: PropertyInsightsUserSettings;
}

export type OrganizationUsersResponse = {
users: OrganizationUser[];
status: string;
Expand Down
2 changes: 2 additions & 0 deletions src/@seed/api/program/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './program.service'
export * from './program.types'
121 changes: 121 additions & 0 deletions src/@seed/api/program/program.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { HttpErrorResponse } from '@angular/common/http'
import { HttpClient } from '@angular/common/http'
import { inject, Injectable } from '@angular/core'
import type { Observable } from 'rxjs'
import { BehaviorSubject, catchError, map, tap } from 'rxjs'
import { ErrorService } from '@seed/services'
import { SnackBarService } from 'app/core/snack-bar/snack-bar.service'
import { UserService } from '../user'
import type { Program, ProgramData, ProgramResponse, ProgramsResponse, ProgramUpsertPayload } from './program.types'

@Injectable({ providedIn: 'root' })
export class ProgramService {
private _httpClient = inject(HttpClient)
private _programs = new BehaviorSubject<Program[]>([])
// private _programs = new ReplaySubject<Program[]>(1)
private _errorService = inject(ErrorService)
private _snackBar = inject(SnackBarService)
private _userService = inject(UserService)
programs$ = this._programs
orgId: number

constructor() {
this._userService.currentOrganizationId$
.pipe(
tap((orgId) => {
this.list(orgId)
}),
)
.subscribe()
}

list(orgId: number) {
const url = `/api/v3/compliance_metrics/?organization_id=${orgId}`
this._httpClient
.get<ProgramsResponse>(url)
.pipe(
map(({ compliance_metrics }) => {
this.programs$.next(compliance_metrics)
return compliance_metrics
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error fetching Programs')
}),
)
.subscribe()
}

create(orgId: number, data: ProgramUpsertPayload): Observable<ProgramResponse> {
const url = `/api/v3/compliance_metrics/?organization_id=${orgId}`
const payload = this._normalizePayload(data)
return this._httpClient.post<ProgramResponse>(url, payload).pipe(
tap(() => {
this.list(orgId)
this._snackBar.success('Successfully created Program')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error creating Program')
}),
)
}

update(orgId: number, programId: number, data: ProgramUpsertPayload): Observable<ProgramResponse> {
const url = `/api/v3/compliance_metrics/${programId}/?organization_id=${orgId}`
const payload = this._normalizePayload(data)
return this._httpClient.put<ProgramResponse>(url, payload).pipe(
tap(() => {
this.list(orgId)
this._snackBar.success('Successfully updated Program')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error updating Program')
}),
)
}

delete(orgId: number, programId: number): Observable<ProgramResponse> {
const url = `/api/v3/compliance_metrics/${programId}/?organization_id=${orgId}`
return this._httpClient.delete<ProgramResponse>(url).pipe(
tap(() => {
this.list(orgId)
this._snackBar.success('Successfully deleted Program')
}),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error deleting Program')
}),
)
}

evaluate(orgId: number, programId: number, aliId: number = null): Observable<ProgramData> {
let url = `/api/v3/compliance_metrics/${programId}/evaluate/?organization_id=${orgId}`
if (aliId) url += `&access_level_instance_id=${aliId}`

return this._httpClient.get<{ data: ProgramData }>(url).pipe(
map(({ data }) => data),
catchError((error: HttpErrorResponse) => {
return this._errorService.handleError(error, 'Error evaluating Program')
}),
)
}

private _normalizePayload(data: ProgramUpsertPayload): ProgramUpsertPayload {
const payload = { ...data } as ProgramUpsertPayload & Partial<Program>
delete payload.organization_id
delete payload.id
delete payload.energy_bool
delete payload.emission_bool

return {
...payload,
actual_emission_column: payload.actual_emission_column ?? null,
actual_energy_column: payload.actual_energy_column ?? null,
cycles: payload.cycles ?? [],
emission_metric_type: payload.emission_metric_type ?? '',
energy_metric_type: payload.energy_metric_type ?? '',
filter_group: payload.filter_group ?? null,
target_emission_column: payload.target_emission_column ?? null,
target_energy_column: payload.target_energy_column ?? null,
x_axis_columns: payload.x_axis_columns ?? [],
}
}
}
Loading