From bfc2ebf53f3383541b1073bee94ca47f6b1e372b Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:03:05 -0800 Subject: [PATCH 01/33] feat: add dashboard nav accordion --- .../dashboard/src/app/app.component.html | 29 +++++++++++++++++-- .../dashboard/src/app/app.component.ts | 13 ++++++++- .../dashboard/src/app/material.module.ts | 4 ++- .../climate-data/climate-data.module.ts | 7 ++++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/app.component.html b/apps/picsa-apps/dashboard/src/app/app.component.html index 64c995e17..257f24f67 100644 --- a/apps/picsa-apps/dashboard/src/app/app.component.html +++ b/apps/picsa-apps/dashboard/src/app/app.component.html @@ -26,11 +26,36 @@ - @for (link of navLinks; track link.href) { + @for (link of navLinks; track link.href) { @if(link.children){ + + + + + {{ link.label }} + + + @for(child of link.children || []; track $index){ + + {{ child.label }} + } + + } @else { + {{ link.label }} - } + } }
Global Admin
diff --git a/apps/picsa-apps/dashboard/src/app/app.component.ts b/apps/picsa-apps/dashboard/src/app/app.component.ts index 8e795648e..8fe88ab25 100644 --- a/apps/picsa-apps/dashboard/src/app/app.component.ts +++ b/apps/picsa-apps/dashboard/src/app/app.component.ts @@ -8,6 +8,7 @@ import { DashboardMaterialModule } from './material.module'; interface INavLink { label: string; href: string; + children?: INavLink[]; isActive?: boolean; } @@ -31,8 +32,18 @@ export class AppComponent implements AfterViewInit { href: '/resources', }, { - label: 'Climate Data', + label: 'Climate', href: '/climate-data', + children: [ + { + label: 'Station Data', + href: '/station', + }, + { + label: 'Forecasts', + href: '/forecasts', + }, + ], }, // { // label: 'Crop Information', diff --git a/apps/picsa-apps/dashboard/src/app/material.module.ts b/apps/picsa-apps/dashboard/src/app/material.module.ts index 8a3c8b562..80c145a1f 100644 --- a/apps/picsa-apps/dashboard/src/app/material.module.ts +++ b/apps/picsa-apps/dashboard/src/app/material.module.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; +import { MatExpansionModule } from '@angular/material/expansion'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -15,6 +16,7 @@ import { MatToolbarModule } from '@angular/material/toolbar'; const matModules = [ MatButtonModule, MatChipsModule, + MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, @@ -24,7 +26,7 @@ const matModules = [ MatStepperModule, MatTableModule, MatTabsModule, - MatToolbarModule + MatToolbarModule, ]; @NgModule({ diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts index 9b5eda51d..6540d6407 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts @@ -12,10 +12,15 @@ import { StationPageComponent } from './pages/station/station-page.component'; RouterModule.forChild([ { path: '', + redirectTo: 'station', + pathMatch: 'full', + }, + { + path: 'station', component: ClimateDataHomeComponent, }, { - path: ':stationId', + path: 'station/:stationId', component: StationPageComponent, }, ]), From 9743c95c20dbdb92e57aedf975900a757b7ca4f6 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:03:48 -0800 Subject: [PATCH 02/33] chore: code tidying --- .../station/components/rainfall-summary/rainfall-summary.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts index 9fe7655b5..e28923b66 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts @@ -56,14 +56,14 @@ export class RainfallSummaryComponent implements AfterViewInit { public async refreshData() { const { station_id, country_code } = this.service.activeStation; - const { response, data, error } = await this.api.useMeta('rainfallSummary').POST('/v1/annual_rainfall_summaries/', { + const { data, error } = await this.api.useMeta('rainfallSummary').POST('/v1/annual_rainfall_summaries/', { body: { country: `${country_code}` as any, station_id: `${station_id}`, summaries: ['annual_rain', 'start_rains', 'end_rains', 'end_season', 'seasonal_rain', 'seasonal_length'], }, }); - console.log('rainfallSummary', { response, data, error }); + if (error) throw error; this.loadData(data as any); // TODO - generalise way to persist db updates from api queries const dbRes = await this.supabase.db.table('climate_products').upsert({ From ece72c14dd117ff48313c16ca31f0b71c21ae044 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:04:33 -0800 Subject: [PATCH 03/33] style: dashboard climate api status spinner --- .../pages/home/climate-data-home.component.html | 10 ++++++++-- .../pages/home/climate-data-home.component.scss | 13 +++++++++++++ .../pages/home/climate-data-home.component.ts | 3 ++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html index 39cc37626..92c61a7be 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html @@ -3,8 +3,14 @@

Climate Data

