MOBILE-4329 dataprivacy: Implement data privacy pages

main
Pau Ferrer Ocaña 2024-02-06 15:43:18 +01:00
parent 84d83b0450
commit 791903a80d
15 changed files with 778 additions and 6 deletions

View File

@ -1677,6 +1677,40 @@
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
"core.currentdevice": "local_moodlemobileapp",
"core.custom": "form",
"core.dataprivacy.cancelrequest": "tool_dataprivacy",
"core.dataprivacy.cancelrequestconfirmation": "tool_dataprivacy",
"core.dataprivacy.contactdataprotectionofficer": "tool_dataprivacy",
"core.dataprivacy.createnewdatarequest": "tool_dataprivacy",
"core.dataprivacy.datarequests": "tool_dataprivacy",
"core.dataprivacy.daterequested": "tool_dataprivacy",
"core.dataprivacy.deletemyaccount": "tool_dataprivacy",
"core.dataprivacy.message": "tool_dataprivacy",
"core.dataprivacy.newrequest": "tool_dataprivacy",
"core.dataprivacy.nodatarequests": "tool_dataprivacy",
"core.dataprivacy.pluginname": "tool_dataprivacy",
"core.dataprivacy.replyto": "tool_dataprivacy",
"core.dataprivacy.requestactions": "tool_dataprivacy",
"core.dataprivacy.requestby": "tool_dataprivacy",
"core.dataprivacy.requestcomments": "tool_dataprivacy",
"core.dataprivacy.requeststatus": "tool_dataprivacy",
"core.dataprivacy.requestsubmitted": "tool_dataprivacy",
"core.dataprivacy.requesttype": "tool_dataprivacy",
"core.dataprivacy.requesttype_help": "tool_dataprivacy",
"core.dataprivacy.requesttypedelete": "tool_dataprivacy",
"core.dataprivacy.requesttypeexport": "tool_dataprivacy",
"core.dataprivacy.requesttypeothers": "tool_dataprivacy",
"core.dataprivacy.send": "tool_dataprivacy",
"core.dataprivacy.statusapproved": "tool_dataprivacy",
"core.dataprivacy.statusawaitingapproval": "tool_dataprivacy",
"core.dataprivacy.statuscancelled": "tool_dataprivacy",
"core.dataprivacy.statuscomplete": "tool_dataprivacy",
"core.dataprivacy.statusdeleted": "tool_dataprivacy",
"core.dataprivacy.statusexpired": "tool_dataprivacy",
"core.dataprivacy.statuspending": "tool_dataprivacy",
"core.dataprivacy.statuspreprocessing": "tool_dataprivacy",
"core.dataprivacy.statusprocessing": "tool_dataprivacy",
"core.dataprivacy.statusready": "tool_dataprivacy",
"core.dataprivacy.statusrejected": "tool_dataprivacy",
"core.datastoredoffline": "local_moodlemobileapp",
"core.date": "moodle",
"core.datecreated": "repository",

View File

@ -2,7 +2,7 @@
:host {
--image-size: 120px;
--icon-color: var(--text-color);
--icon-color: var(--subdued-text-color);
display: flex;
flex-direction: column;

View File

@ -0,0 +1,33 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreDataPrivacyContactDPOComponent } from './contactdpo/contactdpo';
import { CoreDataPrivacyNewRequestComponent } from './newrequest/newrequest';
@NgModule({
declarations: [
CoreDataPrivacyContactDPOComponent,
CoreDataPrivacyNewRequestComponent,
],
imports: [
CoreSharedModule,
],
exports: [
CoreDataPrivacyContactDPOComponent,
CoreDataPrivacyNewRequestComponent,
],
})
export class CoreDataPrivacyComponentsModule {}

View File

