Skip to content
Snippets Groups Projects
viewEvent.js 18.2 KiB
Newer Older
Hermann's avatar
Hermann committed
import m from 'mithril';
import { Toolbar, ToolbarTitle, Card, Button } from 'polythene-mithril';
import Stream from 'mithril/stream';
import { styler } from 'polythene-core-css';
import { DropdownCard, DatalistController, Chip } from 'amiv-web-ui-components';
Hermann's avatar
Hermann committed
// eslint-disable-next-line import/extensions
import { apiUrl } from 'networkConfig';
Hermann's avatar
Hermann committed
import ItemView from '../views/itemView';
import { eventsignups as signupConfig } from '../resourceConfig.json';
import TableView from '../views/tableView';
Hermann's avatar
Hermann committed
import RelationlistController from '../relationlistcontroller';
import { dateFormatter } from '../utils';
import { Property, FilterChip, icons } from '../views/elements';
import { ResourceHandler } from '../auth';
import { colors } from '../style';
Hermann's avatar
Hermann committed
    '.propertyLangIndicator': {
      width: '30px',
      height: '20px',
      float: 'left',
      'background-color': 'rgb(031,045,084)',
      'border-radius': '10px',
      'text-align': 'center',
      'line-height': '20px',
      color: 'rgb(255,255,255)',
      'margin-right': '10px',
      'font-size': '11px',
Hermann's avatar
Hermann committed
    },
    '.eventViewLeft': {
    },
    '.eventViewRight': {
    },
    '.eventViewRight h4': {
// small helper class to display both German and English content together, dependent
// on which content is available.
class DuoLangProperty {
  view({ attrs: { title, de, en } }) {
Hermann's avatar
Hermann committed
    // TODO Lang indicators should be smaller and there should be less margin
    // between languages
    return m(
      Property,
      { title },
      de ? m('div', [
        m('div', { className: 'propertyLangIndicator' }, 'DE'),
        m('p', de),
      ]) : '',
      en ? m('div', [
        m('div', { className: 'propertyLangIndicator' }, 'EN'),
        m('p', en),
      ]) : '',
    );
class ParticipantsSummary {
  constructor() {
    this.onlyAccepted = true;
  }

  view({ attrs: { participants, additionalFields = "{'properties': {}}" } }) {
    // Parse the JSON from additional fields into an object
    const parsedParticipants = participants.map(signup => ({
      ...signup,
      additional_fields: signup.additional_fields ?
        JSON.parse(signup.additional_fields) : {},
    }));
    // Filter if only accepted participants should be shown
    const filteredParticipants = parsedParticipants.filter(participant =>
      (this.onlyAccepted ? participant.accepted : true));

    // check which additional fields should get summarized
    let hasSBB = false;
    let hasFood = false;
    if (additionalFields) {
      hasSBB = 'sbb_abo' in JSON.parse(additionalFields).properties;
      hasFood = 'food' in JSON.parse(additionalFields).properties;
    }

    return m('div', [
      m('div', {
        style: {
          height: '50px',
          'overflow-x': 'auto',
          'overflow-y': 'hidden',
          'white-space': 'nowrap',
          padding: '0px 5px',
        },
      }, [].concat(['Filters: '], ...[
        m(FilterChip, {
          selected: this.onlyAccepted,
          onclick: () => { this.onlyAccepted = !this.onlyAccepted; },
        }, 'accepted users'),
      ])),
      hasSBB ? m('div', { style: { display: 'flex' } }, [
        m(Property, { title: 'No SBB', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.sbb_abo === 'None').length),
        m(Property, { title: 'GA', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.sbb_abo === 'GA').length),
        m(Property, { title: 'Halbtax', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.sbb_abo === 'Halbtax').length),
        m(Property, { title: 'Gleis 7', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.sbb_abo === 'Gleis 7').length),
      ]) : '',
      hasFood ? m('div', { style: { display: 'flex' } }, [
        m(Property, { title: 'Omnivors', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.food === 'Omnivor').length),
        m(Property, { title: 'Vegis', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.food === 'Vegi').length),
        m(Property, { title: 'Vegans', leftAlign: false }, filteredParticipants.filter(signup =>
          signup.additional_fields.food === 'Vegan').length),
      ]) : '',
      m('textarea', {
        style: { opacity: '0', width: '0px' },
        id: 'participantsemails',
      }, filteredParticipants.map(signup => signup.email).toString().replace(/,/g, '; ')),
      m(Button, {
        label: 'Copy Emails',
        events: {
          onclick: () => {
            document.getElementById('participantsemails').select();
            document.execCommand('copy');
          },
        },
      }),
    ]);
  }
}

// Helper class to either display the signed up participants or those on the
// waiting list.
Sandro Lutz's avatar
Sandro Lutz committed
  constructor({ attrs: { where, additional_fields_schema: additionalFieldsSchema } }) {
    this.ctrl = new RelationlistController({
      primary: 'eventsignups',
      secondary: 'users',
      query: { where },
      searchKeys: ['email'],
      includeWithoutRelation: true,
    });
    this.add_fields_schema = additionalFieldsSchema ?
      JSON.parse(additionalFieldsSchema).properties : null;
  exportAsCSV(filePrefix) {
    this.ctrl.getFullList().then((list) => {
      const csvData = (list.map((item) => {
        const additionalFields = item.additional_fields && JSON.parse(item.additional_fields);

        return [
          item.position,
          item._created,
          item.user ? item.user.firstname : '',
          item.user ? item.user.lastname : '',
          item.user ? item.user.membership : 'none',
          item.email,
          item.accepted,
          item.confirmed,
          ...Object.keys(this.add_fields_schema || {}).map(key =>
            (additionalFields && additionalFields[key] ? additionalFields[key] : '')),
        ].join(',');
      })).join('\n');

      const header = [
        'Position', 'Date', 'Firstname', 'Lastname', 'Membership', 'Email', 'Accepted', 'Confirmed',
        ...Object.keys(this.add_fields_schema || {}).map(key => this.add_fields_schema[key].title),
      ].join(',');

      const filename = `${filePrefix}_participants_export.csv`;
      const fileContent = `data:text/csv;charset=utf-8,${header}\n${csvData}`;

      const link = document.createElement('a');
      link.setAttribute('href', encodeURI(fileContent));
      link.setAttribute('download', filename);
      link.click();
    });
  }

  itemRow(data) {
    // TODO list should not have hardcoded size outside of stylesheet
    const hasPatchRights = data._links.self.methods.indexOf('PATCH') > -1;
Sandro Lutz's avatar
Sandro Lutz committed
    const additionalFields = data.additional_fields && JSON.parse(data.additional_fields);
    return [
      m('div', { style: { width: '9em' } }, dateFormatter(data._created)),
      m('div', { style: { width: '16em' } }, [
        ...data.user ? [`${data.user.firstname} ${data.user.lastname}`, m('br')] : '',
        data.email,
      ]),
Sandro Lutz's avatar
Sandro Lutz committed
      m(
        'div', { style: { width: '16em' } },
boian's avatar
boian committed
        m('div', ...data.user ? `Membership: ${data.user.membership}` : ''),
        (additionalFields && this.add_fields_schema) ? Object.keys(additionalFields).map(key =>
Sandro Lutz's avatar
Sandro Lutz committed
          m('div', `${this.add_fields_schema[key].title}: ${additionalFields[key]}`)) : '',
      ),
      m('div', { style: { 'flex-grow': '100' } }),
      hasPatchRights ? m('div', m(Button, {
        // Button to remove this eventsignup
        className: 'red-row-button',
        borders: false,
        label: 'remove',
        events: {
          onclick: () => {
            this.ctrl.handler.delete(data).then(() => {
              this.ctrl.refresh();
              m.redraw();
            });
          },
        },
      })) : '',
  view({ attrs: { title, filePrefix } }) {
      style: { height: '400px', 'margin-bottom': '10px' },
Hermann's avatar
Hermann committed
      content: m('div', [
        m(Toolbar, { compact: true }, [
          m(ToolbarTitle, { text: title }),
          m(Button, {
            className: 'blue-button',
            borders: true,
            label: 'export CSV',
            events: { onclick: () => this.exportAsCSV(filePrefix) },
          }),
Hermann's avatar
Hermann committed
        ]),
        m(TableView, {
Hermann's avatar
Hermann committed
          controller: this.ctrl,
          keys: signupConfig.tableKeys,
          tileContent: data => this.itemRow(data),
          clickOnRows: false,
Hermann's avatar
Hermann committed
          titles: [
            { text: 'Date of Signup', width: '9em' },
            { text: 'Participant', width: '16em' },
            { text: 'Additional Info', width: '16em' },
Hermann's avatar
Hermann committed
          ],
        }),
      ]),
Hermann's avatar
Hermann committed
export default class viewEvent extends ItemView {
Hermann's avatar
Hermann committed
  constructor(vnode) {
    super(vnode);
    this.signupHandler = new ResourceHandler('eventsignups');
    this.signupCtrl = new DatalistController((query, search) => this.signupHandler.get({
      search, ...query,
    }));
carol's avatar
carol committed
    this.description = false;
    this.advertisement = false;
    this.registration = false;
    this.allParticipants = [];
    this.modalDisplay = Stream('none');
    this.signupCtrl.setQuery({ where: { event: this.data._id } });
    this.signupCtrl.getFullList().then((list) => { this.allParticipants = list; });
  cloneEvent() {
    const event = Object.assign({}, this.data);
    console.log(event);

    const eventInfoToDelete = [
      '_id',
      '_created',
      '_etag',
      '_links',
      '_updated',
      'signup_count',
      '__proto__',
    ];
    const now = new Date();
    console.log(`${now.toISOString().slice(0, -5)}Z`);
    if (event.time_end < `${now.toISOString().slice(0, -5)}Z`) {
      eventInfoToDelete.push(...[
        'time_advertising_end',
        'time_advertising_start',
        'time_end',
        'time_register_end',
        'time_register_end',
        'time_register_start',
        'time_start']);
    }
    eventInfoToDelete.forEach((key) => {
      delete event[key];
    });

    console.log(event);
    this.controller.changeModus('new');
    this.controller.data = event;
  }

Hermann's avatar
Hermann committed
  view() {
    let displaySpots = '-';
    const stdMargin = { margin: '5px' };
    // Get the image and insert it inside the modal -
    // use its "alt" text as a caption
    const modalImg = document.getElementById('modalImg');

    if (this.data.spots !== 0) {
      displaySpots = this.data.spots;
    }
carol's avatar
carol committed

Hermann's avatar
Hermann committed
    return this.layout([
Hermann's avatar
Hermann committed
      // this div is the title line
Hermann's avatar
Hermann committed
      m('div.maincontainer', [
Hermann's avatar
Hermann committed
        // event image if existing
        this.data.img_thumbnail ? m('img', {
          src: `${apiUrl}${this.data.img_thumbnail.file}`,
          style: { float: 'left', margin: '0 5px' },
Hermann's avatar
Hermann committed
        m('h1', this.data.title_de || this.data.title_en),
Hermann's avatar
Hermann committed
      // below the title, most important details are listed
Hermann's avatar
Hermann committed
      m('div.maincontainer', { style: { display: 'flex' } }, [
        (this.data.spots !== null && 'signup_count' in this.data
         && this.data.signup_count !== null) ?
          m(Property, {
            style: stdMargin,
            title: 'Signups',
          }, `${this.data.signup_count} / ${displaySpots}`) : '',
        this.data.location && m(Property, {
          style: stdMargin,
Hermann's avatar
Hermann committed
          title: 'Location',
        }, `${this.data.location}`),
        this.data.time_start && m(Property, {
Hermann's avatar
Hermann committed
          title: 'Time',
          style: stdMargin,
        }, `${dateFormatter(this.data.time_start)} - ${dateFormatter(this.data.time_end)}`),
boian's avatar
boian committed
        this.data.moderator && m(Property, {
          title: 'Moderator',
          style: stdMargin,
        }, m.trust(`${this.data.moderator.firstname} ${this.data.moderator.lastname}
         (<a href='mailto:${this.data.moderator.email}'>${this.data.moderator.email}</a>)`)),
Hermann's avatar
Hermann committed
      ]),
Hermann's avatar
Hermann committed
      // everything else is not listed in DropdownCards, which open only on request
Hermann's avatar
Hermann committed
      m('div.viewcontainer', [
        m('div.viewcontainercolumn', [
          m(DropdownCard, { title: 'description' }, [
            m(DuoLangProperty, {
              title: 'Catchphrase',
              de: this.data.catchphrase_de,
              en: this.data.catchphrase_en,
            m(DuoLangProperty, {
              title: 'Description',
              de: this.data.description_de,
              en: this.data.description_en,
            }),
          ]),
carol's avatar
carol committed

          m(DropdownCard, { title: 'advertisement', style: { margin: '10px 0' } }, [
                svg: this.data.show_annonce ? icons.checked : icons.clear,
                border: '1px #aaaaaa solid',
              }, 'announce'),
                svg: this.data.show_infoscreen ? icons.checked : icons.clear,
                border: '1px #aaaaaa solid',
                margin: '4px',
              }, 'infoscreen'),
                svg: this.data.show_website ? icons.checked : icons.clear,
                border: '1px #aaaaaa solid',
              }, 'website'),
            ],
            this.data.time_advertising_start ? m(
              Property,
Hermann's avatar
Hermann committed
              { title: 'Advertising Time' },
              `${dateFormatter(this.data.time_advertising_start)} - ` +
              `${dateFormatter(this.data.time_advertising_end)}`,
            ) : '',
            this.data.priority ? m(
              Property,
              { title: 'Priority' },
              `${this.data.priority}`,
            ) : '',
          ]),

          m(DropdownCard, { title: 'Registration', style: { margin: '10px 0' } }, [
            this.data.price ? m(Property, { title: 'Price' }, `${this.data.price}`) : '',
            this.data.time_register_start ? m(
              Property,
              { title: 'Registration Time' },
Hermann's avatar
Hermann committed
              `${dateFormatter(this.data.time_register_start)} - ` +
              `${dateFormatter(this.data.time_register_end)}`,
            ) : '',
            this.data.selection_strategy ? m(
              Property,
              { title: 'Selection Mode' },
              m.trust(this.data.selection_strategy),
            ) : '',
Hermann's avatar
Hermann committed
            this.data.allow_email_signup && m(Property, 'non AMIV-Members allowed'),
            this.data.additional_fields && m(
              Property,
              { title: 'Registration Form' },
              this.data.additional_fields,
            ),
Hermann's avatar
Hermann committed
          // a list of email adresses of all participants, easy to copy-paste
          this.data.spots !== null ? m(DropdownCard, {
            title: 'Participants Summary',
            style: { margin: '10px 0' },
          }, m(ParticipantsSummary, {
            participants: this.allParticipants,
            additionalFields: this.data.additional_fields,
          })) : '',
rdoerge's avatar
rdoerge committed
          m(DropdownCard, { title: 'Images' }, [
            m('div', {
              style: {
                display: 'flex',
              },
            }, [
              m('div', {
                style: {
                  width: '40%',
                  padding: '5px',
                },
              }, [
                this.data.img_poster && m('div', 'Poster'),
                this.data.img_poster && m('img', {
                  src: `${apiUrl}${this.data.img_poster.file}`,
                  width: '100%',
                    this.modalDisplay('block');
                    modalImg.src = `${apiUrl}${this.data.img_poster.file}`;
rdoerge's avatar
rdoerge committed
              m('div', {
                style: {
                  width: '52%',
                  padding: '5px',
                },
              }, [
                m('div', [
                  this.data.img_infoscreen && m('div', 'Infoscreen'),
                  this.data.img_infoscreen && m('img', {
                    src: `${apiUrl}${this.data.img_infoscreen.file}`,
                    width: '100%',
                      this.modalDisplay('block');
                      modalImg.src = `${apiUrl}${this.data.img_infoscreen.file}`;
        m('div.viewcontainercolumn', { style: { width: '50em' } }, [
Hermann's avatar
Hermann committed
          this.data.time_register_start ? m(ParticipantsTable, {
            where: { accepted: true, event: this.data._id },
            title: 'Accepted Participants',
            filePrefix: 'accepted',
            additional_fields_schema: this.data.additional_fields,
Hermann's avatar
Hermann committed
          }) : '',
          this.data.time_register_start ? m(ParticipantsTable, {
            where: { accepted: false, event: this.data._id },
            title: 'Participants on Waiting List',
            filePrefix: 'waitinglist',
            additional_fields_schema: this.data.additional_fields,
Hermann's avatar
Hermann committed
          }) : '',
      m('div', {
        id: 'imgModal',
        style: {
          display: this.modalDisplay(),
          position: 'fixed',
          'z-index': '100',
          'padding-top': '100px',
          left: 0,
          top: 0,
          width: '100vw',
          height: '100vh',
          overflow: 'auto',
          'background-color': 'rgba(0, 0, 0, 0.9)',
        },
      }, [
        m('img', {
          id: 'modalImg',
          style: {
            margin: 'auto',
            display: 'block',
            'max-width': '80vw',
            'max-heigth': '80vh',
          },
        }),
        m('div', {
          onclick: () => {
            this.modalDisplay('none');
          },
          style: {
            top: '15px',
            right: '35px',
            color: '#f1f1f1',
            transition: '0.3s',
            'z-index': 10,
            position: 'absolute',
            'font-size': '40px',
            'font-weight': 'bold',
          },
        }, 'x'),
      ]),
    ], [
      m(Button, {
        label: 'Clone Event',
        border: true,
        style: {
          color: colors.light_blue,
          'border-color': colors.light_blue,
        },
        events: {
          // opens 'new event' ,
          // coping All information but the 'event_id',  past dates and API generated properties
          onclick: () => this.cloneEvent(),
        },
      }),
Hermann's avatar
Hermann committed
}