Add requirements list and editing, fix type issues
Added suport for numbers and dates
This commit is contained in:
parent
744cc29346
commit
ee4fee004d
11 changed files with 107 additions and 32 deletions
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue