Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard] Crop Probability Module #232

Merged
merged 40 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
02415ac
chore: app build
FaithDaka Jan 18, 2024
dffdebd
feat: crop-information module
FaithDaka Jan 18, 2024
e948d5e
feat: sidebar link and routing
FaithDaka Jan 18, 2024
520a325
feat: v1 crop information dashboard module
FaithDaka Jan 18, 2024
3a5405a
Merge branch 'main' of https://github.com/e-picsa/picsa-apps into ft-…
FaithDaka Jan 31, 2024
c3005ea
chore: updating file structure
FaithDaka Jan 31, 2024
9c84a81
Merge branch 'main' of https://github.com/e-picsa/picsa-apps into ft-…
FaithDaka Feb 12, 2024
78e64c6
fix: file paths
FaithDaka Feb 12, 2024
45ab6d2
server: station crop information table
FaithDaka Feb 12, 2024
3c10703
server: station data table
FaithDaka Feb 12, 2024
fcbd239
server: crop data table
FaithDaka Feb 12, 2024
240c986
types: crop probability type setting
FaithDaka Feb 12, 2024
0c5d05b
feat: crop probability dashboard module
FaithDaka Feb 12, 2024
1dd0c7b
fix: update crop probability module routes
FaithDaka Feb 13, 2024
f5ed638
feat: crop probability dashboard module
FaithDaka Feb 13, 2024
de8d379
feat: new crop probability entry module
FaithDaka Feb 13, 2024
013917e
fix: schema typo
chrismclarke Feb 15, 2024
c61195b
refactor: crop_data table columns
chrismclarke Feb 15, 2024
7d7a057
chore: remove station_data_table
chrismclarke Feb 15, 2024
353da7e
refactor: station_crop_data table
chrismclarke Feb 15, 2024
b4a6fc9
chore: export updated types
chrismclarke Feb 15, 2024
06b9423
chore: rename routes
chrismclarke Feb 15, 2024
245fc71
chore: update table links
chrismclarke Feb 15, 2024
1ad4f4f
chore: code tidying
chrismclarke Feb 15, 2024
f7b42e5
Merge branch 'main' into ft-dashboard-crop-information
chrismclarke Feb 15, 2024
5f2d869
Merge branch 'main' of https://github.com/e-picsa/picsa-apps into ft-…
FaithDaka Mar 1, 2024
b6157aa
fix: Merge conflicts
FaithDaka Mar 1, 2024
c7fafc7
fix: resolve merge conflicts
FaithDaka Mar 1, 2024
b3ca16c
fix: update links to match
FaithDaka Mar 1, 2024
a45618d
fix: updated table functions
FaithDaka Mar 10, 2024
71b70d1
refactor: use picsa data table service
FaithDaka Mar 10, 2024
4ae3ac8
refactor: entry component uses form builder
FaithDaka Mar 10, 2024
6f9488e
file formatted
FaithDaka Mar 10, 2024
0cfed3e
feat: entry input validators
FaithDaka Mar 10, 2024
6b0e503
fix: import paths
FaithDaka Mar 10, 2024
a00447d
chore: navigate back after crop add
chrismclarke Mar 11, 2024
3ff5559
chore: add form types
chrismclarke Mar 12, 2024
30f377f
chore: code tidying
chrismclarke Mar 12, 2024
283070e
feat: crop info edit
chrismclarke Mar 12, 2024
e75b174
feat: enable shared form components in dashboard
chrismclarke Mar 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/picsa-apps/dashboard/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router';
import { SupabaseService } from '@picsa/shared/services/core/supabase';

import { DashboardMaterialModule } from './material.module';
import { CropInformationModule } from './modules/crop-information/crop-information.module';

