Add requirements list and editing, fix type issues

Added suport for numbers and dates
This commit is contained in:
Norbi Peti 2022-05-14 14:56:06 +02:00
parent 744cc29346
commit ee4fee004d
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
11 changed files with 107 additions and 32 deletions

View file

@ -15,13 +15,13 @@ export class FulfillmentModeList implements ListResponse<FulfillmentMode> {
export class FulfillmentModeCreateInput implements Omit<DataObject<FulfillmentMode>, 'requirements' | 'courseId'> { export class FulfillmentModeCreateInput implements Omit<DataObject<FulfillmentMode>, 'requirements' | 'courseId'> {
@field() @field()
name: string; name: string;
@field() @field(returns => Int)
threshold2: number; threshold2: number;
@field() @field(returns => Int)
threshold3: number; threshold3: number;
@field() @field(returns => Int)
threshold4: number; threshold4: number;
@field() @field(returns => Int)
threshold5: number; threshold5: number;
@field(returns => ID) @field(returns => ID)
courseId: number; courseId: number;

View file

@ -1,6 +1,6 @@
import { ListResponse } from './list'; import { ListResponse } from './list';
import { Requirement } from '../models'; import { Requirement } from '../models';
import { field, inputType, Int, objectType } from '@loopback/graphql'; import { field, ID, inputType, Int, objectType } from '@loopback/graphql';
import { DataObject } from '@loopback/repository'; import { DataObject } from '@loopback/repository';
@objectType() @objectType()
@ -12,21 +12,24 @@ export class RequirementList implements ListResponse<Requirement> {
} }
@inputType() @inputType()
export class RequirementCreateInput implements Pick<DataObject<Requirement>, 'name' | 'description' | 'deadline' | 'minPoints' | 'maxPoints'> { export class RequirementCreateInput implements Pick<DataObject<Requirement>, 'name' | 'description' | 'deadline' | 'minPoints' | 'maxPoints' | 'fulfillmentModeId'> {
@field() @field()
deadline: Date; deadline: Date;
@field() @field()
name: string; name: string;
@field() @field()
description: string; description: string;
@field() @field(returns => Int)
minPoints: number; minPoints: number;
@field() @field(returns => Int)
maxPoints: number; maxPoints: number;
@field(returns => ID)
fulfillmentModeId: number;
} }
@inputType() @inputType()
export class RequirementUpdateInput extends RequirementCreateInput { export class RequirementUpdateInput extends RequirementCreateInput {
@field(returns => Int) @field(returns => ID)
id: number; id: number;
} }

View file

@ -1,6 +1,6 @@
import { Entity, hasMany, model, property } from '@loopback/repository'; import { Entity, hasMany, model, property } from '@loopback/repository';
import { Requirement } from './requirement.model'; import { Requirement } from './requirement.model';
import { field, ID, objectType } from '@loopback/graphql'; import { field, ID, Int, objectType } from '@loopback/graphql';
@model() @model()
@objectType() @objectType()
@ -24,28 +24,28 @@ export class FulfillmentMode extends Entity {
type: 'number', type: 'number',
required: true, required: true,
}) })
@field() @field(returns => Int)
threshold2: number; threshold2: number;
@property({ @property({
type: 'number', type: 'number',
required: true, required: true,
}) })
@field() @field(returns => Int)
threshold3: number; threshold3: number;
@property({ @property({
type: 'number', type: 'number',
required: true, required: true,
}) })
@field() @field(returns => Int)
threshold4: number; threshold4: number;
@property({ @property({
type: 'number', type: 'number',
required: true, required: true,
}) })
@field() @field(returns => Int)
threshold5: number; threshold5: number;
@property({ @property({

View file

@ -2,10 +2,20 @@
<form [formGroup]="formGroup"> <form [formGroup]="formGroup">
<div *ngFor="let field of fields"> <div *ngFor="let field of fields">
<mat-label>{{ field.title }}</mat-label> <mat-label>{{ field.title }}</mat-label>
<span [ngSwitch]="getType(item[field.name])"> <span [ngSwitch]="getType(field, item[field.name])">
<span *ngSwitchCase="'boolean'"> <span *ngSwitchCase="'boolean'">
<mat-checkbox [checked]="item[field.name]" [formControlName]="field.name"></mat-checkbox> <mat-checkbox [checked]="item[field.name]" [formControlName]="field.name"></mat-checkbox>
</span> </span>
<span *ngSwitchCase="'integer'">
<mat-form-field>
<input matInput [formControlName]="field.name" type="number" [value]="item[field.name]"/>
</mat-form-field>
</span>
<span *ngSwitchCase="'date'">
<mat-form-field>
<mat-datepicker [formControlName]="field.name" [ngModel]="item[field.name]"></mat-datepicker>
</mat-form-field>
</span>
<mat-form-field *ngSwitchDefault> <mat-form-field *ngSwitchDefault>
<input matInput [formControlName]="field.name" type="text" [value]="item[field.name]"/> <input matInput [formControlName]="field.name" type="text" [value]="item[field.name]"/>
</mat-form-field> </mat-form-field>

View file

@ -15,13 +15,13 @@ export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extend
implements OnInit { implements OnInit {
item?: T; item?: T;
creating = false; @Input() creating = false;
isLoading = true; isLoading = true;
@Input() gql: Query<QT, HasID>; @Input() gql: Query<QT, HasID>;
@Input() updateMutation: Mutation<UT, MutationInput<MIU, T>>; @Input() updateMutation: Mutation<UT, MutationInput<MIU, T>>;
@Input() createMutation: Mutation<CT, MutationInput<MIC, T>>; @Input() createMutation: Mutation<CT, MutationInput<MIC, T>>;
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[]; @Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean, type?: string }[];
@Input() itemType: T; @Input() itemType: T;
/** /**
* Beküldés előtt extra adat hozzáadása * Beküldés előtt extra adat hozzáadása
@ -42,7 +42,7 @@ export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extend
const url = this.route.snapshot.url; const url = this.route.snapshot.url;
this.item = this.customItem; this.item = this.customItem;
this.id = this.item?.id ?? this.gql ? url[url.length - 1].path : 'new'; this.id = this.item?.id ?? this.gql ? url[url.length - 1].path : 'new';
if (!this.item && this.id !== 'new' && this.gql) { if (!this.item && this.id !== 'new' && this.gql && !this.creating) {
const data = (await this.gql.fetch({id: this.id}).toPromise()).data; const data = (await this.gql.fetch({id: this.id}).toPromise()).data;
this.key = Object.keys(data).filter(k => k !== '__typename')[0]; this.key = Object.keys(data).filter(k => k !== '__typename')[0];
this.item = data[this.key]; this.item = data[this.key];
@ -87,8 +87,8 @@ export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extend
await this.router.navigate(['..'], {relativeTo: this.route}); await this.router.navigate(['..'], {relativeTo: this.route});
} }
getType(itemElement: any): typeof itemElement { getType(field: typeof EditComponent.prototype.fields[number] | null, value: any): typeof value | string {
return typeof itemElement; return field?.type ?? typeof value;
} }
} }

View file

@ -13,6 +13,7 @@ import { ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatDatepickerModule } from '@angular/material/datepicker';
@NgModule({ @NgModule({
@ -33,7 +34,8 @@ import { MatTooltipModule } from '@angular/material/tooltip';
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatCheckboxModule, MatCheckboxModule,
MatTooltipModule MatTooltipModule,
MatDatepickerModule
] ]
}) })
export class SharedComponentsModule { export class SharedComponentsModule {

View file

@ -10,8 +10,9 @@
<ng-container *ngFor="let col of columns" [matColumnDef]="col.prop"> <ng-container *ngFor="let col of columns" [matColumnDef]="col.prop">
<th mat-header-cell *matHeaderCellDef>{{ col.title }}</th> <th mat-header-cell *matHeaderCellDef>{{ col.title }}</th>
<td mat-cell *matCellDef="let item"> <td mat-cell *matCellDef="let item">
<span [ngSwitch]="getType(item[col.prop])"> <span [ngSwitch]="getType(col, item[col.prop])">
<span *ngSwitchCase="'boolean'"><mat-checkbox [checked]="item[col.prop]" disabled="disabled"></mat-checkbox></span> <span *ngSwitchCase="'boolean'"><mat-checkbox [checked]="item[col.prop]" disabled="disabled"></mat-checkbox></span>
<span *ngSwitchCase="'date'">{{ item[col.prop] | date }}</span>
<span *ngSwitchDefault>{{ item[col.prop] }}</span> <span *ngSwitchDefault>{{ item[col.prop] }}</span>
</span> </span>
</td> </td>

View file

@ -12,7 +12,7 @@ export class TableComponent<T> implements OnInit {
@Input() showHeader = false; @Input() showHeader = false;
@Input() items: T[] = []; @Input() items: T[] = [];
@Input() loading = false; @Input() loading = false;
@Input() columns: { title: string, prop: string }[] = []; @Input() columns: { title: string, prop: string, type?: string }[] = [];
@Input() paginationData: PaginationData = {page: 1, limit: 10}; @Input() paginationData: PaginationData = {page: 1, limit: 10};
@Input() customActions: { icon: string, label: string, action: (model: T) => void }[] = []; @Input() customActions: { icon: string, label: string, action: (model: T) => void }[] = [];
@Input() allowEditing = true; @Input() allowEditing = true;
@ -30,8 +30,8 @@ export class TableComponent<T> implements OnInit {
return this.columns.map(col => col.prop).concat('actions'); return this.columns.map(col => col.prop).concat('actions');
} }
getType(itemElement: any): typeof itemElement { getType(field: typeof TableComponent.prototype.columns[number] | null, value: any): typeof value | string {
return typeof itemElement; return field?.type ?? typeof value;
} }
} }

View file

@ -19,13 +19,43 @@
{{ editedFulMode?.name || 'Új teljesitési mód' }} {{ editedFulMode?.name || 'Új teljesitési mód' }}
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<app-edit [itemType]="editedFulMode" [createMutation]="modeCreateGQL" <app-edit [itemType]="editedFulMode" creating [createMutation]="modeCreateGQL"
[updateMutation]="modeEditGQL" [customItem]="editedFulMode" [fields]="[ [updateMutation]="modeEditGQL" [customItem]="editedFulMode" [fields]="[
{name: 'name', title: 'Név'}, {name: 'name', title: 'Név'},
{name: 'threshold2', title: 'Kettes %'}, {name: 'threshold2', title: 'Kettes %', type: 'integer'},
{name: 'threshold3', title: 'Hármas %'}, {name: 'threshold3', title: 'Hármas %', type: 'integer'},
{name: 'threshold4', title: 'Négyes %'}, {name: 'threshold4', title: 'Négyes %', type: 'integer'},
{name: 'threshold5', title: 'Ötös %'} {name: 'threshold5', title: 'Ötös %', type: 'integer'}
]" [beforeSubmit]="beforeFulModeSubmit.bind(this)" [itemSubmitted]="submitFulMode.bind(this)"></app-edit> ]" [beforeSubmit]="beforeFulModeSubmit.bind(this)" [itemSubmitted]="submitFulMode.bind(this)"></app-edit>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card style="margin-top: 50px" *ngIf="editingFulMode">
<mat-card-header>
<h3>Követelmények</h3>
</mat-card-header>
<mat-card-content>
<app-list [gql]="requirementListGQL" [queryVariables]="{mode: this.editedFulMode.id}" [columns]="[
{prop: 'name', title: 'Név'},
{prop: 'description', title: 'Leirás'},
{prop: 'minPoints', title: 'Minimum elérendő pontszám'},
{prop: 'maxPoints', title: 'Maximálisan elérhető pontszám'},
{prop: 'deadline', title: 'Határidő', type: 'date'}
]" allowEditing="true" allowNew="true" [editFunction]="editRequirement.bind(this)"
[createFunction]="createRequirement.bind(this)"></app-list>
</mat-card-content>
</mat-card>
<mat-card *ngIf="editingRequirement">
<mat-card-header>
{{ editedRequirement?.name || 'Új követelmény' }}
</mat-card-header>
<mat-card-content>
<app-edit [itemType]="editedRequirement" creating="true" [createMutation]="requirementCreateGQL"
[updateMutation]="requirementEditGQL" [customItem]="editedRequirement" [fields]="[
{name: 'name', title: 'Név'},
{name: 'description', title: 'Leirás'},
{name: 'minPoints', title: 'Minimum elérendő pontszám', type: 'integer'},
{name: 'maxPoints', title: 'Maximálisan elérhető pontszám', type: 'integer'},
{name: 'deadline', title: 'Határidő', type: 'date'}
]" [beforeSubmit]="beforeRequirementSubmit.bind(this)" [itemSubmitted]="submitRequirement.bind(this)"></app-edit>
</mat-card-content>
</mat-card>

View file

@ -6,12 +6,18 @@ import {
CourseGQL, CourseGQL,
CreateCourseGQL, CreateCourseGQL,
CreateFulfillmentModeGQL, CreateFulfillmentModeGQL,
CreateRequirementGQL,
EditCourseGQL, EditCourseGQL,
EditFulfillmentModeGQL, EditFulfillmentModeGQL,
EditRequirementGQL,
FulfillmentMode, FulfillmentMode,
FulfillmentModeGQL, FulfillmentModeGQL,
FulfillmentModeListGQL, FulfillmentModeListGQL,
FulfillmentModeUpdateInput, FulfillmentModeUpdateInput,
Requirement,
RequirementGQL,
RequirementListGQL,
RequirementUpdateInput,
SubjectGQL SubjectGQL
} from '../../../../services/graphql'; } from '../../../../services/graphql';
@ -26,12 +32,15 @@ export class CourseEditComponent implements OnInit, CustomTitleComponent {
courseId: string; courseId: string;
editedFulMode: FulfillmentMode; editedFulMode: FulfillmentMode;
editingFulMode = false; editingFulMode = false;
editedRequirement: Requirement;
editingRequirement = false;
beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId}); beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId});
constructor(private route: ActivatedRoute, public subjectGQL: SubjectGQL, constructor(private route: ActivatedRoute, public subjectGQL: SubjectGQL,
public itemGQL: CourseGQL, public editGQL: EditCourseGQL, public createGQL: CreateCourseGQL, public itemGQL: CourseGQL, public editGQL: EditCourseGQL, public createGQL: CreateCourseGQL,
public modeListGQL: FulfillmentModeListGQL, public modeItemGQL: FulfillmentModeGQL, public modeEditGQL: EditFulfillmentModeGQL, public modeListGQL: FulfillmentModeListGQL, public modeItemGQL: FulfillmentModeGQL, public modeEditGQL: EditFulfillmentModeGQL,
public modeCreateGQL: CreateFulfillmentModeGQL) { public modeCreateGQL: CreateFulfillmentModeGQL, public requirementListGQL: RequirementListGQL, public requirementGQL: RequirementGQL,
public requirementEditGQL: EditRequirementGQL, public requirementCreateGQL: CreateRequirementGQL) {
this.subjectId = route.snapshot.params.subjectId; this.subjectId = route.snapshot.params.subjectId;
this.courseId = route.snapshot.params.id; this.courseId = route.snapshot.params.id;
} }
@ -59,7 +68,7 @@ export class CourseEditComponent implements OnInit, CustomTitleComponent {
if (val < 0) { if (val < 0) {
val = 0; val = 0;
} }
((item[prop]) as number) = val > 1 ? val as number / 100 : +val; ((item[prop]) as number) = val > 100 ? 100 : +val;
} }
thresh('threshold2'); thresh('threshold2');
@ -73,4 +82,23 @@ export class CourseEditComponent implements OnInit, CustomTitleComponent {
this.editedFulMode = null; this.editedFulMode = null;
this.editingFulMode = false; this.editingFulMode = false;
} }
async editRequirement(item: Requirement): Promise<void> {
this.editingRequirement = true;
this.editedRequirement = item;
}
async createRequirement(item: Requirement): Promise<void> {
this.editingRequirement = true;
this.editedRequirement = null;
}
beforeRequirementSubmit(item: Requirement): Partial<RequirementUpdateInput> {
return {...item, fulfillmentModeId: this.editedFulMode.id};
}
submitRequirement(): void {
this.editingRequirement = false;
this.editedRequirement = null;
}
} }

View file

@ -8,6 +8,7 @@ import { Subject, SubjectListGQL } from '../../services/graphql';
styleUrls: ['./subject-list.component.css'] styleUrls: ['./subject-list.component.css']
}) })
export class SubjectListComponent implements OnInit { export class SubjectListComponent implements OnInit {
itemType: Subject;
constructor(private route: ActivatedRoute, private router: Router, public listGQL: SubjectListGQL) { constructor(private route: ActivatedRoute, private router: Router, public listGQL: SubjectListGQL) {
} }