@ -0,0 +1,37 @@
<ion-header>
<ion-toolbar>
<ion-title>
<h1>{{ 'core.dataprivacy.contactdataprotectionofficer' | translate }}</h1>
</ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="close()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true" />
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<form [formGroup]="form" name="contactDPO" (ngSubmit)="send($event)">
<ion-item *ngIf="email">
<ion-label>
<p class="item-heading">
{{ 'core.dataprivacy.replyto' | translate }}
</p>
<p>{{ email }}</p>
</ion-label>
</ion-item>
<ion-item>
<ion-textarea labelPlacement="floating" placeholder="{{ 'core.dataprivacy.message' | translate }}" rows="5"
[(ngModel)]="message" name="text" [required]="true" formControlName="message">
<div [core-mark-required]="true" slot="label">
{{ 'core.dataprivacy.message' | translate }}
</div>
</ion-textarea>
</ion-item>
</form>
</ion-content>
<ion-footer slot="fixed" class="ion-padding">
<ion-button expand="block" (click)="send($event)" [disabled]="!form.valid">
{{ 'core.dataprivacy.send' | translate }}
</ion-button>
</ion-footer>

View File

@ -0,0 +1,94 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { CoreDataPrivacy } from '@features/dataprivacy/services/dataprivacy';
import { CoreUser } from '@features/user/services/user';
import { CoreSites } from '@services/sites';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
/**
* Component that displays the contact DPO page.
*/
@Component({
selector: 'core-data-privacy-contact-dpo',
templateUrl: 'contactdpo.html',
})
export class CoreDataPrivacyContactDPOComponent implements OnInit {
message = '';
email = '';
// Form variables.
form: FormGroup;
constructor(
protected fb: FormBuilder,
) {
this.form = new FormGroup({});
// Initialize form variables.
this.form.addControl('message', this.fb.control('', Validators.required));
}
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
const modal = await CoreDomUtils.showModalLoading();
// Get current user email.
const userId = CoreSites.getCurrentSiteUserId();
const user = await CoreUtils.ignoreErrors(CoreUser.getProfile(userId));
this.email = user?.email || '';
modal.dismiss();
}
/**
* Sends the message.
*/
async send(event: Event): Promise<void> {
event.preventDefault();
event.stopPropagation();
const modal = await CoreDomUtils.showModalLoading();
try {
// Send the message.
const succeed = await CoreDataPrivacy.contactDPO(this.message);
if (succeed) {
CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG);
ModalController.dismiss(true);
}
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error sending data privacy request');
} finally {
modal.dismiss();
}
}
/**
* Close modal.
*/
close(): void {
ModalController.dismiss();
}
}

View File

@ -0,0 +1,50 @@
<ion-header>
<ion-toolbar>
<ion-title>
<h1>{{ 'core.dataprivacy.createnewdatarequest' | translate }}</h1>
</ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="close()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true" />
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<form [formGroup]="form" name="newRequest" (ngSubmit)="send($event)">
<ion-item class="ion-text-wrap">
<ion-label>
<p>
{{ 'core.dataprivacy.requesttype_help' | translate }}
</p>
<p class="item-heading" [core-mark-required]="true">
{{ 'core.dataprivacy.requesttype' | translate }}
</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap">
<ion-radio-group name="type" formControlName="type">
<ion-radio [value]="1" *ngIf="accessInfo?.cancreatedatadownloadrequest">
{{ 'core.dataprivacy.requesttypeexport' | translate }}
</ion-radio>
<ion-radio [value]="2" *ngIf="accessInfo?.cancreatedatadeletionrequest">
{{ 'core.dataprivacy.requesttypedelete' | translate }}
</ion-radio>
</ion-radio-group>
</ion-item>
<ion-item>
<ion-textarea labelPlacement="stacked" placeholder="{{ 'core.dataprivacy.requestcomments' | translate }}" rows="5"
[(ngModel)]="message" name="text" formControlName="message">
<div slot="label">
{{ 'core.dataprivacy.requestcomments' | translate }}
</div>
</ion-textarea>
</ion-item>
</form>
</ion-content>
<ion-footer slot="fixed" class="ion-padding">
<ion-button expand="block" (click)="send($event)" [disabled]="!form.valid">
{{ 'core.dataprivacy.send' | translate }}
</ion-button>
</ion-footer>

View File

@ -0,0 +1,104 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import {
CoreDataPrivacy,
CoreDataPrivacyDataRequestType,
CoreDataPrivacyGetAccessInformationWSResponse,
} from '@features/dataprivacy/services/dataprivacy';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { ModalController } from '@singletons';
/**
* Component that displays the new request page.
*/
@Component({
selector: 'core-data-privacy-new-request',
templateUrl: 'newrequest.html',
})
export class CoreDataPrivacyNewRequestComponent implements OnInit {
@Input() accessInfo?: CoreDataPrivacyGetAccessInformationWSResponse;
message = '';
// Form variables.
form: FormGroup;
typeControl: FormControl<CoreDataPrivacyDataRequestType>;
constructor(
protected fb: FormBuilder,
) {
this.form = new FormGroup({});
// Initialize form variables.
this.typeControl = this.fb.control(
CoreDataPrivacyDataRequestType.DATAREQUEST_TYPE_EXPORT,
{ validators: Validators.required, nonNullable: true },
);
this.form.addControl('type', this.typeControl);
this.form.addControl('message', this.fb.control(''));
}
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
// It should not happen. If there's no access info, close the modal.
if (!this.accessInfo) {
ModalController.dismiss();
return;
}
// Just in case only deleting is allowed, change the default type.
if (!this.accessInfo.cancreatedatadownloadrequest && this.accessInfo.cancreatedatadeletionrequest){
this.typeControl.setValue(CoreDataPrivacyDataRequestType.DATAREQUEST_TYPE_DELETE);
}
}
/**
* Sends the request.
*/
async send(event: Event): Promise<void> {
event.preventDefault();
event.stopPropagation();
const modal = await CoreDomUtils.showModalLoading();
try {
// Send the message.
const requestId = await CoreDataPrivacy.createDataRequest(this.typeControl.value, this.message);
if (requestId) {
CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG);
ModalController.dismiss(true);
}
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error sending data privacy request');
} finally {
modal.dismiss();
}
}
/**
* Close modal.
*/
close(): void {
ModalController.dismiss();
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreDataPrivacyMainPage } from './pages/main/main';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: CoreDataPrivacyMainPage,
},
];
@NgModule({
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
],
declarations: [
CoreDataPrivacyMainPage,
],
})
export class CoreDataPrivacyLazyModule {}

View File

@ -15,9 +15,23 @@
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreUserDelegate } from '@features/user/services/user-delegate';
import { CoreDataPrivacyUserHandler } from './services/handlers/user';
import { Routes } from '@angular/router';
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
import { CoreDataPrivacyComponentsModule } from './components/components.module';
import { CORE_DATAPRIVACY_PAGE_NAME } from './constants';
const routes: Routes = [
{
path: CORE_DATAPRIVACY_PAGE_NAME,
loadChildren: () => import('./dataprivacy-lazy.module').then(m => m.CoreDataPrivacyLazyModule),
},
];
@NgModule({
imports: [
CoreMainMenuTabRoutingModule.forChild(routes),
CoreDataPrivacyComponentsModule,
],
providers: [
{
provide: APP_INITIALIZER,

View File

@ -1,3 +1,36 @@
{
"pluginname": "Data privacy"
"contactdataprotectionofficer": "Contact the privacy officer",
"cancelrequest": "Cancel request",
"cancelrequestconfirmation": "Do you really want cancel this data request?",
"createnewdatarequest": "Create a new data request",
"datarequests": "Data requests",
"daterequested": "Date requested",
"deletemyaccount": "Delete my account",
"message": "Message",
"newrequest": "New request",
"nodatarequests": "There are no data requests",
"pluginname": "Data privacy",
"replyto": "Reply to",
"requestactions": "Actions",
"requestby": "Requested by",
"requestcomments": "Comments",
"requeststatus": "Status",
"requestsubmitted": "Your request has been submitted to the privacy officer",
"requesttype_help": "Select the reason for contacting the privacy officer. Be aware that deletion of all personal data will result in you no longer being able to log in to the site.",
"requesttype": "Type",
"requesttypedelete": "Delete all of my personal data",
"requesttypeexport": "Export all of my personal data",
"requesttypeothers": "General enquiry",
"send": "Send",
"statusapproved": "Approved",
"statusawaitingapproval": "Awaiting approval",
"statuscancelled": "Cancelled",
"statuscomplete": "Complete",
"statusdeleted": "Deleted",
"statusexpired": "Expired",
"statuspending": "Pending",
"statuspreprocessing": "Pre-processing",
"statusprocessing": "Processing",
"statusready": "Download ready",
"statusrejected": "Rejected"
}

View File

@ -0,0 +1,160 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate" />
</ion-buttons>
<ion-title>
<h1>{{ 'core.dataprivacy.pluginname' | translate }}</h1>
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshContent($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}" />
</ion-refresher>
<core-loading [hideUntil]="loaded">
<ion-item-divider class="ion-text-wrap">
<ion-label>
<h2 class="big">{{ 'core.dataprivacy.datarequests' | translate }}</h2>
</ion-label>
</ion-item-divider>
<ion-list *ngIf="requests.length && !isTablet">
<ion-card *ngFor=" let request of requests">
<ion-item class="ion-text-wrap">
<ion-label>
<p class="item-heading">
<ng-container *ngTemplateOutlet="type; context: {request: request}" />
</p>
<ion-row class="ion-justify-content-between ion-no-padding">
<ion-col class="ion-no-padding">
<p>{{request.timecreated * 1000 | coreFormatDate }}</p>
</ion-col>
<ion-col class="core-flex-no-grow ion-text-end ion-no-padding">
<ng-container *ngTemplateOutlet="statusBadge; context: {request: request}" />
</ion-col>
</ion-row>
</ion-label>
</ion-item>
<ion-item button [detail]="false" *ngIf="request.requestedbyuser" core-user-link [userId]="request.requestedbyuser.id"
[attr.aria-label]="request.requestedbyuser.fullname" class="ion-text-wrap">
<ion-label>
<p class="item-heading">{{ 'core.dataprivacy.requestby' | translate }}</p>
<p>{{ request.requestedbyuser.fullname }}</p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap" [lines]="request.canCancel ? 'full' : 'none'">
<ion-label>
<p class="item-heading">{{ 'core.dataprivacy.message' | translate }}</p>
<p><core-format-text [text]="request.messagehtml" /></p>
</ion-label>
</ion-item>
<ion-item class="ion-text-wrap ion-text-end" *ngIf="request.canCancel">
<ion-label>
<ion-button fill="outline" expand="block" (click)="cancelRequest(request.id)">
{{ 'core.dataprivacy.cancelrequest' | translate }}
</ion-button>
</ion-label>
</ion-item>
</ion-card>
</ion-list>
<table *ngIf="requests.length && isTablet" class="core-table">
<thead>
<tr>
<th>{{ 'core.dataprivacy.requesttype' | translate }}</th>
<th>{{ 'core.dataprivacy.daterequested' | translate }}</th>
<th class="shrink">{{ 'core.dataprivacy.requestby' | translate }}</th>
<th class="shrink">{{ 'core.dataprivacy.requeststatus' | translate }}</th>
<th>{{ 'core.dataprivacy.message' | translate }}</th>
<th class=" shrink">{{ 'core.dataprivacy.requestactions' | translate }}</th>
</tr>
</thead>
<tbody class="auto-striped">
<tr *ngFor=" let request of requests">
<td>
<p><ng-container *ngTemplateOutlet="type; context: {request: request}" /></p>
</td>
<td>
<p>{{request.timecreated * 1000 | coreFormatDate }}</p>
</td>
<td>
<p>{{ request.requestedbyuser.fullname }}</p>
</td>
<td>
<ng-container *ngTemplateOutlet="statusBadge; context: {request: request}" />
</td>
<td>
<p><core-format-text [text]="request.messagehtml" /></p>
</td>
<td>
<ion-button fill="outline" size="small" (click)="cancelRequest(request.id)" *ngIf="request.canCancel">
{{ 'core.dataprivacy.cancelrequest' | translate }}
</ion-button>
</td>
</tr>
</tbody>
</table>
<core-empty-box *ngIf="!requests.length" icon="fas-bell-concierge" [message]="'core.dataprivacy.nodatarequests' | translate" />
<div collapsible-footer *ngIf="loaded" slot="fixed">
<div class="list-item-limited-width adaptable-buttons-row">
<ion-button class="ion-margin ion-text-wrap" expand="block" (click)="contactDPO()" *ngIf="accessInfo?.cancontactdpo"
fill="outline">
<ion-icon slot="start" name="fas-envelope" [attr.aria-hidden]="true" />
{{ 'core.dataprivacy.contactdataprotectionofficer' | translate }}
</ion-button>
<ion-button class="ion-margin ion-text-wrap" expand="block" (click)="newRequest()"
*ngIf="accessInfo?.cancreatedatadownloadrequest || accessInfo?.cancreatedatadeletionrequest">
<ion-icon slot="start" name="fas-pen-to-square" [attr.aria-hidden]="true" />
{{ 'core.dataprivacy.newrequest' | translate }}
</ion-button>
</div>
</div>
</core-loading>
</ion-content>
<ng-template #statusBadge let-request="request">
@switch (request.status) {
@case (0) {
<ion-badge color="info">{{'core.dataprivacy.statuspending' | translate }}</ion-badge>
} @case (1) {
<ion-badge color="info">{{'core.dataprivacy.statuspreprocessing' | translate }}</ion-badge>
} @case (2) {
<ion-badge color="info">{{'core.dataprivacy.statusawaitingapproval' | translate }}</ion-badge>
} @case (3) {
<ion-badge color="info">{{'core.dataprivacy.statusapproved' | translate }}</ion-badge>
} @case (4) {
<ion-badge color="info">{{'core.dataprivacy.statusprocessing' | translate }}</ion-badge>
} @case (5) {
<ion-badge color="success">{{'core.dataprivacy.statuscomplete' | translate }}</ion-badge>
} @case (6) {
<ion-badge color="warning">{{'core.dataprivacy.statuscancelled' | translate }}</ion-badge>
} @case (7) {
<ion-badge color="danger">{{'core.dataprivacy.statusrejected' | translate }}</ion-badge>
} @case (8) {
<ion-badge color="success">{{'core.dataprivacy.statusready' | translate }}</ion-badge>
} @case (9) {
<ion-badge color="secondary">{{'core.dataprivacy.statusexpired' | translate }}</ion-badge>
} @case (10) {
<ion-badge color="success">{{'core.dataprivacy.statusdeleted' | translate }}</ion-badge>
} @default {
<ion-badge class="ion-text-wrap">{{request.statuslabel}}</ion-badge>
}
}
</ng-template>
<ng-template #type let-request="request">
@switch (request.type) {
@case (1) {
{{ 'core.dataprivacy.requesttypeexport' | translate }}
} @case (2) {
{{ 'core.dataprivacy.requesttypedelete' | translate }}
} @case (3) {
{{ 'core.dataprivacy.requesttypeothers' | translate }}
} @default {
{{request.typename}}
}
}
</ng-template>

View File

@ -0,0 +1,10 @@
table {
th {
width: 20%;
}
th.shrink {
width: 1%;
}
}

View File

@ -0,0 +1,167 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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, OnInit } from '@angular/core';
import { CoreDataPrivacyContactDPOComponent } from '@features/dataprivacy/components/contactdpo/contactdpo';
import { CoreDataPrivacyNewRequestComponent } from '@features/dataprivacy/components/newrequest/newrequest';
import {
CoreDataPrivacy,
CoreDataPrivacyGetAccessInformationWSResponse,
CoreDataPrivacyRequest,
} from '@features/dataprivacy/services/dataprivacy';
import { CoreScreen } from '@services/screen';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { Subscription } from 'rxjs';
/**
* Page to display the main data privacy page.
*/
@Component({
selector: 'page-core-data-privacy-main',
templateUrl: 'main.html',
styleUrl: 'main.scss',
})
export class CoreDataPrivacyMainPage implements OnInit {
accessInfo?: CoreDataPrivacyGetAccessInformationWSResponse;
requests: CoreDataPrivacyRequestToDisplay[] = [];
loaded = false;
isTablet = false;
layoutSubscription?: Subscription;
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
this.fetchContent();
this.isTablet = CoreScreen.isTablet;
this.layoutSubscription = CoreScreen.layoutObservable.subscribe(() => {
this.isTablet = CoreScreen.isTablet;
});
}
/**
* Fetch page content.
*/
async fetchContent(): Promise<void> {
try {
this.accessInfo = await CoreDataPrivacy.getAccessInformation();
this.requests = await CoreDataPrivacy.getDataRequests();
this.requests.forEach((request) => {
request.canCancel = CoreDataPrivacy.canCancelRequest(request);
});
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error fetching data privacy information', true);
} finally {
this.loaded = true;
}
}
/**
* Refresh the page content.
*
* @param refresher Refresher.
*/
async refreshContent(refresher?: HTMLIonRefresherElement): Promise<void> {
await CoreUtils.ignoreErrors(
CoreDataPrivacy.invalidateAll(),
);
await CoreUtils.ignoreErrors(this.fetchContent());
refresher?.complete();
}
/**
* Open the contact DPO modal.
*/
async contactDPO(): Promise<void> {
// Create and show the modal.
const succeed = await CoreDomUtils.openModal<boolean>({
component: CoreDataPrivacyContactDPOComponent,
});
if (succeed) {
const modal = await CoreDomUtils.showModalLoading();
try {
await this.refreshContent();
} finally {
modal.dismiss();
}
}
}
/**
* Open the new request modal.
*/
async newRequest(): Promise<void> {
// Create and show the modal.
const succeed = await CoreDomUtils.openModal<boolean>({
component: CoreDataPrivacyNewRequestComponent,
componentProps: {
accessInfo: this.accessInfo,
},
});
if (succeed) {
const modal = await CoreDomUtils.showModalLoading();
try {
await this.refreshContent();
} finally {
modal.dismiss();
}
}
}
/**
* Cancel a request.
*
* @param requestId Request ID.
*/
async cancelRequest(requestId: number): Promise<void> {
try {
await CoreDomUtils.showConfirm(
Translate.instant('core.dataprivacy.cancelrequestconfirmation'),
Translate.instant('core.dataprivacy.cancelrequest'),
Translate.instant('core.dataprivacy.cancelrequest'),
);
} catch {
return;
}
const modal = await CoreDomUtils.showModalLoading();
try {
await CoreDataPrivacy.cancelDataRequest(requestId);
await this.refreshContent();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error cancelling data privacy request');
} finally {
modal.dismiss();
}
}
}
type CoreDataPrivacyRequestToDisplay = CoreDataPrivacyRequest & {
canCancel?: boolean;
};

View File

@ -170,7 +170,7 @@ export class CoreDataPrivacyService {
async createDataRequest(type: CoreDataPrivacyDataRequestType, comments: string, siteId?: string): Promise<number> {
const site = await CoreSites.getSite(siteId);
const params: CoreDataPrivacyCreateDataequestWSParams = {
const params: CoreDataPrivacyCreateDataRequestWSParams = {
type,
comments,
};
@ -292,7 +292,7 @@ type CoreDataPrivacyContactDPOWSResponse = {
/**
* Params of tool_dataprivacy_create_data_request WS.
*/
type CoreDataPrivacyCreateDataequestWSParams = {
type CoreDataPrivacyCreateDataRequestWSParams = {
type: CoreDataPrivacyDataRequestType; // The type of data request to create. 1 for export, 2 for data deletion.
comments?: string; // Comments for the data request.
foruserid?: number; // The id of the user to create the data request for. Empty for current user.

View File

@ -24,8 +24,6 @@ export const CORE_EDITOR_SERVICES: Type<unknown>[] = [
];
@NgModule({
declarations: [
],
imports: [
CoreEditorComponentsModule,
],