MOBILE-2354 workshop: Submission component

main
Pau Ferrer Ocaña 2018-05-31 16:58:35 +02:00 committed by Albert Gasset
parent afc2fc1f64
commit b25e5b3ec1
10 changed files with 306 additions and 19 deletions

View File

@ -11,7 +11,7 @@
</button>
</div>
<ion-note *ngIf="!isSent" color="dark">
<ion-icon name="clock"></ion-icon>
<ion-icon name="time"></ion-icon>
{{ 'core.notsent' | translate }}
</ion-note>
</div>

View File

@ -18,12 +18,15 @@ import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
import { AddonModWorkshopIndexComponent } from './index/index';
import { AddonModWorkshopSubmissionComponent } from './submission/submission';
@NgModule({
declarations: [
AddonModWorkshopIndexComponent
AddonModWorkshopIndexComponent,
AddonModWorkshopSubmissionComponent
],
imports: [
CommonModule,
@ -31,12 +34,14 @@ import { AddonModWorkshopIndexComponent } from './index/index';
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
CoreCourseComponentsModule
],
providers: [
],
exports: [
AddonModWorkshopIndexComponent
AddonModWorkshopIndexComponent,
AddonModWorkshopSubmissionComponent
],
entryComponents: [
AddonModWorkshopIndexComponent

View File

@ -14,7 +14,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description *ngIf="description && selectedPhase == workshopPhases.PHASE_SETUP" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<ion-card *ngIf="phases">
<ion-card class="with-borders" *ngIf="phases">
<ion-item (click)="selectPhase()">
<h2 stacked text-wrap>{{ phases[selectedPhase].title }}</h2>
<p text-wrap *ngIf="phases[selectedPhase].code == workshop.phase">{{ 'addon.mod_workshop.userplancurrentphase' | translate }}</p>
@ -27,8 +27,8 @@
</a>
</ion-card>
<ion-card *ngIf="phases && phases[selectedPhase] && phases[selectedPhase].tasks.length">
<ion-item text-wrap *ngFor="let task of phases[selectedPhase].tasks" [ngClass]="{'item-dimmed': selectedPhase != workshop.phase}" (click)="runTask(task)" detail-none>
<ion-card class="with-borders" *ngIf="phases && phases[selectedPhase] && phases[selectedPhase].tasks && phases[selectedPhase].tasks.length">
<ion-item text-wrap *ngFor="let task of phases[selectedPhase].tasks" [class.item-dimmed]="selectedPhase != workshop.phase" (click)="runTask(task)" detail-none>
<ion-icon item-start name="radio-button-off" *ngIf="task.completed == null"></ion-icon>
<ion-icon item-start name="close-circle" color="danger" *ngIf="task.completed == ''"></ion-icon>
<ion-icon item-start name="information-circle" color="info" *ngIf="task.completed == 'info'"></ion-icon>
@ -56,13 +56,15 @@
</ion-item>
</ion-card>
<ion-card *ngIf="canSubmit">
<ion-card class="with-borders" *ngIf="canSubmit">
<ion-item text-wrap *ngIf="!submission">
<h2>{{ 'addon.mod_workshop.yoursubmission' | translate }}</h2>
<p>{{ 'addon.mod_workshop.noyoursubmission' | translate }}</p>
</ion-item>
<!-- <addon-mod-workshop-submission *ngIf="submission" [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access"></addon-mod-workshop-submission> -->
<ng-container *ngIf="submission">
<addon-mod-workshop-submission [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access"></addon-mod-workshop-submission>
</ng-container>
</ion-card>
<!-- Show only on current phase -->
@ -89,15 +91,17 @@
</ion-item>
</ion-card>
<ion-card *ngIf="canAssess && assessments.length">
<ion-card class="with-borders" *ngIf="canAssess && assessments && assessments.length">
<ion-item text-wrap>
<h2>{{ 'addon.mod_workshop.assignedassessments' | translate }}</h2>
</ion-item>
<!-- <addon-mod-workshop-submission *ngFor="let assessment of assessments" [submission]="assessment.submission" [assessment]="assessment" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission> -->
<ng-container *ngFor="let assessment of assessments">
<addon-mod-workshop-submission [submission]="assessment.submission" [assessment]="assessment" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission>
</ng-container>
</ion-card >
</ng-container>
<ion-card *ngIf="!access.canviewallsubmissions && selectedPhase == workshop.phase && (canSubmit || canAssess) && selectedPhase == workshopPhases.PHASE_EVALUATION">
<ion-card class="with-borders" *ngIf="!access.canviewallsubmissions && selectedPhase == workshop.phase && (canSubmit || canAssess) && selectedPhase == workshopPhases.PHASE_EVALUATION">
<ion-item text-wrap *ngIf="submission" (click)="switchPhase(workshopPhases.PHASE_SUBMISSION)" detail-push>
<h2>{{ 'addon.mod_workshop.yoursubmission' | translate }}</h2>
</ion-item>
@ -115,7 +119,7 @@
</ion-item>
</ion-card>
<ion-card *ngIf="userGrades">
<ion-card class="with-borders" *ngIf="userGrades">
<ion-item-divider color="light" text-wrap>
<h2>{{ 'addon.mod_workshop.yourgrades' | translate }}</h2>
</ion-item-divider>
@ -129,16 +133,18 @@
</ion-item>
</ion-card>
<ion-card *ngIf="publishedSubmissions.length">
<ion-card class="with-borders" *ngIf="publishedSubmissions && publishedSubmissions.length">
<ion-item text-wrap>
<h2>{{ 'addon.mod_workshop.publishedsubmissions' | translate }}</h2>
</ion-item>
<!-- <addon-mod-workshop-submission *ngFor="let submission of publishedSubmissions" [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission> -->
<ng-container *ngFor="let submission of publishedSubmissions">
<addon-mod-workshop-submission [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission>
</ng-container>
</ion-card>
</ng-container>
<!-- MULTIPLE PHASES SUBMISSION OR GREATER only teachers -->
<ion-card *ngIf="workshop.phase == selectedPhase && access.canviewallsubmissions && selectedPhase >= workshopPhases.PHASE_SUBMISSION && grades.length">
<ion-card class="with-borders" *ngIf="workshop.phase == selectedPhase && access.canviewallsubmissions && selectedPhase >= workshopPhases.PHASE_SUBMISSION && grades && grades.length">
<ion-item text-wrap *ngIf="selectedPhase == workshopPhases.PHASE_SUBMISSION">
<h2>{{ 'addon.mod_workshop.submissionsreport' | translate }}</h2>
</ion-item>
@ -153,9 +159,11 @@
</ion-select>
</ion-item>
<!-- <addon-mod-workshop-submission *ngFor="submission of grades" [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission>-->
<ng-container *ngFor="let submission of grades">
<addon-mod-workshop-submission [submission]="submission" [courseId]="workshop.course" [module]="module" [workshop]="workshop" [access]="access" summary="true"></addon-mod-workshop-submission>
</ng-container>
<ion-grid ngIf="page > 0 || hasNextPage">
<ion-grid *ngIf="page > 0 || hasNextPage">
<ion-row align-items-center>
<ion-col *ngIf="page > 0">
<button ion-button block outline icon-start (click)="gotoSubmissionsPage(page - 1)">>
@ -164,7 +172,7 @@
</button>
</ion-col>
<ion-col *ngIf="hasNextPage">
<button ion-button block icon-end click)="gotoSubmissionsPage(page + 1)">
<button ion-button block icon-end (click)="gotoSubmissionsPage(page + 1)">
{{ 'core.next' | translate }}
<ion-icon name="arrow-forward"></ion-icon>
</button>

View File

@ -0,0 +1,82 @@
<core-loading [hideUntil]="loaded">
<div *ngIf="!summary">
<ion-list-header text-wrap>
<ion-avatar item-start>
<img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<h2>{{submission.title}}</h2>
<p *ngIf="profile && profile.fullname">{{profile.fullname}}</p>
<p *ngIf="showGrade(submission.submissiongrade)" [class.addon-has-overriden-grade]="showGrade(submission.submissiongradeover)">
{{ 'addon.mod_workshop.submissiongradeof' | translate:{$a: workshop.grade } }}: {{submission.submissiongrade}}
</p>
<p *ngIf="showGrade(submission.submissiongradeover)" class="addon-overriden-grade">
{{ 'addon.mod_workshop.gradeover' | translate }}: {{submission.submissiongradeover}}
</p>
<p *ngIf="access.canviewallsubmissions && showGrade(submission.gradinggrade)">
{{ 'addon.mod_workshop.gradinggradeof' | translate:{$a: workshop.gradinggrade } }}: {{submission.gradinggrade}}
</p>
<ion-note item-end *ngIf="!submission.timemodified">
<ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}
</ion-note>
<ion-note item-end *ngIf="submission.timemodified">
{{submission.timemodified | coreDateDayOrTime}}
<ng-container *ngIf="submission.offline"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</ng-container>
<ng-container *ngIf="submission.deleted"><ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}</ng-container>
</ion-note>
</ion-list-header>
<ion-item text-wrap *ngIf="submission.content">
<core-format-text [component]="component" [componentId]="componentId" [text]="submission.content"></core-format-text>
</ion-item>
<ion-item *ngFor="let attachment of submission.attachmentfiles">
<!-- Files already attached to the submission. -->
<core-file *ngIf="!attachment.name" [file]="attachment" [component]="component" [componentId]="componentId"></core-file>
<!-- Files stored in offline to be sent later. -->
<core-local-file *ngIf="attachment.name" [file]="attachment"></core-local-file>
</ion-item>
<ion-item text-wrap *ngIf="viewDetails && submission.feedbackauthor">
<img [src]="evaluateByProfile && evaluateByProfile.profileimageurl" core-external-content core-user-link [courseId]="courseId" [userId]="evaluateByProfile && evaluateByProfile.id" [alt]="'core.pictureof' | translate:{$a: evaluateByProfile && evaluateByProfile.fullname}" role="presentation" onError="this.src='assets/img/user-avatar.png'"/>
<h2 *ngIf="evaluateByProfile && evaluateByProfile.fullname">{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateByProfile.fullname} }}</h2>
<core-format-text [text]="submission.feedbackauthor"></core-format-text>
</ion-item>
<ion-item *ngIf="viewDetails">
<button ion-button block (click)="gotoSubmission()">
{{ 'core.showmore' | translate }}
<ion-icon name="arrow-forward" item-end></ion-icon>
</button>
</ion-item>
</div>
<ion-item text-wrap *ngIf="summary" [attr.detail-push]="submission.timemodified? true : null" (click)="gotoSubmission()">
<ion-avatar item-start>
<img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" core-user-link [courseId]="courseId" [userId]="profile && profile.id" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<h2>{{submission.title}}</h2>
<p *ngIf="profile && profile.fullname">{{profile.fullname}}</p>
<p *ngIf="submission.reviewedbycount">
{{ 'addon.mod_workshop.receivedgrades' | translate }}: {{submission.reviewedbycount}} / {{submission.reviewedby.length}}
</p>
<p *ngIf="submission.reviewerofcount">
{{ 'addon.mod_workshop.givengrades' | translate }}: {{submission.reviewerofcount}} / {{submission.reviewerof.length}}
</p>
<p *ngIf="!showGrade(submission.submissiongradeover) && showGrade(submission.submissiongrade)">
{{ 'addon.mod_workshop.submissiongradeof' | translate:{$a: workshop.grade } }}: {{submission.submissiongrade}}
</p>
<p *ngIf="showGrade(submission.submissiongradeover)" class="addon-overriden-grade">
{{ 'addon.mod_workshop.submissiongradeof' | translate:{$a: workshop.grade } }}: {{submission.submissiongradeover}}
</p>
<p *ngIf="access.canviewallsubmissions && showGrade(submission.gradinggrade)">
{{ 'addon.mod_workshop.gradinggradeof' | translate:{$a: workshop.gradinggrade } }}: {{submission.gradinggrade}}
</p>
<ion-badge *ngIf="assessment && (showGrade(assessment.grade) || assessment.offline)" color="success">{{ 'addon.mod_workshop.assessedsubmission' | translate }}</ion-badge>
<ion-badge *ngIf="assessment && !showGrade(assessment.grade) && !assessment.offline" color="danger">{{ 'addon.mod_workshop.notassessed' | translate }}</ion-badge>
<ion-note item-end *ngIf="submission.timemodified">
{{submission.timemodified | coreDateDayOrTime}}
<div *ngIf="offline"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</div>
<div *ngIf="submission.deleted"><ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}</div>
</ion-note>
</ion-item>
</core-loading>

View File

@ -0,0 +1,35 @@
addon-mod-workshop-submission {
.item-md.item-block .item-inner {
border-bottom: 1px solid $list-md-border-color;
}
.item-ios.item-block .item-inner {
border-bottom: $hairlines-width solid $list-ios-border-color;
}
.item-wp.item-block .item-inner {
border-bottom: 1px solid $list-wp-border-color;
}
&:last-child .item .item-inner {
border-bottom: 0;
}
}
.card.with-borders addon-mod-workshop-submission {
.item-md.item-block .item-inner {
border-bottom: 1px solid $list-md-border-color;
}
.item-ios.item-block .item-inner {
border-bottom: $hairlines-width solid $list-ios-border-color;
}
.item-wp.item-block .item-inner {
border-bottom: 1px solid $list-wp-border-color;
}
&:last-child .item .item-inner {
border-bottom: 0;
}
}

View File

@ -0,0 +1,132 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, OnInit } from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModWorkshopProvider } from '../../providers/workshop';
import { AddonModWorkshopHelperProvider } from '../../providers/helper';
import { AddonModWorkshopOfflineProvider } from '../../providers/offline';
/**
* Component that displays workshop submission.
*/
@Component({
selector: 'addon-mod-workshop-submission',
templateUrl: 'submission.html',
})
export class AddonModWorkshopSubmissionComponent implements OnInit {
@Input() submission: any;
@Input() module: any;
@Input() workshop: any;
@Input() access: any;
@Input() courseId: number;
@Input() assessment?: any;
@Input() summary?: boolean;
component = AddonModWorkshopProvider.COMPONENT;
componentId: number;
userId: number;
loaded = false;
offline = false;
viewDetails = false;
profile: any;
showGrade: any;
evaluateByProfile: any;
constructor(private workshopOffline: AddonModWorkshopOfflineProvider, private workshopHelper: AddonModWorkshopHelperProvider,
private navCtrl: NavController, private userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider) {
this.userId = sitesProvider.getCurrentSiteUserId();
this.showGrade = this.workshopHelper.showGrade;
}
/**
* Component being initialized.
*/
ngOnInit(): void {
this.componentId = this.module.instance;
this.userId = this.submission.authorid || this.submission.userid || this.userId;
this.submission.title = this.submission.title || this.submission.submissiontitle;
this.submission.timemodified = this.submission.timemodified || this.submission.submissionmodified;
this.submission.id = this.submission.id || this.submission.submissionid;
if (this.workshop.phase == AddonModWorkshopProvider.PHASE_ASSESSMENT) {
if (this.submission.reviewedby && this.submission.reviewedby.length) {
this.submission.reviewedbycount = this.submission.reviewedby.reduce((a, b) => {
return a + (b.grade ? 1 : 0);
}, 0);
}
if (this.submission.reviewerof && this.submission.reviewerof.length) {
this.submission.reviewerofcount = this.submission.reviewerof.reduce((a, b) => {
return a + (b.grade ? 1 : 0);
}, 0);
}
}
const promises = [];
this.offline = (this.submission && this.submission.offline) || (this.assessment && this.assessment.offline);
if (this.submission.id) {
promises.push(this.workshopOffline.getEvaluateSubmission(this.workshop.id, this.submission.id)
.then((offlineSubmission) => {
this.submission.submissiongradeover = offlineSubmission.gradeover;
this.offline = true;
}).catch(() => {
// Ignore errors.
}));
}
if (this.userId) {
promises.push(this.userProvider.getProfile(this.userId, this.courseId, true).then((profile) => {
this.profile = profile;
}));
}
this.viewDetails = !this.summary && this.workshop.phase == AddonModWorkshopProvider.PHASE_CLOSED &&
this.navCtrl.getActive().name !== 'AddonModWorkshopSubmissionPage';
if (this.viewDetails && this.submission.gradeoverby) {
promises.push(this.userProvider.getProfile(this.submission.gradeoverby, this.courseId, true).then((profile) => {
this.evaluateByProfile = profile;
}));
}
Promise.all(promises).finally(() => {
this.loaded = true;
});
}
/**
* Navigate to the submission.
*/
gotoSubmission(): void {
if (this.submission.timemodified) {
const params = {
module: this.module,
workshop: this.workshop,
access: this.access,
courseId: this.courseId,
profile: this.profile,
submission: this.submission,
assessment: this.assessment,
submissionId: this.submission.id
};
this.navCtrl.push('AddonModWorkshopSubmissionPage', params);
}
}
}