interface INavLink {
label: string;
Expand All @@ -13,7 +14,7 @@ interface INavLink {

@Component({
standalone: true,
imports: [RouterModule, DashboardMaterialModule, CommonModule],
imports: [RouterModule, DashboardMaterialModule, CommonModule, CropInformationModule],
selector: 'dashboard-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
Expand Down Expand Up @@ -46,6 +47,10 @@ export class AppComponent implements AfterViewInit {
label: 'Translations',
href: '/translations',
},
{
label: 'Crop Probability',
href: '/crop-probability',
},
];

globalLinks: INavLink[] = [
Expand Down
4 changes: 4 additions & 0 deletions apps/picsa-apps/dashboard/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ export const appRoutes: Route[] = [
redirectTo: 'resources',
pathMatch: 'full',
},
{
path: 'crop-probability',
loadChildren: () => import('./modules/crop-information/crop-information.module').then((m) => m.CropInformationModule),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<div class="page-content">
<div style="display: flex; align-items: center">
<h2 style="flex: 1">Crop Probabilities</h2>
<button mat-raised-button color="primary" (click)="navigateToEntryPage()">Add New Entry</button>
</div>
@if(service.cropProbabilities){
<table mat-table [dataSource]="service.cropProbabilities">
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
<ng-container matColumnDef="station_name" clickable>
<th mat-header-cell *matHeaderCellDef>Station Name</th>
<td mat-cell *matCellDef="let element">{{ element.station_name }}</td>
</ng-container>
<ng-container matColumnDef="water_lower">
<th mat-header-cell *matHeaderCellDef>Water Lower</th>
<td mat-cell *matCellDef="let element">{{ element.water_lower }}</td>
</ng-container>
<ng-container matColumnDef="water_upper">
<th mat-header-cell *matHeaderCellDef>Water Upper</th>
<td mat-cell *matCellDef="let element">{{ element.water_upper }}</td>
</ng-container>
<ng-container matColumnDef="length_lower">
<th mat-header-cell *matHeaderCellDef>Length Lower</th>
<td mat-cell *matCellDef="let element">{{ element.length_lower }}</td>
</ng-container>
<ng-container matColumnDef="length_upper">
<th mat-header-cell *matHeaderCellDef>Length Upper</th>
<td mat-cell *matCellDef="let element">{{ element.length_upper }}</td>
</ng-container>
<ng-container matColumnDef="station_notes">
<th mat-header-cell *matHeaderCellDef>Station Notes</th>
<td mat-cell *matCellDef="let element">{{ element.station_notes }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { CropInformationPageComponent } from './crop-information.page';
import { NewEntryPageComponent } from './pages/new_entry/new_entry.page';

@NgModule({
declarations: [],
imports: [
CommonModule,
RouterModule.forChild([
{
path: '',
component: CropInformationPageComponent,
},
{
path: 'entry',
component: NewEntryPageComponent,
},
]),
],
})
export class CropInformationModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import '@uppy/core/dist/style.min.css';
import '@uppy/dashboard/dist/style.min.css';

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { DashboardMaterialModule } from '../../material.module';
import { CropProbabilityDashboardService } from './crop-information.service';

@Component({
selector: 'dashboard-resources-page',
standalone: true,
imports: [CommonModule, DashboardMaterialModule],
templateUrl: '../crop-information/crop-information.component.html',
styleUrls: ['../crop-information/crop-information.component.scss'],
})
export class CropInformationPageComponent implements OnInit {
constructor(public service: CropProbabilityDashboardService, private router: Router) {}

displayedColumns: string[] = [
'station_name',
'water_lower',
'water_upper',
'length_lower',
'length_upper',
'station_notes',
];

ngOnInit(): void {
this.service.ready();
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
this.refreshCropInformation();
}

refreshCropInformation() {
this.service.listCropProbabilities().catch((error) => {
console.error('Error fetching crop probabilities:', error);
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
});
}

navigateToEntryPage(){
this.router.navigate(['/crop-probability/entry'])
}
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { Database } from '@picsa/server-types';
import { PicsaAsyncService } from '@picsa/shared/services/asyncService.service';
import { SupabaseService } from '@picsa/shared/services/core/supabase';
import { IStorageEntry } from '@picsa/shared/services/core/supabase/services/supabase-storage.service';

export type ICropInformationRow = Database['public']['Tables']['station_crop_information']['Row'];

export interface IResourceStorageEntry extends IStorageEntry {
/** Url generated when upload to public bucket (will always be populated, even if bucket not public) */
publicUrl: string;
}

@Injectable({ providedIn: 'root' })
export class CropProbabilityDashboardService extends PicsaAsyncService {
public cropProbabilities: ICropInformationRow[] = [];

public get table() {
return this.supabaseService.db.table('station_crop_information');
}

constructor(private supabaseService: SupabaseService) {
super();
}

public override async init() {
await this.supabaseService.ready();
await this.listCropProbabilities();
}

public async listCropProbabilities() {
const { data, error } = await this.supabaseService.db
.table('station_crop_information')
.select<'*', ICropInformationRow>('*');
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
if (error) {
throw error;
}
this.cropProbabilities = data || [];
}

public async addCropProbability(cropProbability: Partial<ICropInformationRow>): Promise<string> {
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
const { data, error } = await this.supabaseService.db.table('station_crop_information').insert([cropProbability]);
if (error) {
throw error;
}
return 'Added successfully';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="page-content">
<button mat-stroked-button color="primary" (click)="routeBack()"><mat-icon> Back </mat-icon></button>
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
<h2>New Crop Probability Entry</h2>

<form class="form-content">
<div class="form-data">
<label>Station Name</label>
<input matInput [(ngModel)]="station_name" name="station_name" />
</div>
<div class="form-data">
<label>Water Upper</label>
<input matInput [(ngModel)]="water_upper" name="water_upper" />
</div>
<div class="form-data">
<label>Water Lower</label>
<input matInput [(ngModel)]="water_lower" name="water_lower" />
</div>
<div class="form-data">
<label>Length Upper</label>
<input matInput [(ngModel)]="length_upper" name="length_upper" />
</div>
<div class="form-data">
<label>Length Lower</label>
<input matInput [(ngModel)]="length_lower" name="length_lower" />
</div>
<div class="form-data">
<label>Station Notes</label>
<input matInput [(ngModel)]="station_notes" name="station_notes" />
</div>
<button mat-raised-button color="primary" class="submitButton" (click)="submitForm()">Submit</button>
</form>
@if(ActionFeedbackMessage){
<div>{{ ActionFeedbackMessage }}</div>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.form-content{
border: 0.6px solid #eeeeee;
display: flex;
flex-direction: column;
.form-data{
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
flex-wrap: wrap;
padding: 12px 0;
border:0.6px solid #eeeeee ;
label{
font-weight: 500;
}
input{
border: 1px solid rgb(119, 127, 122);
height:30px;
border-radius: 6px;
width: 180px;
padding: 0 12px;
}
}
button{
margin: 20px 0;
display: flex;
align-self: center;
border: 0px;
width: 180px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NewEntryPageComponent } from './new_entry.page';

describe('NewEntryComponent', () => {
let component: NewEntryPageComponent;
let fixture: ComponentFixture<NewEntryPageComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NewEntryPageComponent],
}).compileComponents();

fixture = TestBed.createComponent(NewEntryPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { Router } from '@angular/router';

import { DashboardMaterialModule } from '../../../../material.module';
import { CropProbabilityDashboardService } from '../../crop-information.service';

@Component({
selector: 'dashboard-new-entry',
standalone: true,
imports: [CommonModule, DashboardMaterialModule, RouterModule, FormsModule, ReactiveFormsModule],
templateUrl: './new_entry.component.html',
styleUrls: ['./new_entry.component.scss'],
})
export class NewEntryPageComponent implements OnInit {
station_name: string;
water_lower: number;
water_upper: number;
length_lower: number;
length_upper: number;
station_notes: string[];
chrismclarke marked this conversation as resolved.
Show resolved Hide resolved
ActionFeedbackMessage: string;
constructor(private service: CropProbabilityDashboardService, private router: Router) {
this.service.ready();
}
ngOnInit(): void {
this.service.ready();
}
submitForm() {
const data = {
station_name: this.station_name,
water_lower: this.water_lower,
water_upper: this.water_upper,
length_lower: this.length_lower,
length_upper: this.length_upper,
station_notes: this.station_notes,
};
this.service
.addCropProbability(data)
.then((data) => {
if (data === 'Added successfully') {
this.ActionFeedbackMessage = 'Added successfully';
}
})
.catch((error) => {
console.error('Error adding translation:', error);
this.ActionFeedbackMessage = 'Failed to add a translation.';
});
}
routeBack(){
this.router.navigate(['/crop-probability'])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
create table
public.station_crop_information (
id bigint generated by default as identity,
created_at timestamp with time zone not null default now(),
station_name text null,
water_lower double precision null,
water_upper double precision null,
length_lower double precision null,
length_upper double precision null,
station_notes text[] null,
constraint station_crop_information_pkey primary key (id)
) tablespace pg_default;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
create table
public.station_data (
id bigint generated by default as identity,
created_at timestamp with time zone not null default now(),
crop_name text null,
station bigint null,
constraint station_data_pkey primary key (id),
constraint station_data_station_fkey foreign key (station) references station_crop_information (id)
) tablespace pg_default;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
create table
public.crop_data (
id bigint generated by default as identity,
created_at timestamp with time zone not null default now(),
variety text null,
days text null,
water _upper double precision null,
water _lower double precision null,
length_lower double precision null,
length_upper double precision null,
station_data bigint null,
constraint crop_data_pkey primary key (id),
constraint crop_data_station_data_fkey foreign key (station_data) references station_data (id)
) tablespace pg_default;
Loading
Loading