Skip to content

Commit

Permalink
feat(orga): create attestations page
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre-Monney committed Nov 14, 2024
1 parent b03787f commit fe104f7
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 0 deletions.
50 changes: 50 additions & 0 deletions orga/app/components/attestations/sixth-grade.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import PixButton from '@1024pix/pix-ui/components/pix-button';
import PixMultiSelect from '@1024pix/pix-ui/components/pix-multi-select';
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { t } from 'ember-intl';

export default class AttestationsSixthGrade extends Component {
@tracked selectedDivisions = [];

@action
onSubmit(event) {
event.preventDefault();

this.args.onSubmit(this.selectedDivisions);
}

@action
onSelectDivision(value) {
this.selectedDivisions = value;
}

get isDisabled() {
return !this.selectedDivisions.length;
}

<template>
<h1 class="attestations-page__title">{{t "pages.attestations.title"}}</h1>

<p class="attestations-page__text">
{{t "pages.attestations.description" htmlSafe=true}}
</p>

<form class="attestations-page__action" {{on "submit" this.onSubmit}}>
<PixMultiSelect
@options={{@divisions}}
@values={{this.selectedDivisions}}
@onChange={{this.onSelectDivision}}
@placeholder={{t "pages.attestations.placeholder"}}
>
<:label>{{t "pages.attestations.select-label"}}</:label>
<:default as |option|>{{option.label}}</:default>
</PixMultiSelect>
<PixButton @type="submit" id="download_attestations" @size="small" @isDisabled={{this.isDisabled}}>
{{t "pages.attestations.download-attestations-button"}}
</PixButton>
</form>
</template>
}
31 changes: 31 additions & 0 deletions orga/app/controllers/authenticated/attestations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { service } from '@ember/service';

export const SIXTH_GRADE_ATTESTATION_KEY = 'SIXTH_GRADE';
export const SIXTH_GRADE_ATTESTATION_FILE_NAME = 'attestations';

export default class AuthenticatedAttestationsController extends Controller {
@service fileSaver;
@service session;
@service currentUser;
@service notifications;

@action
async downloadSixthGradeAttestationsFile(selectedDivisions) {
try {
const organizationId = this.currentUser.organization.id;
const formatedDivisionsForQuery = selectedDivisions
.map((division) => `divisions[]=${encodeURIComponent(division)}`)
.join('&');

const url = `/api/organizations/${organizationId}/attestations/${SIXTH_GRADE_ATTESTATION_KEY}?${formatedDivisionsForQuery}`;

const token = this.session.isAuthenticated ? this.session.data.authenticated.access_token : '';

await this.fileSaver.save({ url, token, fileName: SIXTH_GRADE_ATTESTATION_FILE_NAME });
} catch (error) {
this.notifications.sendError(error.message, { autoClear: false });
}
}
}
19 changes: 19 additions & 0 deletions orga/app/routes/authenticated/attestations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';

export default class AuthenticatedAttestationsRoute extends Route {
@service currentUser;
@service router;

beforeModel() {
if (!this.currentUser.canAccessAttestationsPage) {
this.router.replaceWith('application');
}
}

async model() {
const divisions = await this.currentUser.organization.divisions;
const options = divisions.map(({ name }) => ({ label: name, value: name }));
return { options };
}
}
1 change: 1 addition & 0 deletions orga/app/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
@import 'pages/join';
@import 'pages/join-request';
@import 'pages/authenticated';
@import 'pages/authenticated/attestations';
@import 'pages/authenticated/campaigns/campaign';
@import 'pages/authenticated/campaigns/create-form';
@import 'pages/authenticated/campaigns/new';
Expand Down
25 changes: 25 additions & 0 deletions orga/app/styles/pages/authenticated/attestations.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.attestations-page {
display: flex;
flex-direction: column;

&__title {
@extend %pix-title-m;
margin-bottom: var(--pix-spacing-8x);
}

&__text {
@extend %pix-body-m;
margin-bottom: var(--pix-spacing-8x);

//margin: 2px 0 32px 0;

}

&__action {
display: flex;
flex-direction: column;
flex-wrap: wrap;
gap: var(--pix-spacing-4x);
align-items: start;
}
}
5 changes: 5 additions & 0 deletions orga/app/templates/authenticated/attestations.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{page-title (t "pages.attestations.title")}}

<div class="attestations-page">
<Attestations::SixthGrade @divisions={{@model.options}} @onSubmit={{this.downloadSixthGradeAttestationsFile}} />
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { render } from '@1024pix/ember-testing-library';
import { click } from '@ember/test-helpers';
import { t } from 'ember-intl/test-support';
import SixthGrade from 'pix-orga/components/attestations/sixth-grade';
import { module, test } from 'qunit';
import sinon from 'sinon';

