Skip to content

Commit

Permalink
Merge pull request #233 from e-picsa/feat/dashboard-station-forecasts
Browse files Browse the repository at this point in the history
Feat/dashboard station forecasts
  • Loading branch information
chrismclarke authored Feb 15, 2024
2 parents 3103130 + 06acb59 commit ed51adf
Show file tree
Hide file tree
Showing 46 changed files with 1,130 additions and 326 deletions.
29 changes: 27 additions & 2 deletions apps/picsa-apps/dashboard/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,36 @@
<mat-sidenav-container style="flex: 1">
<mat-sidenav #sidenav mode="side" opened [fixedInViewport]="true" fixedTopGap="64">
<mat-nav-list>
@for (link of navLinks; track link.href) {
@for (link of navLinks; track link.href) { @if(link.children){
<!-- Nested nav items -->
<mat-expansion-panel class="mat-elevation-z0" [expanded]="rla.isActive">
<mat-expansion-panel-header
style="padding: 0 16px"
[routerLink]="link.href"
routerLinkActive
#rla="routerLinkActive"
[routerLinkActiveOptions]="{ exact: false }"
>
<mat-panel-title>
{{ link.label }}
</mat-panel-title>
</mat-expansion-panel-header>
@for(child of link.children || []; track $index){
<a
mat-list-item
[routerLink]="link.href + child.href"
routerLinkActive="mdc-list-item--activated active-link"
>
{{ child.label }}</a
>
}
</mat-expansion-panel>
} @else {
<!-- Single nav item -->
<a mat-list-item [routerLink]="link.href" routerLinkActive="mdc-list-item--activated active-link">
{{ link.label }}
</a>
}
} }
<mat-divider style="margin-top: auto"></mat-divider>
<div mat-subheader>Global Admin</div>
<mat-divider></mat-divider>
Expand Down
20 changes: 13 additions & 7 deletions apps/picsa-apps/dashboard/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DashboardMaterialModule } from './material.module';
interface INavLink {
label: string;
href: string;
isActive?: boolean;
children?: INavLink[];
}

@Component({
Expand All @@ -22,17 +22,23 @@ export class AppComponent implements AfterViewInit {
title = 'picsa-apps-dashboard';

navLinks: INavLink[] = [
// {
// label: 'Home',
// href: '',
// },
{
label: 'Resources',
href: '/resources',
},
{
label: 'Climate Data',
href: '/climate-data',
label: 'Climate',
href: '/climate',
children: [
{
label: 'Station Data',
href: '/station',
},
{
label: 'Forecasts',
href: '/forecast',
},
],
},
// {
// label: 'Crop Information',
Expand Down
4 changes: 2 additions & 2 deletions apps/picsa-apps/dashboard/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 3 additions & 1 deletion apps/picsa-apps/dashboard/src/app/material.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,6 +16,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
const matModules = [
MatButtonModule,
MatChipsModule,
MatExpansionModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
Expand All @@ -24,7 +26,7 @@ const matModules = [
MatStepperModule,
MatTableModule,
MatTabsModule,
MatToolbarModule
MatToolbarModule,
];

@NgModule({
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import type { SupabaseService } from '@picsa/shared/services/core/supabase';
import { SupabaseStorageService } from '@picsa/shared/services/core/supabase/services/supabase-storage.service';

import type { ClimateApiService } from './climate-api.service';
import { IClimateProductInsert, IClimateProductRow, IForecastInsert, IForecastRow, IStationRow } from './types';

export type IApiMapping = ReturnType<typeof ApiMapping>;
export type IApiMappingName = keyof IApiMapping;

// TODO - certain amount of boilerplate could be reduced
// TODO - depends on climate api updates
// TODO - most of these should be run on server as server functions

/**
* Mapping functions that handle processing of data loaded from API server endpoints,
* and populating entries to supabase DB
*/
export const ApiMapping = (api: ClimateApiService, db: SupabaseService['db'], storage: SupabaseStorageService) => {
return {
rainfallSummaries: async (country_code: string, station_id: number) => {
// TODO - add model type definitions for server rainfall summary response body
const { data, error } = await api
.getObservableClient(`rainfallSummary_${country_code}_${station_id}`)
.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'],
},
});
if (error) throw error;
console.log('summary data', data);
// TODO - gen types and handle mapping
const mappedData = data as any;
const { data: dbData, error: dbError } = await db
.table('climate_products')
.upsert<IClimateProductInsert>({
data: mappedData,
station_id,
type: 'rainfallSummary',
})
.select<'*', IClimateProductRow>('*');
if (dbError) throw dbError;
return dbData || [];
},
//
station: async () => {
const { data, error } = await api.getObservableClient('station').GET('/v1/station/');
if (error) throw error;
// TODO - fix climate api bindigns to avoid data.data
console.log('station data', data);
const dbData = data.map(
(d): IStationRow => ({
...d,
})
);
const { error: dbError } = await db.table('climate_stations').upsert<IStationRow>(dbData);
if (dbError) throw dbError;
return dbData;
},
//
forecasts: async (country_code: 'zm' | 'mw') => {
const { data, error } = await api
.getObservableClient(`forecasts/${country_code}`)
.GET(`/v1/forecasts/{country_code}`, { params: { path: { country_code } } });
if (error) throw error;
const forecasts = data.map((d): IForecastInsert => ({ ...d, country_code }));
const { error: dbError, data: dbData } = await db
.table('climate_forecasts')
.upsert<IForecastInsert>(forecasts)
.select<'*', IForecastRow>('*');
if (dbError) throw dbError;
return dbData || [];
},
forecast_file: async (row: IForecastRow) => {
const { country_code, filename } = row;
const { data, error } = await api
.getObservableClient(`forecasts/${filename}`)
.GET(`/v1/forecasts/{country_code}/{file_name}`, {
params: { path: { country_code: country_code as any, file_name: filename } },
parseAs: 'blob',
});
if (error) throw error;
// setup metadata
const fileBlob = data as Blob;
const bucketId = country_code as string;
const folderPath = 'climate/forecasts';
// upload to storage
await storage.putFile({ bucketId, fileBlob, filename, folderPath });
// TODO - handle error if filename already exists
const storageEntry = await storage.getFile({ bucketId, filename, folderPath });
if (storageEntry) {
const { error: dbError } = await db
.table('climate_forecasts')
.upsert<IForecastInsert>({ ...row, storage_file: storageEntry.id })
.select('*');
if (dbError) {
throw dbError;
}
return;
}
throw new Error('Storage file not found');
},
};
};
Loading

0 comments on commit ed51adf

Please sign in to comment.