WIP: DataSets Module

This commit is contained in:
2022-12-11 23:40:16 +01:00
parent d69d571393
commit 120abcaf1d
38 changed files with 1123 additions and 35 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "diuna",
"version": "0.0.2",
"version": "0.0.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "diuna",
"version": "0.0.2",
"version": "0.0.5",
"dependencies": {
"@abacritt/angularx-social-login": "^1.2.5",
"@angular/animations": "^15.0.3",
@@ -16,6 +16,7 @@
"@angular/core": "^15.0.3",
"@angular/forms": "^15.0.3",
"@angular/material": "^15.0.2",
"@angular/material-moment-adapter": "^15.0.2",
"@angular/platform-browser": "^15.0.3",
"@angular/platform-browser-dynamic": "^15.0.3",
"@angular/pwa": "^15.0.3",
@@ -24,6 +25,7 @@
"jwt-decode": "^3.1.2",
"moment": "^2.29.4",
"rxjs": "^7.6.0",
"uuid": "^9.0.0",
"zone.js": "^0.12.0"
},
"devDependencies": {
@@ -31,6 +33,7 @@
"@angular/cli": "~15.0.3",
"@angular/compiler-cli": "^15.0.3",
"@types/jasmine": "^4.3.1",
"@types/uuid": "^9.0.0",
"jasmine-core": "^4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
@@ -651,6 +654,19 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material-moment-adapter": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-15.0.2.tgz",
"integrity": "sha512-d74B4DpZrT89F2EMYcaXcvjp+SJ3gNdI8dVY9iCLHuK3Zy1RN1q1DIlg4qMaZYA+fjmRSu0fh+elRRw6wTVPOQ==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/core": "^15.0.0 || ^16.0.0",
"@angular/material": "15.0.2",
"moment": "^2.18.1"
}
},
"node_modules/@angular/platform-browser": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.0.3.tgz",
@@ -3753,6 +3769,12 @@
"@types/node": "*"
}
},
"node_modules/@types/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
"dev": true
},
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -10663,6 +10685,15 @@
"websocket-driver": "^0.7.4"
}
},
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/socks": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
@@ -11394,10 +11425,9 @@
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
"bin": {
"uuid": "dist/bin/uuid"
}
@@ -12339,6 +12369,14 @@
"tslib": "^2.3.0"
}
},
"@angular/material-moment-adapter": {
"version": "15.0.2",
"resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-15.0.2.tgz",
"integrity": "sha512-d74B4DpZrT89F2EMYcaXcvjp+SJ3gNdI8dVY9iCLHuK3Zy1RN1q1DIlg4qMaZYA+fjmRSu0fh+elRRw6wTVPOQ==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-15.0.3.tgz",
@@ -14781,6 +14819,12 @@
"@types/node": "*"
}
},
"@types/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
"dev": true
},
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -19922,6 +19966,14 @@
"faye-websocket": "^0.11.3",
"uuid": "^8.3.2",
"websocket-driver": "^0.7.4"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
}
}
},
"socks": {
@@ -20456,10 +20508,9 @@
"dev": true
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
},
"validate-npm-package-license": {
"version": "3.0.4",

View File

@@ -18,6 +18,7 @@
"@angular/core": "^15.0.3",
"@angular/forms": "^15.0.3",
"@angular/material": "^15.0.2",
"@angular/material-moment-adapter": "^15.0.2",
"@angular/platform-browser": "^15.0.3",
"@angular/platform-browser-dynamic": "^15.0.3",
"@angular/pwa": "^15.0.3",
@@ -26,6 +27,7 @@
"jwt-decode": "^3.1.2",
"moment": "^2.29.4",
"rxjs": "^7.6.0",
"uuid": "^9.0.0",
"zone.js": "^0.12.0"
},
"devDependencies": {
@@ -33,6 +35,7 @@
"@angular/cli": "~15.0.3",
"@angular/compiler-cli": "^15.0.3",
"@types/jasmine": "^4.3.1",
"@types/uuid": "^9.0.0",
"jasmine-core": "^4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
@@ -42,4 +45,4 @@
"tslib": "^2.4.1",
"typescript": "^4.8.4"
}
}
}

View File