import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering';

module('Integration | Component | Attestations | Sixth-grade', function (hooks) {
setupIntlRenderingTest(hooks);

test('it should display all basics informations', async function (assert) {
// given
const onSubmit = sinon.stub();
const divisions = [];

// when
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);

// then
assert.ok(screen.getByRole('heading', { name: t('pages.attestations.title') }));
assert.ok(
screen.getByText('Sélectionnez la classe pour laquelle vous souhaitez exporter les attestations au format PDF.', {
exact: false,
}),
);
assert.ok(screen.getByRole('button', { name: t('pages.attestations.select-label') }));
assert.ok(screen.getByText(t('pages.attestations.placeholder')));
assert.ok(screen.getByRole('button', { name: t('pages.attestations.download-attestations-button') }));
});

test('download button is disabled if there is no selected divisions', async function (assert) {
// given
const onSubmit = sinon.stub();
const divisions = [];

// when
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);

// then
const downloadButton = await screen.getByRole('button', {
name: t('pages.attestations.download-attestations-button'),
});
assert.dom(downloadButton).isDisabled();
});

test('it should call onSubmit action with selected divisions', async function (assert) {
// given
const onSubmit = sinon.stub();

const divisions = [{ label: 'division1', value: 'division1' }];

// when
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);

const multiSelect = await screen.getByRole('button', { name: t('pages.attestations.select-label') });
await click(multiSelect);

const firstDivisionOption = await screen.findByRole('checkbox', { name: 'division1' });
await click(firstDivisionOption);

const downloadButton = await screen.getByRole('button', {
name: t('pages.attestations.download-attestations-button'),
});

// we need to get out of input choice to click on download button, so we have to click again on the multiselect to close it
await click(multiSelect);
await click(downloadButton);

// then
sinon.assert.calledWithExactly(onSubmit, ['division1']);
assert.ok(true);
});
});
93 changes: 93 additions & 0 deletions orga/tests/unit/controllers/authenticated/attestations-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Service from '@ember/service';
import {
SIXTH_GRADE_ATTESTATION_FILE_NAME,
SIXTH_GRADE_ATTESTATION_KEY,
} from 'pix-orga/controllers/authenticated/attestations';
import { module, test } from 'qunit';
import sinon from 'sinon';

import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering';

module('Unit | Controller | authenticated/attestations', function (hooks) {
setupIntlRenderingTest(hooks);

module('#downloadSixthGradeAttestationsFile', function () {
test('should call the file-saver service with the right parameters', async function (assert) {
// given
const controller = this.owner.lookup('controller:authenticated/attestations');

const token = 'a token';
const organizationId = 12345;
const selectedDivision = ['3èmea'];

controller.session = {
isAuthenticated: true,
data: {
authenticated: {
access_token: token,
},
},
};

controller.currentUser = {
organization: {
id: organizationId,
},
};

controller.fileSaver = {
save: sinon.stub(),
};

controller.model = {
options: [{ label: '3èmeA', value: '3èmeA' }],
};

// when
await controller.downloadSixthGradeAttestationsFile(selectedDivision);

// then
assert.ok(
controller.fileSaver.save.calledWith({
token,
url: `/api/organizations/${organizationId}/attestations/${SIXTH_GRADE_ATTESTATION_KEY}?divisions[]=${encodeURIComponent(selectedDivision)}`,
fileName: SIXTH_GRADE_ATTESTATION_FILE_NAME,
}),
);
});

test('it should not call file-save service and display an error if an error occurs', async function (assert) {
// given
const controller = this.owner.lookup('controller:authenticated/attestations');
const selectedDivision = ['3emeA'];
const organizationId = 123;
const errorMessage = 'oops';

controller.currentUser = {
organization: {
id: organizationId,
},
};
controller.fileSaver = {
save: sinon.stub(),
};
controller.fileSaver.save.rejects(new Error(errorMessage));

controller.model = {
options: [{ label: '3èmeA', value: '3èmeA' }],
};
class NotificationsStub extends Service {
sendError = errorMock;
}
this.owner.register('service:notifications', NotificationsStub);
const errorMock = sinon.stub();

// when
await controller.downloadSixthGradeAttestationsFile(selectedDivision);

// then
sinon.assert.calledWith(errorMock, errorMessage, { autoClear: false });
assert.ok(true);
});
});
});
Loading

0 comments on commit fe104f7

Please sign in to comment.