@if(api.meta.serverStatus; as meta){
- Server Status - {{ meta.rawResponse?.status }} + Server Status + + @if(meta.rawResponse; as response){ + {{ response.status }} + } @else { + + } +
} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss index c620acfc0..69317ae3d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss @@ -1,3 +1,8 @@ +.server-status { + display: flex; + gap: 8px; + align-items: center; +} .status-code { color: white; padding: 8px; @@ -6,6 +11,14 @@ &[data-status='200'] { background: green; } + width: 32px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; +} +mat-progress-spinner.mat-white { + --mdc-circular-progress-active-indicator-color: white; } table.station-table { diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts index 094aa4b0c..77f87a464 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map'; @@ -10,7 +11,7 @@ import { ClimateDataApiService } from '../../climate-data-api.service'; @Component({ selector: 'dashboard-climate-data-home', standalone: true, - imports: [CommonModule, MatTableModule, RouterModule, PicsaMapComponent], + imports: [CommonModule, MatTableModule, RouterModule, PicsaMapComponent, MatProgressSpinnerModule], templateUrl: './climate-data-home.component.html', styleUrls: ['./climate-data-home.component.scss'], }) From 83b481844d9223c3e08f72e3b23e58318477b1f4 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:06:21 -0800 Subject: [PATCH 04/33] feat: integrate climate station list api --- .../climate-data/climate-data.service.ts | 25 ++- .../app/modules/climate-data/types/api.d.ts | 166 +++++++++++++++--- 2 files changed, 162 insertions(+), 29 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts index e71e8c69e..bf3a75d09 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts @@ -37,8 +37,9 @@ export class ClimateDataDashboardService extends PicsaAsyncService { public override async init() { await this.supabaseService.ready(); - await this.checkStatus(); await this.listStations(); + // Initialise other services without await to allow parallel requests + this.checkApiStatus(); this.subscribeToRouteChanges(); } @@ -54,30 +55,36 @@ export class ClimateDataDashboardService extends PicsaAsyncService { private subscribeToRouteChanges() { // Use merged router as service cannot access route params directly like component - ngRouterMergedSnapshot$(this.router).subscribe(({ params }) => { + ngRouterMergedSnapshot$(this.router).subscribe(async ({ params }) => { if (params.stationId) { + await this.ready(); this.setActiveStation(parseInt(params.stationId)); } }); } - private async checkStatus() { + private async checkApiStatus() { await this.api.useMeta('serverStatus').GET('/v1/status/'); } - private async listStations() { + private async listStations(allowRefresh = true) { // HACK - endpoint not operational // TODO - when running should refresh from server as cron task const { data, error } = await this.supabaseService.db.table('climate_stations').select<'*', IStationRow>('*'); if (error) { throw error; } - if (data.length === 0) { - this.notificationService.showUserNotification({ - matIcon: 'warning', - message: 'climate_stations_rows must be imported into database for this feature to work', - }); + if (data.length === 0 && allowRefresh) { + await this.refreshStationList(); + return this.listStations(false); } this.stations = data || []; } + private async refreshStationList() { + const { data, error } = await this.api.useMeta('station').GET('/v1/station/'); + if (error) throw error; + // TODO - general mapping doc + const stations: IStationRow[] = data; + await this.supabaseService.db.table('climate_stations').upsert(stations); + } } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts index 52a0bb695..ba4357a5b 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts @@ -32,19 +32,29 @@ export interface paths { /** Get Season Start Probabilities */ post: operations["get_season_start_probabilities_v1_season_start_probabilities__post"]; }; + "/v1/extremes_summaries/": { + /** Get Extremes Summaries */ + post: operations["get_extremes_summaries_v1_extremes_summaries__post"]; + }; "/v1/station/": { - /** - * Read Stations - * @description Retrieve stations. - */ + /** Read Stations */ get: operations["read_stations_v1_station__get"]; }; - "/v1/station/{id}": { - /** - * Read Station - * @description Get station by ID. - */ - get: operations["read_station_v1_station__id__get"]; + "/v1/station/{country}": { + /** Read Stations */ + get: operations["read_stations_v1_station__country__get"]; + }; + "/v1/station/{country}/{station_id}": { + /** Read Stations */ + get: operations["read_stations_v1_station__country___station_id__get"]; + }; + "/v1/forecasts/": { + /** Get Forecasts */ + get: operations["get_forecasts_v1_forecasts__get"]; + }; + "/v1/forecasts/{country}/{file_name}": { + /** Get Forecasts */ + get: operations["get_forecasts_v1_forecasts__country___file_name__get"]; }; } @@ -122,6 +132,29 @@ export interface components { /** Start Before Season */ start_before_season?: boolean; }; + /** ExtremesSummariesParameters */ + ExtremesSummariesParameters: { + /** + * Country + * @default zm + * @enum {string} + */ + country?: "zm" | "mw"; + /** + * Station Id + * @default test_1 + */ + station_id?: string; + /** + * Summaries + * @default [ + * "extremes_rain", + * "extremes_tmin", + * "extremes_tmax" + * ] + */ + summaries?: ("extremes_rain" | "extremes_tmin" | "extremes_tmax")[]; + }; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ @@ -165,6 +198,26 @@ export interface components { /** Start Dates */ start_dates?: number[]; }; + /** Station */ + Station: { + /** + * Country Code + * @enum {string} + */ + country_code: "zm" | "mw"; + /** District */ + district: string; + /** Elevation */ + elevation: number; + /** Latitude */ + latitude: number; + /** Longitude */ + longitude: number; + /** Station Id */ + station_id: number; + /** Station Name */ + station_name: string; + }; /** ValidationError */ ValidationError: { /** Location */ @@ -312,11 +365,46 @@ export interface operations { }; }; }; - /** - * Read Stations - * @description Retrieve stations. - */ + /** Get Extremes Summaries */ + get_extremes_summaries_v1_extremes_summaries__post: { + requestBody: { + content: { + "application/json": components["schemas"]["ExtremesSummariesParameters"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Read Stations */ read_stations_v1_station__get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["Station"][]; + }; + }; + }; + }; + /** Read Stations */ + read_stations_v1_station__country__get: { + parameters: { + path: { + country: "zm" | "mw"; + }; + }; responses: { /** @description Successful Response */ 200: { @@ -324,16 +412,54 @@ export interface operations { "application/json": unknown; }; }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; }; }; - /** - * Read Station - * @description Get station by ID. - */ - read_station_v1_station__id__get: { + /** Read Stations */ + read_stations_v1_station__country___station_id__get: { + parameters: { + path: { + country: "zm" | "mw"; + station_id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Get Forecasts */ + get_forecasts_v1_forecasts__get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** Get Forecasts */ + get_forecasts_v1_forecasts__country___file_name__get: { parameters: { path: { - id: number; + country: "zm" | "mw"; + file_name: string; }; }; responses: { From 0a740fc195c2d4927ceb17973650ee60bfb50ef7 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:16:17 -0800 Subject: [PATCH 05/33] chore: station-data rename --- .../modules/{climate-data => climate}/climate-data-api.service.ts | 0 .../app/modules/{climate-data => climate}/climate-data.module.ts | 0 .../app/modules/{climate-data => climate}/climate-data.service.ts | 0 .../pages/home/climate-data-home.component.html | 0 .../pages/home/climate-data-home.component.scss | 0 .../pages/home/climate-data-home.component.spec.ts | 0 .../pages/home/climate-data-home.component.ts | 0 .../station/components/rainfall-summary/rainfall-summary.html | 0 .../station/components/rainfall-summary/rainfall-summary.scss | 0 .../station/components/rainfall-summary/rainfall-summary.spec.ts | 0 .../pages/station/components/rainfall-summary/rainfall-summary.ts | 0 .../pages/station/station-page.component.html | 0 .../pages/station/station-page.component.scss | 0 .../pages/station/station-page.component.spec.ts | 0 .../pages/station/station-page.component.ts | 0 .../src/app/modules/{climate-data => climate}/types/README.md | 0 .../src/app/modules/{climate-data => climate}/types/api.d.ts | 0 17 files changed, 0 insertions(+), 0 deletions(-) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/climate-data-api.service.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/climate-data.module.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/climate-data.service.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/home/climate-data-home.component.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/home/climate-data-home.component.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/home/climate-data-home.component.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/home/climate-data-home.component.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/components/rainfall-summary/rainfall-summary.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/components/rainfall-summary/rainfall-summary.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/components/rainfall-summary/rainfall-summary.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/components/rainfall-summary/rainfall-summary.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/station-page.component.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/station-page.component.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/station-page.component.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/pages/station/station-page.component.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/types/README.md (100%) rename apps/picsa-apps/dashboard/src/app/modules/{climate-data => climate}/types/api.d.ts (100%) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data-api.service.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data-api.service.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate-data-api.service.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.module.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.module.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.module.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.service.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/climate-data.service.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.service.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/home/climate-data-home.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/components/rainfall-summary/rainfall-summary.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/pages/station/station-page.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/README.md b/apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/types/README.md rename to apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate-data/types/api.d.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts From a2d4cb99700a7fc6b5195a4cadcd75f84128a7fd Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:17:25 -0800 Subject: [PATCH 06/33] chore: service rename --- .../{climate-data-api.service.ts => climate-api.service.ts} | 0 .../modules/climate/{climate-data.module.ts => climate.module.ts} | 0 .../climate/{climate-data.service.ts => climate.service.ts} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename apps/picsa-apps/dashboard/src/app/modules/climate/{climate-data-api.service.ts => climate-api.service.ts} (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/{climate-data.module.ts => climate.module.ts} (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/{climate-data.service.ts => climate.service.ts} (100%) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/climate-data-api.service.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.module.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/climate-data.service.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts From fdc7ee9be8bf439b29d741744d4bee9ab0a975ce Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:20:23 -0800 Subject: [PATCH 07/33] chore: code tidying --- apps/picsa-apps/dashboard/src/app/app.component.ts | 2 +- apps/picsa-apps/dashboard/src/app/app.routes.ts | 4 ++-- .../src/app/modules/climate/climate-api.service.ts | 2 +- .../dashboard/src/app/modules/climate/climate.module.ts | 2 +- .../dashboard/src/app/modules/climate/climate.service.ts | 6 +++--- .../climate/pages/home/climate-data-home.component.ts | 6 +++--- .../components/rainfall-summary/rainfall-summary.ts | 8 ++++---- .../climate/pages/station/station-page.component.ts | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/app.component.ts b/apps/picsa-apps/dashboard/src/app/app.component.ts index 8fe88ab25..031595a2d 100644 --- a/apps/picsa-apps/dashboard/src/app/app.component.ts +++ b/apps/picsa-apps/dashboard/src/app/app.component.ts @@ -33,7 +33,7 @@ export class AppComponent implements AfterViewInit { }, { label: 'Climate', - href: '/climate-data', + href: '/climate', children: [ { label: 'Station Data', diff --git a/apps/picsa-apps/dashboard/src/app/app.routes.ts b/apps/picsa-apps/dashboard/src/app/app.routes.ts index c49395830..c4f1845db 100644 --- a/apps/picsa-apps/dashboard/src/app/app.routes.ts +++ b/apps/picsa-apps/dashboard/src/app/app.routes.ts @@ -6,8 +6,8 @@ export const appRoutes: Route[] = [ loadChildren: () => import('./modules/resources/resources.module').then((m) => m.ResourcesPageModule), }, { - path: 'climate-data', - loadChildren: () => import('./modules/climate-data/climate-data.module').then((m) => m.ClimateDataModule), + path: 'climate', + loadChildren: () => import('./modules/climate/climate.module').then((m) => m.ClimateModule), }, { path: 'translations', diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts index 414381324..096579cf5 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts @@ -35,7 +35,7 @@ interface IMetaEntry{ * ``` * */ @Injectable({ providedIn: 'root' }) -export class ClimateDataApiService { +export class ClimateApiService { /** Request additional meta by id */ public meta:Record={} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts index 6540d6407..72914f0b7 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts @@ -26,4 +26,4 @@ import { StationPageComponent } from './pages/station/station-page.component'; ]), ], }) -export class ClimateDataModule {} +export class ClimateModule {} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts index bf3a75d09..56e953827 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts @@ -8,7 +8,7 @@ import { SupabaseService } from '@picsa/shared/services/core/supabase'; import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/supabase-storage.service'; import { ngRouterMergedSnapshot$ } from '@picsa/utils/angular'; -import { ClimateDataApiService } from './climate-data-api.service'; +import { ClimateApiService } from './climate-api.service'; export type IStationRow = Database['public']['Tables']['climate_stations']['Row']; @@ -20,14 +20,14 @@ export interface IResourceStorageEntry extends IStorageEntry { export type IResourceEntry = Database['public']['Tables']['resources']['Row']; @Injectable({ providedIn: 'root' }) -export class ClimateDataDashboardService extends PicsaAsyncService { +export class ClimateService extends PicsaAsyncService { public apiStatus: number; public stations: IStationRow[] = []; public activeStation: IStationRow; constructor( private supabaseService: SupabaseService, - private api: ClimateDataApiService, + private api: ClimateApiService, private notificationService: PicsaNotificationService, private router: Router ) { diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts index 77f87a464..d084a296d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts @@ -5,8 +5,8 @@ import { MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map'; -import { ClimateDataDashboardService, IStationRow } from '../../climate-data.service'; -import { ClimateDataApiService } from '../../climate-data-api.service'; +import { ClimateService, IStationRow } from '../../climate.service'; +import { ClimateApiService } from '../../climate-api.service'; @Component({ selector: 'dashboard-climate-data-home', @@ -20,7 +20,7 @@ export class ClimateDataHomeComponent implements OnInit { public mapMarkers: IMapMarker[]; - constructor(public service: ClimateDataDashboardService, public api: ClimateDataApiService) {} + constructor(public service: ClimateService, public api: ClimateApiService) {} async ngOnInit() { await this.service.ready(); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts index e28923b66..a04eefe77 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts @@ -6,8 +6,8 @@ import { MatTabsModule } from '@angular/material/tabs'; import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features/data-table'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; -import { ClimateDataDashboardService } from '../../../../climate-data.service'; -import { ClimateDataApiService } from '../../../../climate-data-api.service'; +import { ClimateService } from '../../../../climate.service'; +import { ClimateApiService } from '../../../../climate-api.service'; interface IRainfallSummary { data: any[]; @@ -25,8 +25,8 @@ interface IRainfallSummary { export class RainfallSummaryComponent implements AfterViewInit { public summary: IRainfallSummary = { data: [], metadata: {} }; constructor( - public api: ClimateDataApiService, - private service: ClimateDataDashboardService, + public api: ClimateApiService, + private service: ClimateService, private cdr: ChangeDetectorRef, private supabase: SupabaseService ) {} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts index 85d6a6439..c1d399d24 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { ClimateDataDashboardService } from '../../climate-data.service'; +import { ClimateService } from '../../climate.service'; import { RainfallSummaryComponent } from './components/rainfall-summary/rainfall-summary'; @Component({ @@ -24,7 +24,7 @@ export class StationPageComponent implements OnInit { }; } - constructor(private service: ClimateDataDashboardService) {} + constructor(private service: ClimateService) {} async ngOnInit() { await this.service.ready(); From b355f51fae33135ce49774de49a584488f37c850 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:22:44 -0800 Subject: [PATCH 08/33] chore: rename station-details page --- .../components/rainfall-summary/rainfall-summary.html | 0 .../components/rainfall-summary/rainfall-summary.scss | 0 .../components/rainfall-summary/rainfall-summary.spec.ts | 0 .../components/rainfall-summary/rainfall-summary.ts | 0 .../{station => station-details}/station-page.component.html | 0 .../{station => station-details}/station-page.component.scss | 0 .../{station => station-details}/station-page.component.spec.ts | 0 .../pages/{station => station-details}/station-page.component.ts | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/components/rainfall-summary/rainfall-summary.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/components/rainfall-summary/rainfall-summary.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/components/rainfall-summary/rainfall-summary.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/components/rainfall-summary/rainfall-summary.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/station-page.component.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/station-page.component.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/station-page.component.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{station => station-details}/station-page.component.ts (100%) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/components/rainfall-summary/rainfall-summary.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station-page.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.ts From c4a71874948bc55d0d54488e5d16f5d09bb79e06 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:25:21 -0800 Subject: [PATCH 09/33] refactor: station details page --- .../src/app/modules/climate/climate.module.ts | 4 ++-- .../app/modules/climate/climate.service.ts | 2 +- ...nt.html => station-details.component.html} | 0 ...nt.scss => station-details.component.scss} | 0 .../station-details.component.spec.ts | 21 +++++++++++++++++++ ...ponent.ts => station-details.component.ts} | 8 +++---- .../station-page.component.spec.ts | 21 ------------------- 7 files changed, 28 insertions(+), 28 deletions(-) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/{station-page.component.html => station-details.component.html} (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/{station-page.component.scss => station-details.component.scss} (100%) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.spec.ts rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/{station-page.component.ts => station-details.component.ts} (78%) delete mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts index 72914f0b7..522a1eb3b 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { ClimateDataHomeComponent } from './pages/home/climate-data-home.component'; -import { StationPageComponent } from './pages/station/station-page.component'; +import { StationDetailsPageComponent } from './pages/station-details/station-details.component'; @NgModule({ declarations: [], @@ -21,7 +21,7 @@ import { StationPageComponent } from './pages/station/station-page.component'; }, { path: 'station/:stationId', - component: StationPageComponent, + component: StationDetailsPageComponent, }, ]), ], diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts index 56e953827..ab39ba18d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts @@ -39,7 +39,7 @@ export class ClimateService extends PicsaAsyncService { await this.supabaseService.ready(); await this.listStations(); // Initialise other services without await to allow parallel requests - this.checkApiStatus(); + // this.checkApiStatus(); this.subscribeToRouteChanges(); } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.spec.ts new file mode 100644 index 000000000..59311ffc2 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { StationDetailsPageComponent } from './station-details.component'; + +describe('StationDetailsPageComponent', () => { + let component: StationDetailsPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [StationDetailsPageComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(StationDetailsPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.ts similarity index 78% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.ts index c1d399d24..6fc50d3c5 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-details.component.ts @@ -6,13 +6,13 @@ import { ClimateService } from '../../climate.service'; import { RainfallSummaryComponent } from './components/rainfall-summary/rainfall-summary'; @Component({ - selector: 'dashboard-station-page', + selector: 'dashboard-station-details', standalone: true, imports: [CommonModule, MatProgressBarModule, RainfallSummaryComponent], - templateUrl: './station-page.component.html', - styleUrls: ['./station-page.component.scss'], + templateUrl: './station-details.component.html', + styleUrls: ['./station-details.component.scss'], }) -export class StationPageComponent implements OnInit { +export class StationDetailsPageComponent implements OnInit { public get station() { return this.service.activeStation; } diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts deleted file mode 100644 index dcdb50ac9..000000000 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/station-page.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { StationPageComponent } from './station-page.component'; - -describe('StationPageComponent', () => { - let component: StationPageComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [StationPageComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(StationPageComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); From e229641ec999b817e74682fe677ed03bcd3cc20b Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:25:57 -0800 Subject: [PATCH 10/33] chore: rename station home --- .../pages/{home => station}/climate-data-home.component.html | 0 .../pages/{home => station}/climate-data-home.component.scss | 0 .../pages/{home => station}/climate-data-home.component.spec.ts | 0 .../pages/{home => station}/climate-data-home.component.ts | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{home => station}/climate-data-home.component.html (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{home => station}/climate-data-home.component.scss (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{home => station}/climate-data-home.component.spec.ts (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/{home => station}/climate-data-home.component.ts (100%) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.spec.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.ts similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/home/climate-data-home.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.ts From 48913ffa690ff54b7a17fd065584c08a940ad067 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 20:27:56 -0800 Subject: [PATCH 11/33] chore: code tidying --- .../src/app/modules/climate/climate.module.ts | 4 ++-- .../climate-data-home.component.spec.ts | 22 ------------------- ....component.html => station.component.html} | 0 ....component.scss => station.component.scss} | 0 .../pages/station/station.component.spec.ts | 22 +++++++++++++++++++ ...home.component.ts => station.component.ts} | 8 +++---- 6 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/{climate-data-home.component.html => station.component.html} (100%) rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/{climate-data-home.component.scss => station.component.scss} (100%) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.spec.ts rename apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/{climate-data-home.component.ts => station.component.ts} (83%) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts index 522a1eb3b..98223b72e 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts @@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { ClimateDataHomeComponent } from './pages/home/climate-data-home.component'; +import { ClimateStationPageComponent } from './pages/station/station.component'; import { StationDetailsPageComponent } from './pages/station-details/station-details.component'; @NgModule({ @@ -17,7 +17,7 @@ import { StationDetailsPageComponent } from './pages/station-details/station-det }, { path: 'station', - component: ClimateDataHomeComponent, + component: ClimateStationPageComponent, }, { path: 'station/:stationId', diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts deleted file mode 100644 index d643e4000..000000000 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ClimateDataHomeComponent } from './climate-data-home.component'; - -describe('ClimateDataHomeComponent', () => { - let component: ClimateDataHomeComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ClimateDataHomeComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(ClimateDataHomeComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.html rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss similarity index 100% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.scss rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.spec.ts new file mode 100644 index 000000000..c4e61cbcc --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ClimateStationPageComponent } from './station.component'; + +describe('ClimateStationPageComponent', () => { + let component: ClimateStationPageComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ClimateStationPageComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ClimateStationPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts similarity index 83% rename from apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.ts rename to apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts index d084a296d..114bfc419 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/climate-data-home.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts @@ -9,13 +9,13 @@ import { ClimateService, IStationRow } from '../../climate.service'; import { ClimateApiService } from '../../climate-api.service'; @Component({ - selector: 'dashboard-climate-data-home', + selector: 'dashboard-climate-station-page', standalone: true, imports: [CommonModule, MatTableModule, RouterModule, PicsaMapComponent, MatProgressSpinnerModule], - templateUrl: './climate-data-home.component.html', - styleUrls: ['./climate-data-home.component.scss'], + templateUrl: './station.component.html', + styleUrls: ['./station.component.scss'], }) -export class ClimateDataHomeComponent implements OnInit { +export class ClimateStationPageComponent implements OnInit { public displayedColumns: (keyof IStationRow)[] = ['station_id', 'station_name']; public mapMarkers: IMapMarker[]; From 3f23914741c51bb23381d51d5b8b5e78486f68f4 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 8 Feb 2024 22:18:56 -0800 Subject: [PATCH 12/33] feat: forecast page placeholder --- .../dashboard/src/app/app.component.html | 16 +++++++------- .../dashboard/src/app/app.component.ts | 7 +------ .../src/app/modules/climate/climate.module.ts | 5 +++++ .../pages/forecast/forecast.component.html | 1 + .../pages/forecast/forecast.component.scss | 0 .../pages/forecast/forecast.component.spec.ts | 21 +++++++++++++++++++ .../pages/forecast/forecast.component.ts | 12 +++++++++++ .../rainfall-summary/rainfall-summary.ts | 1 + 8 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.html create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.scss create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.spec.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts diff --git a/apps/picsa-apps/dashboard/src/app/app.component.html b/apps/picsa-apps/dashboard/src/app/app.component.html index 257f24f67..c9bca6e1d 100644 --- a/apps/picsa-apps/dashboard/src/app/app.component.html +++ b/apps/picsa-apps/dashboard/src/app/app.component.html @@ -28,14 +28,14 @@ @for (link of navLinks; track link.href) { @if(link.children){ - - + + {{ link.label }} diff --git a/apps/picsa-apps/dashboard/src/app/app.component.ts b/apps/picsa-apps/dashboard/src/app/app.component.ts index 031595a2d..76af98c55 100644 --- a/apps/picsa-apps/dashboard/src/app/app.component.ts +++ b/apps/picsa-apps/dashboard/src/app/app.component.ts @@ -9,7 +9,6 @@ interface INavLink { label: string; href: string; children?: INavLink[]; - isActive?: boolean; } @Component({ @@ -23,10 +22,6 @@ export class AppComponent implements AfterViewInit { title = 'picsa-apps-dashboard'; navLinks: INavLink[] = [ - // { - // label: 'Home', - // href: '', - // }, { label: 'Resources', href: '/resources', @@ -41,7 +36,7 @@ export class AppComponent implements AfterViewInit { }, { label: 'Forecasts', - href: '/forecasts', + href: '/forecast', }, ], }, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts index 98223b72e..8430cb47f 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.module.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { ClimateForecastPageComponent } from './pages/forecast/forecast.component'; import { ClimateStationPageComponent } from './pages/station/station.component'; import { StationDetailsPageComponent } from './pages/station-details/station-details.component'; @@ -19,6 +20,10 @@ import { StationDetailsPageComponent } from './pages/station-details/station-det path: 'station', component: ClimateStationPageComponent, }, + { + path: 'forecast', + component: ClimateForecastPageComponent, + }, { path: 'station/:stationId', component: StationDetailsPageComponent, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.html new file mode 100644 index 000000000..0dd7d8241 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.html @@ -0,0 +1 @@ +
diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.spec.ts new file mode 100644 index 000000000..603a95581 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ForecastComponent } from './forecast.component'; + +describe('ForecastComponent', () => { + let component: ForecastComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ForecastComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ForecastComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts new file mode 100644 index 000000000..5e8315259 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +@Component({ + selector: 'dashboard-climate-forecast', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './forecast.component.html', + styleUrls: ['./forecast.component.scss'], +}) +export class ClimateForecastPageComponent {} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts index a04eefe77..65a15e3b1 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.ts @@ -63,6 +63,7 @@ export class RainfallSummaryComponent implements AfterViewInit { summaries: ['annual_rain', 'start_rains', 'end_rains', 'end_season', 'seasonal_rain', 'seasonal_length'], }, }); + this.cdr.markForCheck(); if (error) throw error; this.loadData(data as any); // TODO - generalise way to persist db updates from api queries From ce7d69af79aa84a75bcc269e6f6d1c11a184f447 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 9 Feb 2024 09:09:47 -0800 Subject: [PATCH 13/33] feat: data table custom value templates and header formatter --- .../data-table/data-table.component.html | 17 +++++- .../data-table/data-table.component.ts | 61 ++++++++++++++++++- libs/utils/data.ts | 6 ++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/libs/shared/src/features/data-table/data-table.component.html b/libs/shared/src/features/data-table/data-table.component.html index b85436fc8..19cf6b8f5 100644 --- a/libs/shared/src/features/data-table/data-table.component.html +++ b/libs/shared/src/features/data-table/data-table.component.html @@ -19,10 +19,23 @@ @for(column of tableOptions.displayColumns; track column){ - - + + + } + string; /** Bind to row click events */ handleRowClick?: (row: any) => void; } +/** + * Simple pipe that allows providing a custom formatter function, + * used to modify cell values in a pure way + */ +@Pipe({ + standalone: true, + name: 'formatValue', +}) +export class FormatValuePipe implements PipeTransform { + transform(value: T, formatter: (v: T) => U) { + if (!formatter) return value; + return formatter(value); + } +} + /** * The `picsa-data-table` component is a lightweight wrapper around `mat-table`, used * to simplify display of basic tables. - * - * By default the table has support for sort, pagination and data search (filter) + * @example + * ``` + * + * ``` + * The table has support for sort, pagination and data search (filter), + * enabled by default and configurable by an options input @see IDataTableOptions + * @example + * ``` + * + * ``` + * The table will display all cell values directly, without any additional formatting + * If needing to render values within a custom template this can be done via `valueTemplates` + * @example + * ``` + * + * + * {{value | modifierPipe}} + * + * + * ``` * * For more advanced use cases such as custom column display prefer to directly use `mat-table` */ @@ -38,6 +84,7 @@ export interface IDataTableOptions { standalone: true, imports: [ CommonModule, + FormatValuePipe, MatButtonModule, MatFormFieldModule, MatIconModule, @@ -56,6 +103,13 @@ export class PicsaDataTableComponent implements OnChanges { /** User option overrides */ @Input() options: IDataTableOptions = {}; + /** + * Optional references to display specific column values in a custom template, + * indexed by column name. E.g. `{colA: myCustomTemplate}` + * https://angular.io/guide/content-projection#conditional-content-projection + */ + @Input() valueTemplates: Record> = {}; + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -66,6 +120,7 @@ export class PicsaDataTableComponent implements OnChanges { search: true, sort: true, handleRowClick: () => null, + formatHeader: (v) => v.split('_').map(capitalise).join(' '), }; public dataSource: MatTableDataSource; diff --git a/libs/utils/data.ts b/libs/utils/data.ts index 226c4e398..7ff664814 100644 --- a/libs/utils/data.ts +++ b/libs/utils/data.ts @@ -84,3 +84,9 @@ export function jsonNestedProperty(obj: any, nestedPath: string) { export function base64ToBlob(base64String, mimetype: string) { return createBlobFromBase64(base64String, mimetype); } + +/** Capitalise the first letter of a string */ +export function capitalise(str: string) { + if (typeof str !== 'string') return str; + return str.charAt(0).toUpperCase() + str.slice(1); +} From dc3ab351cc83d477f3dd6add63390b9378f156ed Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 9 Feb 2024 12:23:06 -0800 Subject: [PATCH 14/33] feat: add climate forecasts db endpoint --- .../20240209065921_climate_forecasts_create.sql | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql diff --git a/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql b/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql new file mode 100644 index 000000000..c210a201f --- /dev/null +++ b/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql @@ -0,0 +1,13 @@ +create table + public.climate_forecasts ( + id character varying not null, + district text null, + filename text not null, + language text not null, + type text null, + date_modified timestamp with time zone not null, + country character varying null, + storage_file uuid null, + constraint climate_forecasts_pkey primary key (id), + constraint climate_forecasts_storage_file_fkey foreign key (storage_file) references storage.objects (id) on update cascade on delete cascade + ) tablespace pg_default; \ No newline at end of file From d58c96d76249f116825dd32ecb14a1a547c3a710 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 9 Feb 2024 12:32:39 -0800 Subject: [PATCH 15/33] chore: update type definitions --- .../src/app/modules/climate/types/README.md | 2 +- .../src/app/modules/climate/types/api.d.ts | 94 ++++++++++++++++--- .../src/app/modules/climate/types/db.d.ts | 3 + .../src/app/modules/climate/types/index.ts | 2 + apps/picsa-server/supabase/types/index.ts | 58 ++++++++++-- 5 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md b/apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md index 4e86aec40..af81a2f9a 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/README.md @@ -5,5 +5,5 @@ The climate data api runs on a remote server and exports endpoint definitions us These definitions can be converted into typescript types on-demand using the command: ```sh -npx openapi-typescript "https://api.epicsa.idems.international/openapi.json" -o "apps\picsa-apps\dashboard\src\app\modules\climate-data\types\api.d.ts" +npx openapi-typescript "https://api.epicsa.idems.international/openapi.json" -o "apps\picsa-apps\dashboard\src\app\modules\climate\types\api.d.ts" ``` diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts index ba4357a5b..e2f1001ab 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/api.d.ts @@ -8,7 +8,7 @@ export interface paths { "/v1/status/": { /** * Get Status - * @description Check server up and authorized to access data + * @description Check server up */ get: operations["get_status_v1_status__get"]; }; @@ -49,12 +49,25 @@ export interface paths { get: operations["read_stations_v1_station__country___station_id__get"]; }; "/v1/forecasts/": { - /** Get Forecasts */ - get: operations["get_forecasts_v1_forecasts__get"]; + /** + * List Endpoints + * @description List available forecast endpoints + */ + get: operations["list_endpoints_v1_forecasts__get"]; }; - "/v1/forecasts/{country}/{file_name}": { - /** Get Forecasts */ - get: operations["get_forecasts_v1_forecasts__country___file_name__get"]; + "/v1/forecasts/{country_code}": { + /** + * List Forecasts + * @description Get available forecasts for country + */ + get: operations["list_forecasts_v1_forecasts__country_code__get"]; + }; + "/v1/forecasts/{country_code}/{file_name}": { + /** + * Get Forecast + * @description Get forecast file + */ + get: operations["get_forecast_v1_forecasts__country_code___file_name__get"]; }; } @@ -155,6 +168,30 @@ export interface components { */ summaries?: ("extremes_rain" | "extremes_tmin" | "extremes_tmax")[]; }; + /** Forecast */ + Forecast: { + /** + * Date Modified + * Format: date-time + */ + date_modified: string; + /** District */ + district?: string; + /** Filename */ + filename: string; + /** Id */ + id: string; + /** + * Language Code + * @enum {string} + */ + language_code?: "en" | "ny"; + /** + * Type + * @enum {string} + */ + type?: "downscale_forecast" | "annual_forecast"; + }; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ @@ -243,7 +280,7 @@ export interface operations { /** * Get Status - * @description Check server up and authorized to access data + * @description Check server up */ get_status_v1_status__get: { responses: { @@ -443,22 +480,53 @@ export interface operations { }; }; }; - /** Get Forecasts */ - get_forecasts_v1_forecasts__get: { + /** + * List Endpoints + * @description List available forecast endpoints + */ + list_endpoints_v1_forecasts__get: { responses: { /** @description Successful Response */ 200: { content: { - "application/json": unknown; + "application/json": string[]; }; }; }; }; - /** Get Forecasts */ - get_forecasts_v1_forecasts__country___file_name__get: { + /** + * List Forecasts + * @description Get available forecasts for country + */ + list_forecasts_v1_forecasts__country_code__get: { parameters: { path: { - country: "zm" | "mw"; + country_code: "zm" | "mw"; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["Forecast"][]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** + * Get Forecast + * @description Get forecast file + */ + get_forecast_v1_forecasts__country_code___file_name__get: { + parameters: { + path: { + country_code: "zm" | "mw"; file_name: string; }; }; diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts new file mode 100644 index 000000000..f20e33626 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts @@ -0,0 +1,3 @@ +import { Database } from '@picsa/server-types'; + +export type IForecastRow = Database['public']['Tables']['climate_forecasts']['Row']; diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts new file mode 100644 index 000000000..62dd5da1c --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/index.ts @@ -0,0 +1,2 @@ +export * from './api'; +export * from './db'; diff --git a/apps/picsa-server/supabase/types/index.ts b/apps/picsa-server/supabase/types/index.ts index c5486929a..06449bb7b 100644 --- a/apps/picsa-server/supabase/types/index.ts +++ b/apps/picsa-server/supabase/types/index.ts @@ -34,26 +34,69 @@ export interface Database { } public: { Tables: { + climate_forecasts: { + Row: { + country: string | null + date_modified: string + district: string | null + filename: string + id: string + language: string + storage_file: string | null + type: string | null + } + Insert: { + country?: string | null + date_modified: string + district?: string | null + filename: string + id: string + language: string + storage_file?: string | null + type?: string | null + } + Update: { + country?: string | null + date_modified?: string + district?: string | null + filename?: string + id?: string + language?: string + storage_file?: string | null + type?: string | null + } + Relationships: [ + { + foreignKeyName: "climate_forecasts_storage_file_fkey" + columns: ["storage_file"] + referencedRelation: "objects" + referencedColumns: ["id"] + }, + { + foreignKeyName: "climate_forecasts_storage_file_fkey" + columns: ["storage_file"] + referencedRelation: "storage_objects" + referencedColumns: ["id"] + } + ] + } climate_products: { Row: { created_at: string data: Json - id: number - station_id: number | null + station_id: number type: string } Insert: { created_at?: string data: Json - id?: number - station_id?: number | null + station_id: number type: string } Update: { created_at?: string data?: Json - id?: number - station_id?: number | null + station_id?: number type?: string } Relationships: [ @@ -275,7 +318,6 @@ export interface Database { metadata: Json | null name: string | null owner: string | null - owner_id: string | null path_tokens: string[] | null updated_at: string | null version: string | null @@ -288,7 +330,6 @@ export interface Database { metadata?: Json | null name?: string | null owner?: string | null - owner_id?: string | null path_tokens?: string[] | null updated_at?: string | null version?: string | null @@ -301,7 +342,6 @@ export interface Database { metadata?: Json | null name?: string | null owner?: string | null - owner_id?: string | null path_tokens?: string[] | null updated_at?: string | null version?: string | null From 162b219b35da91a57ff794902a8fa342f4ecb166 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 9 Feb 2024 12:37:22 -0800 Subject: [PATCH 16/33] chore: code tidying --- .../src/app/modules/climate/climate-api.service.ts | 2 -- .../dashboard/src/app/modules/climate/climate.service.ts | 7 +------ .../modules/climate/pages/station/station.component.ts | 3 ++- .../dashboard/src/app/modules/climate/types/db.d.ts | 7 ++++++- .../resources/pages/create/resource-create.component.ts | 9 +++------ .../src/app/modules/resources/resources.service.ts | 8 ++++---- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts index 096579cf5..34d9d65d0 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts @@ -12,8 +12,6 @@ type ICallbackClient = (id:string)=>ReturnType> /** Type-safe http client with added support for callbacks */ type IClient = ReturnType> & {useMeta:ICallbackClient} - - interface IMetaEntry{ status:'pending' | 'success' | 'error' | 'unknown', rawResponse?:Response, diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts index ab39ba18d..5c787ec56 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate.service.ts @@ -1,7 +1,5 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -// eslint-disable-next-line @nx/enforce-module-boundaries -import { Database } from '@picsa/server-types'; import { PicsaAsyncService } from '@picsa/shared/services/asyncService.service'; import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; @@ -9,16 +7,13 @@ import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/sup import { ngRouterMergedSnapshot$ } from '@picsa/utils/angular'; import { ClimateApiService } from './climate-api.service'; - -export type IStationRow = Database['public']['Tables']['climate_stations']['Row']; +import { IStationRow } from './types'; export interface IResourceStorageEntry extends IStorageEntry { /** Url generated when upload to public bucket (will always be populated, even if bucket not public) */ publicUrl: string; } -export type IResourceEntry = Database['public']['Tables']['resources']['Row']; - @Injectable({ providedIn: 'root' }) export class ClimateService extends PicsaAsyncService { public apiStatus: number; diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts index 114bfc419..842c053f9 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts @@ -5,8 +5,9 @@ import { MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map'; -import { ClimateService, IStationRow } from '../../climate.service'; +import { ClimateService } from '../../climate.service'; import { ClimateApiService } from '../../climate-api.service'; +import { IStationRow } from '../../types'; @Component({ selector: 'dashboard-climate-station-page', diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts index f20e33626..4dc76d5a0 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts @@ -1,3 +1,8 @@ -import { Database } from '@picsa/server-types'; +// eslint-disable-next-line @nx/enforce-module-boundaries +import type { Database } from '@picsa/server-types'; export type IForecastRow = Database['public']['Tables']['climate_forecasts']['Row']; + +export type IResourceRow = Database['public']['Tables']['resources']['Row']; + +export type IStationRow = Database['public']['Tables']['climate_stations']['Row']; diff --git a/apps/picsa-apps/dashboard/src/app/modules/resources/pages/create/resource-create.component.ts b/apps/picsa-apps/dashboard/src/app/modules/resources/pages/create/resource-create.component.ts index 2d38af24c..268f43a4c 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/resources/pages/create/resource-create.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/resources/pages/create/resource-create.component.ts @@ -3,8 +3,6 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { PICSAFormValidators } from '@picsa/forms'; -// eslint-disable-next-line @nx/enforce-module-boundaries -import type { Database } from '@picsa/server-types'; import { IUploadResult, SupabaseStoragePickerDirective, @@ -13,11 +11,10 @@ import { import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/supabase-storage.service'; import { DashboardMaterialModule } from '../../../../material.module'; +import { IResourceRow } from '../../../climate/types'; import { DashboardResourcesStorageLinkComponent } from '../../components/storage-link/storage-link.component'; import { ResourcesDashboardService } from '../../resources.service'; -type IResourceEntry = Database['public']['Tables']['resources']['Row']; - @Component({ selector: 'dashboard-resource-create', standalone: true, @@ -67,7 +64,7 @@ export class ResourceCreateComponent implements OnInit { await this.service.ready(); const { id } = this.route.snapshot.params; if (id) { - const { data } = await this.service.table.select<'*', IResourceEntry>('*').eq('id', id); + const { data } = await this.service.table.select<'*', IResourceRow>('*').eq('id', id); const resource = data?.[0]; if (resource) { this.populateResource(resource); @@ -86,7 +83,7 @@ export class ResourceCreateComponent implements OnInit { console.log({ data, error }); } - private populateResource(resource: IResourceEntry) { + private populateResource(resource: IResourceRow) { this.resourceType = resource.type as any; console.log('populate resource', resource); switch (resource.type) { diff --git a/apps/picsa-apps/dashboard/src/app/modules/resources/resources.service.ts b/apps/picsa-apps/dashboard/src/app/modules/resources/resources.service.ts index 0638c4133..438711585 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/resources/resources.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/resources/resources.service.ts @@ -7,18 +7,18 @@ import { SupabaseService } from '@picsa/shared/services/core/supabase'; import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/supabase-storage.service'; import { arrayToHashmap } from '@picsa/utils'; +import { IResourceRow } from '../climate/types'; + export interface IResourceStorageEntry extends IStorageEntry { /** Url generated when upload to public bucket (will always be populated, even if bucket not public) */ publicUrl: string; } -export type IResourceEntry = Database['public']['Tables']['resources']['Row']; - @Injectable({ providedIn: 'root' }) export class ResourcesDashboardService extends PicsaAsyncService { private storageFiles: IResourceStorageEntry[] = []; public storageFilesHashmap: Record = {}; - public readonly resources = signal([]); + public readonly resources = signal([]); public get table() { return this.supabaseService.db.table('resources'); @@ -104,7 +104,7 @@ export class ResourcesDashboardService extends PicsaAsyncService { } private async listResources() { - const { data, error } = await this.supabaseService.db.table('resources').select<'*', IResourceEntry>('*'); + const { data, error } = await this.supabaseService.db.table('resources').select<'*', IResourceRow>('*'); if (error) { throw error; } From 57bd1bd1e191490e13a395926c4938e781a17a3a Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Fri, 9 Feb 2024 14:24:18 -0800 Subject: [PATCH 17/33] chore: update type definitions --- .../dashboard/src/app/modules/climate/types/db.d.ts | 4 ++++ .../20240209065921_climate_forecasts_create.sql | 4 ++-- apps/picsa-server/supabase/types/index.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts index 4dc76d5a0..9f2783bc0 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/types/db.d.ts @@ -1,7 +1,11 @@ // eslint-disable-next-line @nx/enforce-module-boundaries import type { Database } from '@picsa/server-types'; +export type IClimateProductRow = Database['public']['Tables']['climate_products']['Row']; +export type IClimateProductInsert = Database['public']['Tables']['climate_products']['Insert']; + export type IForecastRow = Database['public']['Tables']['climate_forecasts']['Row']; +export type IForecastInsert = Database['public']['Tables']['climate_forecasts']['Insert']; export type IResourceRow = Database['public']['Tables']['resources']['Row']; diff --git a/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql b/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql index c210a201f..cfbd90036 100644 --- a/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql +++ b/apps/picsa-server/supabase/migrations/20240209065921_climate_forecasts_create.sql @@ -3,10 +3,10 @@ create table id character varying not null, district text null, filename text not null, - language text not null, + language_code text null, type text null, date_modified timestamp with time zone not null, - country character varying null, + country_code character varying null, storage_file uuid null, constraint climate_forecasts_pkey primary key (id), constraint climate_forecasts_storage_file_fkey foreign key (storage_file) references storage.objects (id) on update cascade on delete cascade diff --git a/apps/picsa-server/supabase/types/index.ts b/apps/picsa-server/supabase/types/index.ts index 06449bb7b..604433bba 100644 --- a/apps/picsa-server/supabase/types/index.ts +++ b/apps/picsa-server/supabase/types/index.ts @@ -36,32 +36,32 @@ export interface Database { Tables: { climate_forecasts: { Row: { - country: string | null + country_code: string | null date_modified: string district: string | null filename: string id: string - language: string + language_code: string | null storage_file: string | null type: string | null } Insert: { - country?: string | null + country_code?: string | null date_modified: string district?: string | null filename: string id: string - language: string + language_code?: string | null storage_file?: string | null type?: string | null } Update: { - country?: string | null + country_code?: string | null date_modified?: string district?: string | null filename?: string id?: string - language?: string + language_code?: string | null storage_file?: string | null type?: string | null } From b2d19073f522cb54b30fe2a5ce51ce232af3a21a Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Tue, 13 Feb 2024 22:06:26 -0800 Subject: [PATCH 18/33] feat: api status component --- .../components/api-status/api-status.html | 6 ++ .../components/api-status/api-status.scss | 21 ++++ .../components/api-status/api-status.spec.ts | 21 ++++ .../components/api-status/api-status.ts | 96 +++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.spec.ts create mode 100644 apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html new file mode 100644 index 000000000..5455221e7 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html @@ -0,0 +1,6 @@ + + + diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss new file mode 100644 index 000000000..50e062446 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss @@ -0,0 +1,21 @@ +mat-icon.spin { + animation: spin 2s linear infinite; +} + +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.spec.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.spec.ts new file mode 100644 index 000000000..31e012139 --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DashboardClimateApiStatusComponent } from './api-status'; + +describe('DashboardClimateApiStatusComponent', () => { + let component: DashboardClimateApiStatusComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DashboardClimateApiStatusComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DashboardClimateApiStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts new file mode 100644 index 000000000..e84c4260a --- /dev/null +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts @@ -0,0 +1,96 @@ +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnDestroy, + Output, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; +import { Subject, Subscription, takeUntil } from 'rxjs'; + +import { ClimateService } from '../../climate.service'; +import { ClimateApiService } from '../../climate-api.service'; + +export type IStatus = 'pending' | 'success' | 'error' | 'unknown'; + +/** + * Component used to display status of ongoing API requests + * ``` + * + * ``` + */ +@Component({ + selector: 'dashboard-climate-api-status', + standalone: true, + imports: [CommonModule, MatButtonModule, MatIconModule], + templateUrl: './api-status.html', + styleUrls: ['./api-status.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DashboardClimateApiStatusComponent implements OnDestroy { + public status: IStatus = 'pending'; + public code?: number; + + private componentDestroyed$ = new Subject(); + private subscription: Subscription; + + @Output() handleRefresh = new EventEmitter(); + + constructor( + public api: ClimateApiService, + public service: ClimateService, + private notificationService: PicsaNotificationService, + private cdr: ChangeDetectorRef + ) {} + + /** Unique id of API request to monitor for status updates */ + @Input() set clientId(id: string) { + // clear any previous subscription + if (this.subscription) this.subscription.unsubscribe(); + // subscribe to any requests sent via client and update UI accordingly + const client = this.api.getObservableClient(id); + this.subscription = client.$.pipe(takeUntil(this.componentDestroyed$)).subscribe((response) => { + this.status = this.getCallbackStatus(response?.status); + this.code = response?.status; + this.cdr.markForCheck(); + if (response && this.status === 'error') { + this.showCustomFetchErrorMessage(id, response); + } + }); + } + ngOnDestroy(): void { + this.componentDestroyed$.next(true); + this.componentDestroyed$.complete(); + } + + private getCallbackStatus(statusCode?: number): IStatus { + if (!statusCode) return 'unknown'; + if (100 <= statusCode && statusCode <= 199) return 'pending'; + if (200 <= statusCode && statusCode <= 299) return 'success'; + if (400 <= statusCode && statusCode <= 599) return 'error'; + return 'unknown'; + } + + /** Show error message when using custom fetch with callbacks */ + private async showCustomFetchErrorMessage(id: string, response: Response) { + // clone body so that open-api can still consume when constructing full fetch response + const clone = response.clone(); + try { + const json = await clone.json(); + const errorText = json.detail || 'failed, see console logs for details'; + this.notificationService.showUserNotification({ matIcon: 'error', message: `[${id}] ${errorText}` }); + } catch (error) { + console.error(error); + console.error('Fetch Error', error); + this.notificationService.showUserNotification({ + matIcon: 'error', + message: `[${id}] 'failed, see console logs for details'`, + }); + } + } +} From a88cab6c4fb4b4f164c586b4381ebb19b6ac170f Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Tue, 13 Feb 2024 22:09:07 -0800 Subject: [PATCH 19/33] refactor: climate api service client --- .../modules/climate/climate-api.service.ts | 96 +++++++------------ 1 file changed, 36 insertions(+), 60 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts index 34d9d65d0..c9f50214d 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts @@ -1,32 +1,27 @@ import { Injectable } from '@angular/core'; -import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; import createClient from 'openapi-fetch'; +import { BehaviorSubject, Subject } from 'rxjs'; import { paths } from './types/api'; const API_ENDPOINT = 'https://api.epicsa.idems.international'; -/** Custom client which tracks responses by callback id */ -type ICallbackClient = (id:string)=>ReturnType> - /** Type-safe http client with added support for callbacks */ -type IClient = ReturnType> & {useMeta:ICallbackClient} - -interface IMetaEntry{ - status:'pending' | 'success' | 'error' | 'unknown', - rawResponse?:Response, -} - +export type IApiClient = ReturnType> & { $:Subject} /** * Service to interact with external PICSA Climate API * All methods are exposed through a type-safe `client` property, or can additionally use * a custom client that includes status notification updates via the `useMeta` method * @example - * Use custom callback that will show user notifications on error and record to service + * Use observable client that can also be accessed by api-status component * ```ts - * const {response, data, error} = await api.useMeta('myRequestId').POST(...) + * await api.getObservableClient('myRequestId').POST(...) * ``` + * ```html + * + * ``` + * * Use default client without additional callbacks * ```ts * const {response, data, error} = await api.client.POST(...) @@ -34,64 +29,45 @@ interface IMetaEntry{ * */ @Injectable({ providedIn: 'root' }) export class ClimateApiService { - - /** Request additional meta by id */ - public meta:Record={} /** Http client with type-definitions for API endpoints */ - public client:IClient + public client:IApiClient - constructor(private notificationService:PicsaNotificationService) { - const client = createClient({ baseUrl: API_ENDPOINT,mode:'cors' }); - this.client = {...client,useMeta:()=>{ - return client - }} - } + /** List of custom clients generated to be shared across observers */ + private observableClients:Record = {} + constructor() { + this.client = this.createObservableClient('_default') + } - /** - * Provide an id which which will be updated alongside requests. - * The cache will also include interceptors to provide user notification on error - **/ - public useMeta(id:string){ - const customFetch = this.createCustomFetchClient(id) - const customClient = createClient({ baseUrl: API_ENDPOINT,mode:'cors',fetch:customFetch }); - return customClient + /** + * Retrive an instance of the api client with a specific ID so that request updates can be subscribed to + * by any other services or components accessing via the same id. Creates new client if not existing + */ + public getObservableClient(clientId?:string):IApiClient{ + if(clientId){ + return this.observableClients[clientId] || this.createObservableClient(clientId) + } + return this.client } - /** Create a custom implementation of fetch client to handle status updates and notifications */ - private createCustomFetchClient(id:string){ - return async (...args:Parameters)=>{ - this.meta[id]={status:'pending'} + private createObservableClient(clientId:string){ + const $ = new BehaviorSubject(undefined) + const customFetch = async (...args:Parameters)=>{ + // send a custom response with 102 status code to inform that request has been sent but is pending + $.next({status:102} as Response) const response = await window.fetch(...args); - this.meta[id].status = this.getCallbackStatus(response.status) - this.meta[id].rawResponse = response - if(this.meta[id].status ==='error' ){ - await this.showCustomFetchErrorMessage(id,response) - } + $.next(response) return response } + const baseClient = createClient({ baseUrl: API_ENDPOINT,mode:'cors',fetch:customFetch }); + const client:IApiClient ={...baseClient, $} + this.observableClients[clientId] = client + return client } - /** Show error message when using custom fetch with callbacks */ - private async showCustomFetchErrorMessage(id:string,response:Response){ - // clone body so that open-api can still consume when constructing full fetch response - const clone = response.clone() - try { - const json = await clone.json() - const errorText = json.detail || 'failed, see console logs for details' - this.notificationService.showUserNotification({matIcon:'error',message:`[${id}] ${errorText}`}) - } catch (error) { - console.error(error) - console.error('Fetch Error',error) - this.notificationService.showUserNotification({matIcon:'error',message:`[${id}] 'failed, see console logs for details'`}) - } - } - private getCallbackStatus(statusCode:number):IMetaEntry['status']{ - if(200 <= statusCode && statusCode <=299) return 'success' - if(400 <= statusCode && statusCode <=499) return 'error' - if(500 <= statusCode && statusCode <=599) return 'error' - return 'unknown' - } + + + } From 074c1fe469a24faa94145a5c0727e23edeb8af4b Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Tue, 13 Feb 2024 23:12:55 -0800 Subject: [PATCH 20/33] style: api status component --- .../components/api-status/api-status.html | 15 ++++-- .../components/api-status/api-status.scss | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html index 5455221e7..f0a206e9b 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html @@ -1,6 +1,11 @@ - - +
+ {{code}} + @if(options.labels?.ready; as readyLabel){ +
{{readyLabel}}
+ } @if(options.events?.refresh; as refreshEvent){ + + } +
diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss index 50e062446..718cb6020 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.scss @@ -1,3 +1,53 @@ +.status-container { + display: flex; + align-items: center; + justify-content: center; +} + +.status-label, +button.status-refresh-button, +.status-code { + --border-color: var(--color-light); + height: 30px; + padding: 8px; + display: flex; + justify-content: center; + align-items: center; + border-top: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-color); + border-radius: 0; +} + +.status-code { + width: 24px; + color: white; + background: var(--color-light); + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + &[data-status='success'] { + --border-color: hsl(120, 25%, 50%); + background: hsl(120, 25%, 50%); + } + &[data-status='error'] { + --border-color: hsl(0, 60%, 65%); + background: hsl(0, 60%, 65%); + } +} +.status-label { + border-right: 1px solid var(--border-color); +} + +button.status-refresh-button { + --mdc-icon-button-state-layer-size: 40px; + border-right: 1px solid var(--border-color); + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +// mat-progress-spinner.mat-white { +// --mdc-circular-progress-active-indicator-color: white; +// } + mat-icon.spin { animation: spin 2s linear infinite; } From 7db2fc74c416d2b18c5df33e9502f030232ce6f2 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Tue, 13 Feb 2024 23:13:26 -0800 Subject: [PATCH 21/33] chore: code tidying --- .../components/api-status/api-status.ts | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts index e84c4260a..bb374fafe 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts @@ -1,13 +1,5 @@ import { CommonModule } from '@angular/common'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - EventEmitter, - Input, - OnDestroy, - Output, -} from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { PicsaNotificationService } from '@picsa/shared/services/core/notification.service'; @@ -16,7 +8,17 @@ import { Subject, Subscription, takeUntil } from 'rxjs'; import { ClimateService } from '../../climate.service'; import { ClimateApiService } from '../../climate-api.service'; -export type IStatus = 'pending' | 'success' | 'error' | 'unknown'; +export type IStatus = 'pending' | 'success' | 'error' | 'ready'; + +export interface IApiStatusOptions { + labels?: { + ready?: string; + error?: string; + }; + events?: { + refresh?: () => void; + }; +} /** * Component used to display status of ongoing API requests @@ -39,8 +41,6 @@ export class DashboardClimateApiStatusComponent implements OnDestroy { private componentDestroyed$ = new Subject(); private subscription: Subscription; - @Output() handleRefresh = new EventEmitter(); - constructor( public api: ClimateApiService, public service: ClimateService, @@ -48,6 +48,8 @@ export class DashboardClimateApiStatusComponent implements OnDestroy { private cdr: ChangeDetectorRef ) {} + @Input() options: IApiStatusOptions = {}; + /** Unique id of API request to monitor for status updates */ @Input() set clientId(id: string) { // clear any previous subscription @@ -56,7 +58,10 @@ export class DashboardClimateApiStatusComponent implements OnDestroy { const client = this.api.getObservableClient(id); this.subscription = client.$.pipe(takeUntil(this.componentDestroyed$)).subscribe((response) => { this.status = this.getCallbackStatus(response?.status); - this.code = response?.status; + // only assign success and error codes + if (this.status === 'error' || this.status === 'success') { + this.code = response?.status; + } this.cdr.markForCheck(); if (response && this.status === 'error') { this.showCustomFetchErrorMessage(id, response); @@ -69,11 +74,11 @@ export class DashboardClimateApiStatusComponent implements OnDestroy { } private getCallbackStatus(statusCode?: number): IStatus { - if (!statusCode) return 'unknown'; + if (!statusCode) return 'ready'; if (100 <= statusCode && statusCode <= 199) return 'pending'; if (200 <= statusCode && statusCode <= 299) return 'success'; if (400 <= statusCode && statusCode <= 599) return 'error'; - return 'unknown'; + return 'ready'; } /** Show error message when using custom fetch with callbacks */ From 47652487065818eadcef63d20bb719fb901426d9 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Tue, 13 Feb 2024 23:15:59 -0800 Subject: [PATCH 22/33] chore: code tidying --- .../rainfall-summary/rainfall-summary.html | 5 +--- .../rainfall-summary/rainfall-summary.scss | 22 ------------------ .../pages/station/station.component.html | 13 +---------- .../pages/station/station.component.scss | 23 ------------------- .../pages/station/station.component.ts | 20 +++++++++++++++- 5 files changed, 21 insertions(+), 62 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html index 613ca95f3..0465b6405 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.html @@ -1,9 +1,6 @@

Rainfall Summary

- +
diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss index 58125726c..5d4e87f30 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station-details/components/rainfall-summary/rainfall-summary.scss @@ -1,25 +1,3 @@ :host { display: block; } - -mat-icon.spin { - animation: spin 2s linear infinite; -} - -@-webkit-keyframes spin { - 0% { - -webkit-transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - } -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html index 92c61a7be..03e9679de 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.html @@ -1,18 +1,7 @@

Climate Data

- @if(api.meta.serverStatus; as meta){ -
- Server Status - - @if(meta.rawResponse; as response){ - {{ response.status }} - } @else { - - } - -
- } +

Stations

diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss index 69317ae3d..bfe89e0d1 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.scss @@ -1,26 +1,3 @@ -.server-status { - display: flex; - gap: 8px; - align-items: center; -} -.status-code { - color: white; - padding: 8px; - border-radius: 4px; - background: gray; - &[data-status='200'] { - background: green; - } - width: 32px; - height: 24px; - display: flex; - justify-content: center; - align-items: center; -} -mat-progress-spinner.mat-white { - --mdc-circular-progress-active-indicator-color: white; -} - table.station-table { max-height: 50vh; display: block; diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts index 842c053f9..743843c43 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/station/station.component.ts @@ -7,12 +7,20 @@ import { IMapMarker, PicsaMapComponent } from '@picsa/shared/features/map/map'; import { ClimateService } from '../../climate.service'; import { ClimateApiService } from '../../climate-api.service'; +import { DashboardClimateApiStatusComponent, IApiStatusOptions } from '../../components/api-status/api-status'; import { IStationRow } from '../../types'; @Component({ selector: 'dashboard-climate-station-page', standalone: true, - imports: [CommonModule, MatTableModule, RouterModule, PicsaMapComponent, MatProgressSpinnerModule], + imports: [ + CommonModule, + DashboardClimateApiStatusComponent, + MatTableModule, + RouterModule, + PicsaMapComponent, + MatProgressSpinnerModule, + ], templateUrl: './station.component.html', styleUrls: ['./station.component.scss'], }) @@ -21,6 +29,11 @@ export class ClimateStationPageComponent implements OnInit { public mapMarkers: IMapMarker[]; + public apiStatusOptions: IApiStatusOptions = { + events: { refresh: () => this.getApiStatus() }, + labels: { ready: 'Server Status' }, + }; + constructor(public service: ClimateService, public api: ClimateApiService) {} async ngOnInit() { @@ -29,5 +42,10 @@ export class ClimateStationPageComponent implements OnInit { latlng: [m.latitude as number, m.longitude as number], number: m.station_id, })); + this.getApiStatus(); + } + + public getApiStatus() { + this.api.getObservableClient('serverStatus').GET('/v1/status/'); } } From 53469264d02563b9a81402eb9a45a423b4584916 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Wed, 14 Feb 2024 08:33:58 -0800 Subject: [PATCH 23/33] feat: wip forecast component --- .../components/api-status/api-status.html | 3 +- .../components/api-status/api-status.scss | 24 ++++----- .../components/api-status/api-status.ts | 15 ++++-- .../pages/forecast/forecast.component.html | 10 +++- .../pages/forecast/forecast.component.ts | 52 +++++++++++++++++-- 5 files changed, 82 insertions(+), 22 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html index f0a206e9b..468845a64 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.html @@ -1,7 +1,8 @@
+ @if(options.showStatusCode){ {{code}} - @if(options.labels?.ready; as readyLabel){ + } @if(options.labels?.ready; as readyLabel){
{{readyLabel}}
} @if(options.events?.refresh; as refreshEvent){ +
diff --git a/apps/picsa-apps/dashboard/src/styles.scss b/apps/picsa-apps/dashboard/src/styles.scss index 0029378bb..a06c718d9 100644 --- a/apps/picsa-apps/dashboard/src/styles.scss +++ b/apps/picsa-apps/dashboard/src/styles.scss @@ -12,13 +12,13 @@ Made available from project.json }, ``` */ - -@import 'themes'; -@import 'variables'; +@import 'animations'; @import 'fonts'; @import 'layout'; -@import 'typography'; @import 'overrides'; +@import 'themes'; +@import 'typography'; +@import 'variables'; mat-sidenav-content > * { display: contents; diff --git a/libs/theme/src/_animations.scss b/libs/theme/src/_animations.scss new file mode 100644 index 000000000..bca415c58 --- /dev/null +++ b/libs/theme/src/_animations.scss @@ -0,0 +1,22 @@ +// Provide perpetual spin animation to any mat-icon +mat-icon.spin { + animation: spin 2s linear infinite; +} + +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/libs/theme/src/_index.scss b/libs/theme/src/_index.scss index 0fde0dd35..0689ecb7f 100644 --- a/libs/theme/src/_index.scss +++ b/libs/theme/src/_index.scss @@ -3,9 +3,10 @@ */ // shared variables -@import 'themes'; -@import 'variables'; +@import 'animations'; @import 'fonts'; @import 'layout'; -@import 'typography'; @import 'overrides'; +@import 'themes'; +@import 'typography'; +@import 'variables'; From 015d54fd5a4ff5c25a87391a81ecf4f20420646f Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 15 Feb 2024 15:14:37 -0800 Subject: [PATCH 31/33] feat: storage putFile and blob response handling --- .../modules/climate/climate-api.service.ts | 18 ++++++++-- .../components/api-status/api-status.ts | 35 ++++++++++++------- .../services/supabase-storage.service.ts | 16 ++++++++- 3 files changed, 53 insertions(+), 16 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts index c9f50214d..24cc3a130 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/climate-api.service.ts @@ -56,9 +56,21 @@ export class ClimateApiService { const customFetch = async (...args:Parameters)=>{ // send a custom response with 102 status code to inform that request has been sent but is pending $.next({status:102} as Response) - const response = await window.fetch(...args); - $.next(response) - return response + try { + const response = await window.fetch(...args); + $.next(response) + return response + } catch (error:any) { + // Likely internal server error thrown + console.error(args) + console.error(error) + const message = error.message + const blob = new Blob([JSON.stringify({message}, null, 2)], {type : 'application/json'}); + const errorRes = new Response(blob,{status:500,statusText:message}) + $.next(errorRes) + return errorRes + } + } const baseClient = createClient({ baseUrl: API_ENDPOINT,mode:'cors',fetch:customFetch }); const client:IApiClient ={...baseClient, $} diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts index e486ca305..2ed4fd973 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/components/api-status/api-status.ts @@ -72,8 +72,8 @@ export class DashboardClimateApiStatusComponent implements OnInit, OnDestroy { } // console log response body (debug purposes) if (response && this.status === 'success') { - const resJson = await this.getResponseBodyJson(response); - console.log(`[API] ${id}`, resJson); + const body = await this.parseResponseBody(response); + console.log(`[API] ${id}`, body); } }); } @@ -95,21 +95,32 @@ export class DashboardClimateApiStatusComponent implements OnInit, OnDestroy { /** Show error message when using custom fetch with callbacks */ private async showCustomFetchErrorMessage(id: string, response: Response) { - const resJson = await this.getResponseBodyJson(response); - const errorText = resJson.detail || 'failed, see console logs for details'; + const body = await this.parseResponseBody(response); + const errorText = body.detail || 'failed, see console logs for details'; console.error(response); this.notificationService.showUserNotification({ matIcon: 'error', message: `[${id}] ${errorText}` }); } - private async getResponseBodyJson(response: Response) { - // clone body so that open-api can still consume when constructing full fetch response + /** + * Parse response body and format as JSON, nesting blob and text content types as custom properties + * in cases where response is not json type + */ + private async parseResponseBody(response: Response): Promise> { + // if (!response.bodyUsed) return {}; const clone = response.clone(); - try { - const json = await clone.json(); - return json; - } catch (error) { - console.error('Response body parse error', error); - return {}; + const contentType = response.headers.get('content-type'); + switch (contentType) { + case 'application/json': + return clone.json(); + case 'application/pdf': { + const blob = await clone.blob(); + return { blob }; + } + default: { + console.warn('No parser for response content', contentType); + const text = await clone.text(); + return { text }; + } } } } diff --git a/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts b/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts index 6290aeed8..aecc75557 100644 --- a/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts +++ b/libs/shared/src/services/core/supabase/services/supabase-storage.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; // eslint-disable-next-line @nx/enforce-module-boundaries import { Database } from '@picsa/server-types'; -import { FileObject } from '@supabase/storage-js'; +import { FileObject, FileOptions } from '@supabase/storage-js'; import { SupabaseClient } from '@supabase/supabase-js'; import { PicsaNotificationService } from '../../notification.service'; @@ -81,6 +81,20 @@ export class SupabaseStorageService { return data?.[0] || null; } + public async putFile( + options: { bucketId: string; filename: string; fileBlob: Blob; folderPath?: string }, + fileOptions: FileOptions = { upsert: false } + ) { + const defaults = { folderPath: '' }; + const { bucketId, fileBlob, filename, folderPath } = { ...defaults, ...options }; + const filePath = folderPath ? `${folderPath}/${filename}` : `${filename}`; + const { data, error } = await this.storage.from(bucketId).upload(filePath, fileBlob, fileOptions); + if (error) { + throw error; + } + return data?.[0] || null; + } + /** Return the link to a file in a public bucket */ public getPublicLink(bucketId: string, objectPath: string) { return this.storage.from(bucketId).getPublicUrl(objectPath).data.publicUrl; From 80fa1a079fd9982455795b2ee1c52c4d601de9c7 Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 15 Feb 2024 15:15:07 -0800 Subject: [PATCH 32/33] feat: data-table context row and column --- .../src/features/data-table/data-table.component.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/shared/src/features/data-table/data-table.component.html b/libs/shared/src/features/data-table/data-table.component.html index 19cf6b8f5..cee4c71eb 100644 --- a/libs/shared/src/features/data-table/data-table.component.html +++ b/libs/shared/src/features/data-table/data-table.component.html @@ -17,20 +17,20 @@
{{ column }}{{ el[column] }}{{ column | formatValue: tableOptions.formatHeader }} + @if(valueTemplates[column]){ + + + } @else { + + {{ el[column] }} + } +
- @for(column of tableOptions.displayColumns; track column){ + @for(column of tableOptions.displayColumns; track column; let index=$index){ - From 06acb5978008c4d3672271b7c6faa8e0c7dbae5e Mon Sep 17 00:00:00 2001 From: chrismclarke Date: Thu, 15 Feb 2024 15:15:24 -0800 Subject: [PATCH 33/33] feat: forecast dashboard download and open --- .../pages/forecast/forecast.component.ts | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts index 5cdb2d468..a2b005ae2 100644 --- a/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts +++ b/apps/picsa-apps/dashboard/src/app/modules/climate/pages/forecast/forecast.component.ts @@ -4,14 +4,31 @@ import { RouterModule } from '@angular/router'; import { IDataTableOptions, PicsaDataTableComponent } from '@picsa/shared/features'; import { SupabaseService } from '@picsa/shared/services/core/supabase'; +import { DashboardMaterialModule } from '../../../../material.module'; import { ClimateService } from '../../climate.service'; import { DashboardClimateApiStatusComponent, IApiStatusOptions } from '../../components/api-status/api-status'; import { IForecastRow } from '../../types'; +const DISPLAY_COLUMNS: (keyof IForecastRow)[] = [ + 'country_code', + 'district', + 'type', + 'language_code', + 'filename', + 'date_modified', + 'storage_file', +]; + @Component({ selector: 'dashboard-climate-forecast', standalone: true, - imports: [CommonModule, DashboardClimateApiStatusComponent, RouterModule, PicsaDataTableComponent], + imports: [ + CommonModule, + DashboardClimateApiStatusComponent, + RouterModule, + PicsaDataTableComponent, + DashboardMaterialModule, + ], templateUrl: './forecast.component.html', styleUrls: ['./forecast.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -20,13 +37,15 @@ export class ClimateForecastPageComponent implements OnInit { public forecastData: IForecastRow[] = []; public tableOptions: IDataTableOptions = { - displayColumns: ['country', 'district', 'type', 'language', 'date_modified'], + displayColumns: DISPLAY_COLUMNS, }; public apiStatusOptions: IApiStatusOptions = { events: { refresh: () => this.refreshData() }, showStatusCode: false, }; + public activeDownloads: Record = {}; + constructor(private service: ClimateService, private supabase: SupabaseService, private cdr: ChangeDetectorRef) {} private get db() { @@ -46,6 +65,26 @@ export class ClimateForecastPageComponent implements OnInit { await this.refreshData(); } } + + public async handleStorageClick(row: IForecastRow) { + // handle download if storage file doesn't exist or hasn't been downloaded + if (!row.storage_file && this.activeDownloads[row.filename] !== 'complete') { + await this.downloadStorageFile(row); + } + // handle open + const storagePath = `climate/forecasts/${row.filename}`; + const publicLink = this.supabase.storage.getPublicLink('mw', storagePath); + open(publicLink, '_blank'); + } + + private async downloadStorageFile(row: IForecastRow) { + this.activeDownloads[row.filename] = 'pending'; + this.cdr.markForCheck(); + await this.service.loadFromAPI.forecast_file(row); + this.activeDownloads[row.filename] = 'complete'; + this.cdr.markForCheck(); + } + private loadForecastData(data: any[] = []) { this.forecastData = data; this.cdr.detectChanges();
{{ column | formatValue: tableOptions.formatHeader }} + @if(valueTemplates[column]){ } @else { - {{ el[column] }} + {{ row[column] }} }