@@ -18,6 +18,10 @@ const routes: Routes = [
path: '',
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule)
},
{
path: 'datasets',
loadChildren: () => import('./modules/data-sets/data-sets.module').then(m => m.DataSetsModule)
},
]
}
];

View File

@@ -1,6 +1,5 @@
import { NgModule, isDevMode } from '@angular/core';
import { NgModule, isDevMode, LOCALE_ID } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@@ -11,7 +10,12 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ServiceWorkerModule } from '@angular/service-worker';
import { LoaderInterceptor } from './interceptors/loader.interceptor';
import { AuthInterceptor } from './interceptors/auth.interceptor';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MomentDateAdapter } from '@angular/material-moment-adapter';
import { registerLocaleData } from '@angular/common';
import localePl from '@angular/common/locales/pl';
registerLocaleData(localePl, 'pl-PL');
@NgModule({
declarations: [
@@ -33,6 +37,9 @@ import { AuthInterceptor } from './interceptors/auth.interceptor';
})
],
providers: [
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] },
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } },
{ provide: LOCALE_ID, useValue: 'pl-PL' },
{
provide: HTTP_INTERCEPTORS,
useClass: LoaderInterceptor,

View File

@@ -1,21 +1,21 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { DataService } from '../services/data.service';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private data$: DataService,
private auth$: AuthService,
private router$: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (this.data$.currentUser) {
if (this.auth$.user && this.auth$.user.googleCredentials) {
return true;
} else {
this.router$.navigate(['']);

View File

@@ -1,15 +1,15 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { User } from '../models/user';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class AuthService {
apiToken: string | null = null;
user: User | null = null;
apiToken!: string;
user!: User;
constructor(
private http$: HttpClient
@@ -35,7 +35,7 @@ export class AuthService {
const header = new HttpHeaders().set('Content-type', 'application/json');
this.http$.post<any>(`${environment.api.url}/auth/apiToken`, JSON.stringify(credentials), { headers: header }).subscribe({
next: (data) => {
if (this.user) { this.user.id = data.id }
this.user.id = data.id;
this.apiToken = data.token;
resolve(data);
},

View File

@@ -2,7 +2,7 @@ import { Component, NgZone, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import jwt_decode from "jwt-decode";
import { AuthService } from 'src/app/auth/auth.service';
import { User } from 'src/app/models/user';
import { User } from 'src/app/models/user.model';
import { DataService } from 'src/app/services/data.service';
import { environment } from 'src/environments/environment';
@@ -44,7 +44,7 @@ export class LoginPageComponent implements OnInit {
async handleCredentialResponse(response: any) {
try {
const responsePayload: any = jwt_decode(response.credential);
this.data$.currentUser = new User({
this.auth$.user = new User({
googleCredentials: response.credential,
userName: `${responsePayload.given_name} ${responsePayload.family_name}`,
email: responsePayload.email,

View File

@@ -14,9 +14,9 @@
</button>
<h1 class="topbar-app-name">Diuna</h1>
<span class="fill-to-right"></span>
<span class="topbar-user-name" *ngIf="data$.currentUser">
<img *ngIf="data$.currentUser.avatar" src="{{data$.currentUser.avatar}}" class="avatar">
{{data$.currentUser.userName}}
<span class="topbar-user-name" *ngIf="auth$.user">
<img *ngIf="auth$.user.avatar" src="{{auth$.user.avatar}}" class="avatar">
{{auth$.user.userName}}
</span>
<button mat-icon-button (click)="logout()">
<mat-icon>exit_to_app</mat-icon>
@@ -25,14 +25,18 @@
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #snav fixedTopGap="56" mode="side" opened="true">
<mat-nav-list>
<a mat-list-item routerLink="./">
<a mat-list-item routerLink=".">
<mat-icon class="menu-icon">dashboard</mat-icon>
Dashboard
</a>
<a mat-list-item routerLink="Invoices">
<mat-icon class="menu-icon">request_quote</mat-icon>
<a mat-list-item routerLink="./datasets">
<mat-icon class="menu-icon">table</mat-icon>
Zbiory danych
</a>
<a mat-list-item routerLink=".">
<mat-icon class="menu-icon">insights</mat-icon>
Analiza MPK
</a>
<mat-divider></mat-divider>
<a mat-list-item (click)="reloadApp()">
<mat-icon class="menu-icon">refresh</mat-icon>

View File

@@ -5,6 +5,8 @@ import { SwUpdate } from '@angular/service-worker';
import * as moment from 'moment';
import packageInfo from 'package.json';
import { delay } from 'rxjs';
import { AuthService } from '../auth/auth.service';
import { User } from '../models/user.model';
import { DataService } from '../services/data.service';
import { DeviceService } from '../services/device.service';
@@ -26,7 +28,8 @@ export class MainViewComponent {
public device$: DeviceService,
private router$: Router,
private swUpdate$: SwUpdate,
private ngZone$: NgZone
private ngZone$: NgZone,
public auth$: AuthService
) {
this.swUpdate$.versionUpdates.subscribe(() => {
this.reloadApp();
@@ -38,7 +41,7 @@ export class MainViewComponent {
});
//listen to loading subject
data$.showLoader.subscribe(loading => {
data$.showLoader.pipe(delay(0)).subscribe(loading => {
this.ngZone$.run(() => {
this.loading = loading;
});
@@ -51,6 +54,6 @@ export class MainViewComponent {
// @ts-ignore
google.accounts.id.disableAutoSelect();
this.router$.navigate(['']);
this.data$.currentUser = null;
this.auth$.user = new User({});
}
}

View File

@@ -0,0 +1,33 @@
import { Moment } from 'moment';
import { Deserializable } from './deserializable.model';
import { Serializable } from './serializable.model';
import * as moment from 'moment';
import { User } from './user.model';
export class Base implements Deserializable, Serializable {
id?: string;
createdAt?: Moment;
modifiedAt?: Moment;
createdById?: string;
modifiedById?: string;
createdBy?: User;
modifiedBy?: User;
constructor(data: Partial<Base> = {}) {
Object.assign(this, data);
}
deserialize(input: any): this {
if (input.createdAt) { input.createdAt = moment(input.createdAt); }
if (input.modifiedAt) { input.modifiedAt = moment(input.modifiedAt); }
if (input.createdBy) { input.createdBy = new User(input.createdBy); }
if (input.modifiedBy) { input.modifiedBy = new User(input.modifiedBy); }
Object.assign(this, input);
return this;
}
serialize() : any {
return Object.assign({}, this);
}
}

View File

@@ -0,0 +1,26 @@
import { Base } from './base.model';
import { UntypedFormBuilder, Validators, UntypedFormGroup, FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { DataService } from '../services/data.service';
import { map } from 'rxjs';
export class DataRow extends Base {
mpk?: string;
value?: number;
desc1?: string;
desc2?: string;
desc3?: string;
desc4?: string;
desc5?: string;
constructor(data: Partial<DataRow> = {}) {
super();
Object.assign(this, data);
}
override deserialize(input: any): this {
Object.assign(this, Object.assign(this, super.deserialize(input)));
return this;
}
}

View File

@@ -0,0 +1,97 @@
import { Base } from './base.model';
import { UntypedFormBuilder, Validators, UntypedFormGroup } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { DataService } from '../services/data.service';
import { map } from 'rxjs';
import { DataRow } from './dataRow.model copy';
export class DataSet extends Base {
number?: string;
name?: string;
dataRows: DataRow[] = [];
constructor(data: Partial<DataSet> = {}) {
super();
Object.assign(this, data);
}
override deserialize(input: any): this {
Object.assign(this, Object.assign(this, super.deserialize(input)));
return this;
}
static getForm(fb: UntypedFormBuilder) {
return fb.group({
id: [null],
number: ['', Validators.required],
name: ['', Validators.required],
createdAt: '',
modifiedAt: '',
createdBy: '',
modifiedBy: '',
});
}
fillForm(form: UntypedFormGroup) {
form.patchValue(this);
form.patchValue({
createdBy: this.createdBy?.userName,
modifiedBy: this.modifiedBy?.userName
});
}
loadForm(form: UntypedFormGroup) {
for (let field of Object.keys(form.controls)) {
this[field as keyof DataSet] = form.controls[field].value;
}
this.createdBy = undefined;
this.modifiedBy = undefined;
}
//API Actions
/*
static add(input: Account, _http: HttpClient, _data: DataService): Promise<string> {
return new Promise((resolve, reject) => {
_http.post<string>(`${environment.api.url}/accounts`, {...input.serialize(), modifiedById: _data.currentUser.id }).subscribe({
next: (data) => resolve(data),
error: (e) => reject(e)
}
);
});
}
static update(input: Account, _http: HttpClient, _data: DataService): Promise<string> {
return new Promise((resolve, reject) => {
_http.patch<string>(`${environment.api.url}/accounts`, {...input.serialize(), modifiedById: _data.currentUser.id }).subscribe({
next: (data) => resolve(data),
error: (e) => reject(e)
}
);
});
}
*/
static getList(_http: HttpClient): any {
return new Promise((resolve, reject) => {
_http.get<DataSet[]>(`${environment.api.url}/datasets`)
.pipe(map(data => data.map(x => new DataSet().deserialize(x))))
.subscribe({
next: (data) => resolve(data),
error: (e) => reject(e)
})
});
}
static getById(id: string, _http: HttpClient): Promise<DataSet> {
return new Promise((resolve, reject) => {
_http.get<DataSet>(`${environment.api.url}/datasets/${id}`).pipe(map(x => new DataSet().deserialize(x))).subscribe({
next: (data) => resolve(data),
error: (e) => reject(e)
})
});
}
/*
static delete(id: string, _http: HttpClient): Promise<string> {
return new Promise((resolve, reject)=> {
_http.delete<string>(`${environment.api.url}/accounts/${id}`).subscribe({
next: (data) => resolve(data),
error: (e) => reject(e)
})
});
}
*/
}

View File

@@ -0,0 +1,3 @@
export interface Deserializable {
deserialize(input: any): this;
}

View File

@@ -0,0 +1,3 @@
export interface Serializable {
serialize(input: this): any;
}

View File

@@ -0,0 +1 @@
<p>data-set-detail works!</p>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataSetDetailComponent } from './data-set-detail.component';
describe('DataSetDetailComponent', () => {
let component: DataSetDetailComponent;
let fixture: ComponentFixture<DataSetDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DataSetDetailComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DataSetDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-data-set-detail',
templateUrl: './data-set-detail.component.html',
styleUrls: ['./data-set-detail.component.scss']
})
export class DataSetDetailComponent {
}

View File

@@ -0,0 +1,40 @@
<div>
<form [formGroup]="form" (ngSubmit)="save()" novalidate>
<mat-card appearance="outlined">
<mat-toolbar color="secondary">
Edycja zbioru danych
<span class="fill-to-right"></span>
<button mat-button type="submit" [disabled]="form.invalid">Zapisz</button>
<button mat-button type="button" routerLink="../..">Anuluj</button>
</mat-toolbar>
<mat-card-content>
<mat-grid-list cols="2" rowHeight="90px">
<mat-grid-tile>
<mat-form-field class="detail-input">
<mat-label>Numer</mat-label>
<input matInput formControlName="number">
<mat-error *ngIf="form.controls['number'].touched && form.controls['number'].invalid">
Pole obowiązkowe
</mat-error>
</mat-form-field>
</mat-grid-tile>
<mat-grid-tile>
<mat-form-field class="detail-input">
<mat-select placeholder="Źródło" formControlName="name" (selectionChange)="generateNumber()">
<mat-option value="Import ręczny">Import ręczny</mat-option>
<mat-option value="Arkusz Google">Arkusz Google</mat-option>
<mat-option value="SAP">SAP</mat-option>
<mat-option value="Symfonia">Symfonia</mat-option>
</mat-select>
<mat-error *ngIf="form.controls['name'].touched && form.controls['name'].invalid">
Pole obowiązkowe
</mat-error>
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
</mat-card-content>
</mat-card>
</form>
</div>

View File

@@ -0,0 +1 @@
@import "../../../main-view/main-view.component.scss";

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataSetEditComponent } from './data-set-edit.component';
describe('DataSetEditComponent', () => {
let component: DataSetEditComponent;
let fixture: ComponentFixture<DataSetEditComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DataSetEditComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DataSetEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,68 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Router, ActivatedRoute } from '@angular/router';
import moment from 'moment';
import { AuthService } from 'src/app/auth/auth.service';
import { DataSet } from 'src/app/models/dataSet.model';
import { DataService } from 'src/app/services/data.service';
import { v4 as uuidv4 } from 'uuid';
@Component({
selector: 'app-data-set-edit',
templateUrl: './data-set-edit.component.html',
styleUrls: ['./data-set-edit.component.scss']
})
export class DataSetEditComponent implements OnInit {
public form!: UntypedFormGroup;
private document!: DataSet;
constructor(
private fb$: UntypedFormBuilder,
private router$: Router,
private http$: HttpClient,
private data$: DataService,
private route$: ActivatedRoute,
private dialog$: MatDialog,
private auth$: AuthService
) { }
async ngOnInit() {
this.form = DataSet.getForm(this.fb$);
this.document = await this.load();
this.document.fillForm(this.form);
}
private load(): Promise<DataSet> {
return new Promise((resolve) => {
const id = this.route$.snapshot.paramMap.get('id') || "";
if (this.route$.snapshot.paramMap.get('id') === 'new') {
resolve(new DataSet({ id: uuidv4(), createdById: this.auth$.user.id, createdAt: moment(),
modifiedAt: moment() })) // new element
return;
}
resolve(DataSet.getById(id, this.http$))
});
}
async save() {
if (this.form.invalid) {
return;
}
/*
this.document.loadForm(this.form);
let id;
if (this.route$.snapshot.paramMap.get('id') === 'new') {
id = await DataSet.add(this.document, this.http$, this._data);
} else {
id = await DataSet.update(this.document, this._http, this._data);
}
this._router.navigate(['../../Detail', id], { relativeTo: this._route});
*/
}
generateNumber() {
this.form.patchValue({
number: `${this.form.controls['name'].value}-${moment().format("YYYY")}-${moment().format("MM")}`
})
}
}

View File

@@ -0,0 +1,38 @@
<div class="list-container mat-elevation-z8">
<div class="list-header">
<mat-grid-list cols="10" rowHeight="60">
<mat-grid-tile>
<button mat-button routerLink="Edit/new">
Dodaj
</button>
</mat-grid-tile>
<mat-grid-tile [colspan]="8">
<mat-form-field class="searchListInput">
<mat-label>Filtruj</mat-label>
<input matInput (keyup)="applyFilter($event)">
</mat-form-field>
</mat-grid-tile>
</mat-grid-list>
</div>
<mat-table #table [dataSource]="dataSource" [trackBy]="trackByUid" matSort class="animate">
<ng-container matColumnDef="number">
<mat-header-cell *matHeaderCellDef mat-sort-header>Numer</mat-header-cell>
<mat-cell *matCellDef="let item">{{item.number}}</mat-cell>
</ng-container>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Źródło</mat-header-cell>
<mat-cell *matCellDef="let item">{{item.name}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let item; columns: displayedColumns;" [routerLink]="['Detail/', item.id]"></mat-row>
</mat-table>
<mat-paginator #paginator
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]">
</mat-paginator>
</div>

View File

@@ -0,0 +1 @@
@import "../../../main-view/main-view.component.scss";

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataSetsListComponent } from './data-sets-list.component';
describe('DataSetsListComponent', () => {
let component: DataSetsListComponent;
let fixture: ComponentFixture<DataSetsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DataSetsListComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(DataSetsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,37 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { DataSet } from 'src/app/models/dataSet.model';
@Component({
selector: 'app-data-sets-list',
templateUrl: './data-sets-list.component.html',
styleUrls: ['./data-sets-list.component.scss']
})
export class DataSetsListComponent implements OnInit {
displayedColumns = ['number', 'name'];
dataSource!: MatTableDataSource<DataSet>;
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
constructor(
private _http: HttpClient
) { }
async ngOnInit() {
this.dataSource = new MatTableDataSource(await DataSet.getList(this._http));
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
trackByUid(index : number, item : DataSet) {
return item.id;
}
}

View File

@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DataSetDetailComponent } from './data-set-detail/data-set-detail.component';
import { DataSetEditComponent } from './data-set-edit/data-set-edit.component';
import { DataSetsListComponent } from './data-sets-list/data-sets-list.component';
const routes: Routes = [
{ path: '', component: DataSetsListComponent },
{ path: 'Edit/:id', component: DataSetEditComponent },
{ path: 'Detail/:id', component: DataSetDetailComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DataSetsRoutingModule { }

View File

@@ -0,0 +1,24 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DataSetsRoutingModule } from './data-sets-routing.module';
import { DataSetsListComponent } from './data-sets-list/data-sets-list.component';
import { MaterialModule } from 'src/app/material.module';
import { DataSetDetailComponent } from './data-set-detail/data-set-detail.component';
import { DataSetEditComponent } from './data-set-edit/data-set-edit.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
DataSetsListComponent,
DataSetDetailComponent,
DataSetEditComponent
],
imports: [
CommonModule,
DataSetsRoutingModule,
MaterialModule,
FormsModule,
ReactiveFormsModule
]
})
export class DataSetsModule { }

View File

@@ -1,12 +1,11 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { User } from '../models/user';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class DataService {
currentUser?: User | null;
public showLoader: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor() { }

View File

@@ -5,8 +5,8 @@
export const environment = {
production: false,
api: {
//url: "http://localhost:5400/api"
url: "https://diuna.bim-it.pl/api"
url: "http://localhost:5400/api"
//url: "https://diuna.bim-it.pl/api"
},
google: {
clientId: "107631825312-bkfe438ehr9k9ecb2h76g802tj6advma.apps.googleusercontent.com"

View File

@@ -7,6 +7,8 @@ namespace WebAPI
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<DataSet> DataSets { get; set; }
public DbSet<DataRow> DataRows { get; set; }
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{

View File

@@ -0,0 +1,37 @@
using Google.Apis.Auth;
using Google.Apis.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos;
using Microsoft.IdentityModel.Tokens;
using System.Configuration;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebAPI.Models;
namespace WebAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class DataSetsController : Controller
{
private readonly AppDbContext db;
public DataSetsController(AppDbContext _db) { db = _db; }
[HttpGet]
public IActionResult GetAll()
{
try
{
return Ok(db.DataSets.Where(x => !x.IsDeleted).ToList());
}
catch (Exception e)
{
return BadRequest(e.ToString());
}
}
}
}

View File

@@ -0,0 +1,192 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using WebAPI;
#nullable disable
namespace WebAPI.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20221211210507_DataSetsAndDataRows")]
partial class DataSetsAndDataRows
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("WebAPI.Models.DataRow", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid>("CreatedById")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("DataSetId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Desc1")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc2")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc3")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc4")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc5")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MPK")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<Guid>("ModifiedById")
.HasColumnType("uniqueidentifier");
b.Property<float>("Value")
.HasColumnType("real");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("DataSetId");
b.HasIndex("ModifiedById");
b.ToTable("DataRows");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid>("CreatedById")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<Guid>("ModifiedById")
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<string>("Number")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("ModifiedById");
b.ToTable("DataSets");
});
modelBuilder.Entity("WebAPI.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Email")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserName")
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("WebAPI.Models.DataRow", b =>
{
b.HasOne("WebAPI.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAPI.Models.DataSet", null)
.WithMany("DataRows")
.HasForeignKey("DataSetId");
b.HasOne("WebAPI.Models.User", "ModifiedBy")
.WithMany()
.HasForeignKey("ModifiedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy");
b.Navigation("ModifiedBy");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.HasOne("WebAPI.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAPI.Models.User", "ModifiedBy")
.WithMany()
.HasForeignKey("ModifiedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy");
b.Navigation("ModifiedBy");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.Navigation("DataRows");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace WebAPI.Migrations
{
/// <inheritdoc />
public partial class DataSetsAndDataRows : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DataSets",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Number = table.Column<string>(type: "nvarchar(max)", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
CreatedById = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ModifiedById = table.Column<Guid>(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DataSets", x => x.Id);
table.ForeignKey(
name: "FK_DataSets_Users_CreatedById",
column: x => x.CreatedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_DataSets_Users_ModifiedById",
column: x => x.ModifiedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
migrationBuilder.CreateTable(
name: "DataRows",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
MPK = table.Column<string>(type: "nvarchar(max)", nullable: false),
Value = table.Column<float>(type: "real", nullable: false),
Desc1 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Desc2 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Desc3 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Desc4 = table.Column<string>(type: "nvarchar(max)", nullable: true),
Desc5 = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ModifiedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: false),
CreatedById = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ModifiedById = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
DataSetId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataRows", x => x.Id);
table.ForeignKey(
name: "FK_DataRows_DataSets_DataSetId",
column: x => x.DataSetId,
principalTable: "DataSets",
principalColumn: "Id");
table.ForeignKey(
name: "FK_DataRows_Users_CreatedById",
column: x => x.CreatedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_DataRows_Users_ModifiedById",
column: x => x.ModifiedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
migrationBuilder.CreateIndex(
name: "IX_DataRows_CreatedById",
table: "DataRows",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_DataRows_DataSetId",
table: "DataRows",
column: "DataSetId");
migrationBuilder.CreateIndex(
name: "IX_DataRows_ModifiedById",
table: "DataRows",
column: "ModifiedById");
migrationBuilder.CreateIndex(
name: "IX_DataSets_CreatedById",
table: "DataSets",
column: "CreatedById");
migrationBuilder.CreateIndex(
name: "IX_DataSets_ModifiedById",
table: "DataSets",
column: "ModifiedById");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DataRows");
migrationBuilder.DropTable(
name: "DataSets");
}
}
}

View File

@@ -22,6 +22,100 @@ namespace WebAPI.Migrations
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("WebAPI.Models.DataRow", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid>("CreatedById")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("DataSetId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Desc1")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc2")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc3")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc4")
.HasColumnType("nvarchar(max)");
b.Property<string>("Desc5")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("MPK")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<Guid>("ModifiedById")
.HasColumnType("uniqueidentifier");
b.Property<float>("Value")
.HasColumnType("real");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("DataSetId");
b.HasIndex("ModifiedById");
b.ToTable("DataRows");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<Guid>("CreatedById")
.HasColumnType("uniqueidentifier");
b.Property<bool>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime>("ModifiedAt")
.HasColumnType("datetime2");
b.Property<Guid>("ModifiedById")
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<string>("Number")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("CreatedById");
b.HasIndex("ModifiedById");
b.ToTable("DataSets");
});
modelBuilder.Entity("WebAPI.Models.User", b =>
{
b.Property<Guid>("Id")
@@ -42,6 +136,53 @@ namespace WebAPI.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("WebAPI.Models.DataRow", b =>
{
b.HasOne("WebAPI.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAPI.Models.DataSet", null)
.WithMany("DataRows")
.HasForeignKey("DataSetId");
b.HasOne("WebAPI.Models.User", "ModifiedBy")
.WithMany()
.HasForeignKey("ModifiedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy");
b.Navigation("ModifiedBy");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.HasOne("WebAPI.Models.User", "CreatedBy")
.WithMany()
.HasForeignKey("CreatedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebAPI.Models.User", "ModifiedBy")
.WithMany()
.HasForeignKey("ModifiedById")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreatedBy");
b.Navigation("ModifiedBy");
});
modelBuilder.Entity("WebAPI.Models.DataSet", b =>
{
b.Navigation("DataRows");
});
#pragma warning restore 612, 618
}
}

31
WebAPI/Models/DataRow.cs Normal file
View File

@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations;
namespace WebAPI.Models
{
public class DataRow
{
#region Properties
[Key]
public Guid Id { get; set; }
[Required]
public string? MPK { get; set; }
[Required]
public float Value { get; set; }
//Description fields
public string? Desc1 { get; set; }
public string? Desc2 { get; set; }
public string? Desc3 { get; set; }
public string? Desc4 { get; set; }
public string? Desc5 { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
public bool IsDeleted { get; set; }
#endregion
#region Relations
public Guid CreatedById { get; set; }
public User? CreatedBy { get; set; }
public Guid ModifiedById { get; set; }
public User? ModifiedBy { get; set; }
#endregion
}
}

25
WebAPI/Models/DataSet.cs Normal file
View File

@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
namespace WebAPI.Models
{
public class DataSet
{
#region Properties
[Key]
public Guid Id { get; set; }
[Required]
public string? Number { get; set; }
public string? Name { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
public bool IsDeleted { get; set; }
#endregion
#region Relations
public ICollection<DataRow>? DataRows { get; set; }
public Guid CreatedById { get; set; }
public User? CreatedBy { get; set; }
public Guid ModifiedById { get; set; }
public User? ModifiedBy { get; set; }
#endregion
}
}