View File

@ -32,7 +32,7 @@
</ion-avatar>
<h2>{{note.userfullname}}</h2>
<p *ngIf="!note.offline" item-end>{{note.lastmodified | coreDateDayOrTime}}</p>
<p *ngIf="note.offline" item-end><ion-icon name="clock"></ion-icon> {{ 'core.notsent' | translate }}</p>
<p *ngIf="note.offline" item-end><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p>
</ion-item>
<ion-item text-wrap>
<core-format-text [clean]="true" [text]="note.content"></core-format-text>

View File

@ -111,3 +111,11 @@
padding-left: 15px * $i + $item-ios-padding-start;
}
}
// Recover borders on items inside cards.
.card-ios.with-borders .item-ios.item-block .item-inner {
border-bottom: $hairlines-width solid $list-ios-border-color;
}
.card-ios.with-borders .item-ios:last-child .item-inner {
border-bottom: 0;
}

View File

@ -112,3 +112,12 @@
padding-left: 15px * $i + $item-md-padding-start;
}
}
// Recover borders on items inside cards.
.card-md.with-borders .item-md.item-block .item-inner {
border-bottom: 1px solid $list-md-border-color;
}
.card-md.with-borders .item-md:last-child .item-inner {
border-bottom: 0;
}

View File

@ -47,3 +47,11 @@
padding-left: 15px * $i + $item-wp-padding-start;
}
}
// Recover borders on items inside cards.
.card-wp.with-borders .item-wp.item-block .item-inner {
border-bottom: 1px solid $list-wp-border-color;
}
.card-wp.with-borders .item-wp:last-child .item-inner {
border-bottom: 0;
}