Skip to content
Commits on Source (45)
......@@ -39,7 +39,7 @@ stages:
- !reference [.rules-map, on-dev]
- !reference [.rules-map, not-on-semantic-release-commit]
- !reference [.rules-map, on-branch]
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13
image: hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
before_script:
- cd app
cache:
......
# The tag here should match the Meteor version of your app, per .meteor/release
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13
FROM hub.eole.education/proxyhub/geoffreybooth/meteor-base:2.13.3
# Copy app package.json and package-lock.json into container
COPY ./app/package*.json $APP_SOURCE_FOLDER/
......
# L'application **Agenda** environnement DEV :
- [Installation](#installation)
- [Application : Laboite](#application-laboite)
- [Application : LaBoite](#application-laboite)
- [Application : Agenda](#application-agenda)
- [Paramètres](#paramètres)
- [Lancer le projet](#lancer-le-projet)
- [Dans un terminal **laboite**](#dans-un-terminal-laboite)
- [Dans un terminal **laBoite**](#dans-un-terminal-laboite)
- [Lancer un autre terminal **agenda**](#lancer-un-autre-terminal-agenda)
- [Ajouter des groupes à votre utilisateur](#ajouter-des-groupes-à-votre-utilisateur)
- [Via l'interface utilisateur **localhost:3000**](#via-linterface-utilisateur-localhost3000)
......@@ -14,7 +14,7 @@
## Installation
### Application : Laboite
### Application : LaBoite
Procédure d'installation :
......@@ -45,7 +45,7 @@ Se reporter au [document relatif à la configuration](config/LISEZ-MOI.md).
## Lancer le projet
### Dans un terminal **laboite**
### Dans un terminal **laBoite**
```
cd laboite/app
......@@ -75,7 +75,7 @@ http://localhost:3030
#### Via l'interface utilisateur **localhost:3000**
À partir de l'application `laboite` que vous accédez à partir du navigateur
À partir de l'application `laBoite` que vous accédez à partir du navigateur
```
http://localhost:3000
......
# The DEV environment **Agenda** application :
- [Install](#install)
- [Application : Laboite](#application-laboite)
- [Application : LaBoite](#application-laboite)
- [Application : Agenda](#application-agenda)
- [Parameters](#parameters)
- [Run project](#run-project)
- [In a terminal **laboite**](#in-a-terminal-laboite)
- [In a terminal **laBoite**](#in-a-terminal-laboite)
- [Run an other terminal **agenda**](#run-an-other-terminal-agenda)
- [Add groups to your user](#add-groups-to-your-user)
- [In user interface **localhost:3000**](#in-user-interface-localhost3000)
......@@ -14,7 +14,7 @@
## Install
### Application : Laboite
### Application : LaBoite
Install process :
......@@ -44,7 +44,7 @@ To run **Agenda** locally, you need to configure a **LaBoite**' local instance w
## Run project
### In a terminal **laboite**
### In a terminal **laBoite**
```
cd laboite/app
......
......@@ -47,11 +47,11 @@ johanbrook:publication-collector@1.1.0
launch-screen@1.3.0
localstorage@1.2.0
logging@1.3.2
matb33:collection-hooks@1.2.2
matb33:collection-hooks@1.3.0
mdg:validated-method@1.3.0
meteor@1.11.3
meteor-base@1.5.1
meteortesting:browser-tests@1.5.1
meteortesting:browser-tests@1.5.3
meteortesting:mocha@2.1.0
meteortesting:mocha-core@8.1.2
mexar:mdt@0.2.2
......@@ -99,4 +99,4 @@ universe:i18n@1.32.6
url@1.3.2
webapp@1.13.5
webapp-hashing@1.1.1
zodern:types@1.0.9
zodern:types@1.0.10
......@@ -27,6 +27,7 @@
"closeButton": "Cancel",
"eventCreated": "Event created with success",
"needTitle": "A title is required",
"needEventType": "An event type is required",
"startDateMustBeAfterToday": "Start date must be after today",
"startMustBeAfterNow": "The event has already started",
"endMustBeAfterBegin": "The end of the event must be after the beginning"
......@@ -35,6 +36,7 @@
"title": "Event",
"closeButton": "Close",
"editButton": "Edit",
"copyButton": "Duplicate",
"cancelButton": "Cancel",
"participants": "Participants",
"informations": "Information",
......@@ -52,6 +54,7 @@
"closeButton": "Cancel",
"delete": "Delete",
"eventCreated": "Event updated with success",
"eventCopied": "Event duplicated with success",
"eventDeleted": "Event deleted with success"
},
"FormEvent": {
......@@ -100,7 +103,9 @@
"importButton": "Import",
"exportButton": "Export",
"displayWeekendButton": "Show Week-End",
"cantMoveRecurrent": "Recurrent event can not be moved"
"cantMoveRecurrent": "Recurrent event can not be moved",
"eventsImported": "Events imported successfully",
"importPending": "Import in progress..."
},
"Footer": {
"legal": "Legal",
......@@ -145,6 +150,20 @@
"en": "English",
"fr": "French"
},
"eventType": {
"type": "Type",
"rdv": "appointment",
"préférés": "favorite",
"projets": "project",
"problèmes": "problems",
"suivis": "follow up",
"personnel": "personal",
"réunion": "meeting",
"divers": "diverse",
"ferie": "public holiday",
"appels": "call",
"congés": "holiday"
},
"SimpleSchema": {
"required": "{$label} is required",
"minString": "{$label} must be at least {$min} characters",
......
......@@ -5,8 +5,8 @@
"logout": "Déconnexion en cours"
},
"notifications": {
"newMeetingEvent": "Nouvel évenement sur l'agenda",
"youAreInvitedTo": "Vous avez été invité à participer à l'évenement"
"newMeetingEvent": "Nouvel évènement sur l'agenda",
"youAreInvitedTo": "Vous avez été invité à participer à l'évènement"
},
"api": {
"appsettings": {
......@@ -25,16 +25,18 @@
"title": "Ajouter un évènement",
"validateButton": "Valider",
"closeButton": "Annuler",
"eventCreated": "Évenement créé avec succès",
"eventCreated": "Évènement créé avec succès",
"needTitle": "Un titre est requis",
"needEventType": "Un type d'évènements est requis",
"startDateMustBeAfterToday": "La date doit être après aujourd'hui",
"startMustBeAfterNow": "Le début de l'événement est déjà passé",
"endMustBeAfterBegin": "La fin de l'événement doit être après le début"
"startMustBeAfterNow": "Le début de l'évènement est déjà passé",
"endMustBeAfterBegin": "La fin de l'évènement doit être après le début"
},
"ReadEvent": {
"title": "Évenement",
"title": "Évènement",
"closeButton": "Fermer",
"editButton": "Éditer",
"copyButton": "Dupliquer",
"cancelButton": "Annuler",
"accept": "Accepter",
"refuse": "Refuser",
......@@ -51,8 +53,9 @@
"validateButton": "Valider",
"closeButton": "Annuler",
"delete": "Supprimer",
"eventCreated": "Évenement modifié avec succès",
"eventDeleted": "Évenement supprimé avec succès"
"eventCreated": "Évènement modifié avec succès",
"eventCopied": "Évènement dupliqué avec succès",
"eventDeleted": "Évènement supprimé avec succès"
},
"FormEvent": {
"eventTitle": "Titre",
......@@ -71,7 +74,7 @@
"allDay": "Journée entière",
"searchUsers": "Entrez un email",
"addAsAGuest": "Ajouter en tant qu'invité",
"recurrent": "Évenement régulier",
"recurrent": "Évènement régulier",
"from": "A partir de",
"to": "Jusqu'au",
"day_0": "Dimanche",
......@@ -81,9 +84,9 @@
"day_4": "Jeudi",
"day_5": "Vendredi",
"day_6": "Samedi",
"daysOfWeek": "Jours de l'évenement",
"daysOfWeek": "Jours de l'évènement",
"youCantRemoveToday": "Vous ne pouvez pas enlever aujourd'hui",
"searchUserHelp": "Recherchez un utilisateur de la boîte ou ajoutez un invité externe",
"searchUserHelp": "Recherchez un utilisateur de LaBoite ou ajoutez un invité externe",
"endDateMustBeAfterStartDate": "La date de fin doit être après la date de début",
"endTimeMustBeAfterStartTime": "L'heure de fin doit être après l'heure de début"
},
......@@ -100,7 +103,9 @@
"importButton": "Import",
"exportButton": "Export",
"displayWeekendButton": "Week-Ends",
"cantMoveRecurrent": "Un évenement récurrent ne peut pas être déplacé"
"cantMoveRecurrent": "Un évènement récurrent ne peut pas être déplacé",
"eventsImported": "Évènements importés avec succès",
"importPending": "Import en cours..."
},
"Footer": {
"legal": "Mentions légales",
......@@ -115,7 +120,7 @@
},
"MainMenu": {
"menuLogoutLabel": "Se déconnecter",
"menuAddEvent": "Ajouter événement",
"menuAddEvent": "Ajouter évènement",
"copyCaldavUrl": "Mon URL Caldav",
"successCopyCaldav": "Url copié avec succès"
},
......@@ -145,6 +150,20 @@
"en": "Anglais",
"fr": "Français"
},
"eventType": {
"type": "Type",
"rdv": "rendez-vous",
"préférés": "préféré",
"projets": "projet",
"problèmes": "problème",
"suivis": "suivis",
"personnel": "personel",
"réunion": "réunion",
"divers": "divers",
"ferie": "jour férié",
"appels": "appels",
"congés": "congés"
},
"SimpleSchema": {
"required": "{$label} est requis",
"minString": "{$label} doit faire au moins {$min} caractères",
......
......@@ -41,7 +41,7 @@ const sendEmail = (event, userId) => {
return Email.send({
to: guest,
from: Meteor.settings.private.smtp.fromEmail,
subject: `Laboite - Agenda - Votre rdv du ${moment(event.start).format('L')}`,
subject: `LaBoite - Agenda - Votre rdv du ${moment(event.start).format('L')}`,
icalEvent: cal.toString(),
inReplyTo: Meteor.settings.private.smtp.toEmail,
html,
......
......@@ -58,6 +58,10 @@ EventsAgenda.schema = new SimpleSchema(
regEx: SimpleSchema.RegEx.Id,
min: 1,
},
eventType: {
type: String,
optional: true,
},
title: {
type: String,
},
......@@ -190,6 +194,7 @@ EventsAgenda.publicFields = {
participants: 1,
guests: 1,
authorId: 1,
eventType: 1,
};
EventsAgenda.attachSchema(EventsAgenda.schema);
......
......@@ -19,6 +19,30 @@ export const validateEvent = (data) => {
data.participants.forEach((participant) => validateString(participant.email));
};
export const _createEvent = (data) => {
validateEvent(data);
const result = Events.insert(data);
if (result && Meteor.isServer && !Meteor.isTest) {
// eslint-disable-next-line global-require
const sendnotif = require('../notifications/server/notifSender').default;
// eslint-disable-next-line global-require
const sendEmail = require('../emails/server/methods').default;
if (data.participants && data.participants.filter((p) => p._id !== this.userId).length) {
sendnotif({
groups: data.groups,
participants: data.participants.filter((p) => p._id !== this.userId),
title: i18n.__('notifications.newMeetingEvent'),
content: `${i18n.__('notifications.youAreInvitedTo')} ${data.title}`,
eventId: result,
});
}
if (data.guests && data.guests.length) {
sendEmail(data, this.userId);
}
}
return result;
};
export const createEvent = new ValidatedMethod({
name: 'events.create',
validate: new SimpleSchema({
......@@ -30,27 +54,7 @@ export const createEvent = new ValidatedMethod({
if (!isActive(this.userId)) {
throw new Meteor.Error('api.events.create.notLoggedIn', i18n.__('api.users.notLoggedIn'));
}
validateEvent(data);
const result = Events.insert(data);
if (result && Meteor.isServer && !Meteor.isTest) {
// eslint-disable-next-line global-require
const sendnotif = require('../notifications/server/notifSender').default;
// eslint-disable-next-line global-require
const sendEmail = require('../emails/server/methods').default;
if (data.participants && data.participants.filter((p) => p._id !== this.userId).length) {
sendnotif({
groups: data.groups,
participants: data.participants.filter((p) => p._id !== this.userId),
title: i18n.__('notifications.newMeetingEvent'),
content: `${i18n.__('notifications.youAreInvitedTo')} ${data.title}`,
eventId: result,
});
}
if (data.guests && data.guests.length) {
sendEmail(data, this.userId);
}
}
return result;
_createEvent(data);
} catch (error) {
throw new Meteor.Error(error.code, error.message);
}
......
......@@ -7,6 +7,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
import i18n from 'meteor/universe:i18n';
import { isActive } from '../../utils/functions';
import { _createEvent } from '../methods';
import Events from '../events';
export const getEvent = new ValidatedMethod({
......@@ -27,8 +28,30 @@ export const getEvent = new ValidatedMethod({
},
});
export const importEvents = new ValidatedMethod({
name: 'events.import',
// data will be validated on the fly in the run method because
// validating all data at once here was taking a looooot of time
validate: null,
run({ data }) {
try {
if (!isActive(this.userId)) {
throw new Meteor.Error('api.events.import.notLoggedIn', i18n.__('api.users.notLoggedIn'));
}
const evtSchema = Events.schema.omit('createdAt', 'updatedAt', 'userId', '_id');
data.forEach((event) => {
const validatedEvent = evtSchema.clean(event);
_createEvent(validatedEvent);
});
} catch (error) {
throw new Meteor.Error(error.code, error.message);
}
},
});
// Get list of all method names on User
const LISTS_METHODS = _.pluck([getEvent], 'name');
const LISTS_METHODS = _.pluck([getEvent, importEvents], 'name');
if (Meteor.isServer) {
// Only allow 5 list operations per connection per second
......
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { useEffect, useState } from 'react';
import i18n from 'meteor/universe:i18n';
import PropTypes from 'prop-types';
......@@ -8,6 +9,11 @@ import TextField from '@material-ui/core/TextField';
import Divider from '@material-ui/core/Divider';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import InputLabel from '@material-ui/core/InputLabel';
import EVENTS_COLOR from '../../utils/eventsColor';
// import moment from 'moment';
const useStyles = makeStyles(() => ({
......@@ -78,6 +84,34 @@ const InformationsForm = ({ stateHook: [state, setState], errors }) => {
onChange={(e) => setState({ location: e.target.value })}
/>
</Grid>
<Grid md={10} xs={12} item>
<FormControl fullWidth sx={{ m: 1 }} variant="outlined">
<InputLabel id="selectCategory">{i18n.__('eventType.type')}</InputLabel>
<Select
labelId="selectCategory"
label={i18n.__('eventType.type')}
onChange={(e) => setState({ eventType: e.target.value })}
value={state.eventType ?? Object.keys(EVENTS_COLOR)[0]}
>
{Object.entries(EVENTS_COLOR).map(([type, color]) => (
<MenuItem value={type} key={type}>
<div style={{ display: 'flex', alignItems: 'center', height: '2vh' }}>
<div
style={{
backgroundColor: color,
height: 20,
width: 20,
borderRadius: 20,
marginRight: 20,
}}
/>
<p>{i18n.__(`eventType.${type}`)}</p>
</div>
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
{/* {moment(state.startDate).isSame(state.endDate) && (
<>
<Grid item md={12} xs={12}>
......
......@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
import moment from 'moment';
import i18n from 'meteor/universe:i18n';
import { useObjectState } from '../../../api/utils/hooks';
import EVENTS_COLOR from '../../utils/eventsColor';
export const initialState = {
startDate: '',
......@@ -20,6 +21,7 @@ export const initialState = {
startRecur: '',
endRecur: '',
participateUserEvent: false,
eventType: Object.keys(EVENTS_COLOR)[0],
};
export const useErrors = (state) => {
......@@ -29,18 +31,13 @@ export const useErrors = (state) => {
useEffect(() => {
setErrors({ title: null, endTime: null, startTime: null, endDate: null, startDate: null });
if (!title) setErrors({ title: i18n.__('pages.AddEvent.needTitle') });
if (moment(`${startDate} ${startTime}`).isSameOrAfter(`${endDate} ${endTime}`)) {
setErrors({
endDate: i18n.__('pages.AddEvent.endMustBeAfterBegin'),
endTime: i18n.__('pages.AddEvent.endMustBeAfterBegin'),
});
}
if (moment(`${startDate} ${startTime}`).isBefore()) {
setErrors({
startDate: i18n.__('pages.AddEvent.startMustBeAfterNow'),
startTime: i18n.__('pages.AddEvent.startMustBeAfterNow'),
});
}
}, [title, endTime, startTime, endDate, startDate]);
return errors;
......
......@@ -77,7 +77,13 @@ const Footer = () => {
{i18n.__(`components.Footer.${text}`)}
</a>
) : (
<a className={classes.link} target="_blank" rel="noreferrer noopener" href={`${laboiteURL}/legal/${link}`}>
<a
key={key}
className={classes.link}
target="_blank"
rel="noreferrer noopener"
href={`${laboiteURL}/legal/${link}`}
>
{i18n.__(`components.Footer.${text}`)}
</a>
);
......
import React, { useRef, useState } from 'react';
import i18n from 'meteor/universe:i18n';
import { useTracker } from 'meteor/react-meteor-data';
import { useHistory } from 'react-router-dom';
import i18n from 'meteor/universe:i18n';
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
......@@ -22,6 +22,8 @@ import {
} from './utils';
import useCalendarStyles from './style';
import ROUTES from '../../../layouts/routes';
import EVENTS_COLOR from '../../../utils/eventsColor';
import Spinner from '../Spinner';
const plugins = [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin];
......@@ -29,6 +31,7 @@ const Calendar = () => {
const [{ language, isMobile }] = useAppContext();
const [weekends, toggleWeekends] = useToggle();
const [datesRange, setDatesRange] = useState({});
const [importICS, setImportICS] = useState(false);
const inputRef = useRef();
const history = useHistory();
const classes = useCalendarStyles(isMobile)();
......@@ -36,12 +39,6 @@ const Calendar = () => {
const buttons = useTracker(() => BUTTONS_TEXTS(), [language]);
const addEventToDate = ({ dateStr, endStr, startStr }) => {
if (
(dateStr && moment(dateStr).isBefore(moment().subtract(1, 'days'))) ||
(startStr && moment(startStr).isBefore(moment().subtract(1, 'days')))
) {
return msg.warning(i18n.__('pages.AddEvent.startDateMustBeAfterToday'));
}
return history.push(
ROUTES.ADD_EVENT_TO_DATE({
date: dateStr,
......@@ -57,6 +54,11 @@ const Calendar = () => {
const events = useEvents(datesRange);
events.forEach((event) => {
// eslint-disable-next-line no-param-reassign
event.color = EVENTS_COLOR[event.eventType];
});
const customButtons = CUSTOM_BUTTONS({
importFunc: () => inputRef.current.click(),
exportFunc: () => exportAgendaToICS(events),
......@@ -73,56 +75,65 @@ const Calendar = () => {
history.push(ROUTES.EVENT_MAKE(_id));
};
const renderEventContent = (info) => {
info.el.setAttribute('title', info.event.extendedProps.description || info.event.title);
};
return (
<div className={classes.container}>
<input
ref={inputRef}
onChange={importICSToAgenda}
onChange={(files) => importICSToAgenda(files, setImportICS)}
className={classes.hidden}
id="myfile"
type="file"
accept=".ics"
/>
<FullCalendar
locale={language}
timeZone="local"
eventAllow={() => true}
plugins={plugins}
eventClick={eventClick}
businessHours={[
{
daysOfWeek: weekends ? [1, 2, 3, 4, 5, 6, 7] : [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '18:00',
},
]}
datesSet={changeViewDates}
initialDate={moment().format()}
slotDuration="00:15:00"
dayHeaderFormat={{
weekday: isMobile ? 'short' : 'long',
omitCommas: true,
}}
dateClick={addEventToDate}
select={addEventToDate}
weekends={weekends}
events={events}
editable
selectable
eventDrop={eventDropOrResize}
eventResize={eventDropOrResize}
height={isMobile ? 'auto' : 1000}
firstDay={1}
nowIndicator
buttonText={buttons}
customButtons={customButtons}
headerToolbar={TOOLBAR}
eventTimeFormat={{
hour: 'numeric',
minute: '2-digit',
meridiem: 'short',
}}
/>
{importICS ? (
<Spinner message={i18n.__('components.Calendar.importPending')} inside />
) : (
<FullCalendar
locale={language}
timeZone="local"
eventAllow={() => true}
plugins={plugins}
eventClick={eventClick}
businessHours={[
{
daysOfWeek: weekends ? [1, 2, 3, 4, 5, 6, 7] : [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '18:00',
},
]}
datesSet={changeViewDates}
initialDate={moment().format()}
slotDuration="00:15:00"
dayHeaderFormat={{
weekday: isMobile ? 'short' : 'long',
omitCommas: true,
}}
dateClick={addEventToDate}
select={addEventToDate}
weekends={weekends}
events={events}
editable
selectable
eventDrop={eventDropOrResize}
eventResize={eventDropOrResize}
height={isMobile ? 'auto' : 1000}
firstDay={1}
nowIndicator
buttonText={buttons}
customButtons={customButtons}
headerToolbar={TOOLBAR}
eventTimeFormat={{
hour: 'numeric',
minute: '2-digit',
meridiem: 'short',
}}
eventDidMount={renderEventContent}
/>
)}
</div>
);
};
......
import { Meteor } from 'meteor/meteor';
import i18n from 'meteor/universe:i18n';
import { useTracker } from 'meteor/react-meteor-data';
import moment from 'moment';
import importICS from 'node-ical';
import exportICS from 'ical-generator';
import Events from '../../../../api/events/events';
import { editEvent, createEvent } from '../../../../api/events/methods';
import { editEvent } from '../../../../api/events/methods';
export const BUTTONS_TEXTS = () => ({
today: i18n.__('components.Calendar.today'),
......@@ -96,12 +97,12 @@ export const exportAgendaToICS = (events = []) => {
name: '<agenda>',
});
// value events when create in calendar
// Find event in array
events.forEach((line) => {
cal.createEvent({
start: line.start,
end: line.end,
categories: [{ name: line?.eventType || 'rdv' }],
timestamp: moment(),
summary: line.title,
description: line.description,
......@@ -127,14 +128,15 @@ export const exportAgendaToICS = (events = []) => {
/** ************************************************** */
/** *****************Import ICS file ******************* */
export const importICSToAgenda = (eFiles) => {
export const importICSToAgenda = (eFiles, setImporting) => {
setImporting(true);
const { files } = eFiles.target;
if (files.lenght === 0) return;
const fileToRead = files[0];
const reader = new FileReader();
reader.onload = async (eFile) => {
const file = eFile.target.result;
const data = [];
// Parse
const parseF = importICS.sync.parseICS(file);
// eslint-disable-next-line no-restricted-syntax
......@@ -149,6 +151,7 @@ export const importICSToAgenda = (eFiles) => {
allDayImport = true;
}
const title = ev.summary;
const eventType = ev.categories?.[0]?.toLowerCase() || 'rdv';
const { location } = ev;
// eslint-disable-next-line prefer-destructuring
const description = ev.description;
......@@ -156,32 +159,39 @@ export const importICSToAgenda = (eFiles) => {
? moment.utc(`${ev.start.toDateString()} 00:00`).format()
: moment(ev.start).format();
const end = allDayImport ? moment.utc(`${ev.end.toDateString()} 00:00`).format() : moment(ev.end).format();
createEvent.call(
{
data: {
title,
location,
description,
allDay: allDayImport,
start,
end,
},
},
(error) => {
if (error) {
msg.error(error.reason);
} else {
msg.success(i18n.__('pages.AddEvent.eventCreated'));
}
},
);
data.push({
title,
location,
description,
allDay: allDayImport,
start,
end,
eventType,
});
}
}
}
Meteor.call(
'events.import',
{
data,
},
(error) => {
if (error) {
setImporting(false);
msg.error(error.reason);
} else {
setImporting(false);
msg.success(i18n.__('components.Calendar.eventsImported'));
}
},
);
};
reader.onerror = (e) => msg.error(e.target.error.name);
reader.onerror = (e) => {
setImporting(false);
msg.error(e.target.error.name);
};
reader.readAsText(fileToRead);
};
......@@ -51,7 +51,12 @@ const MainLayout = ({ userFailed, setUserFailed }) => {
<Route exact path={ROUTES.LOGIN} component={Login} />
<Route exact path={ROUTES.ADD_EVENT} component={AddEvent} />
<Route exact path={ROUTES.EVENT} component={ReadEvent} />
<Route exact path={ROUTES.EVENT_EDIT} component={EditEvent} />
<Route
exact
path={ROUTES.EVENT_EDIT}
render={(props) => <EditEvent {...props} copyMode={false} />}
/>
<Route exact path={ROUTES.EVENT_COPY} render={(props) => <EditEvent {...props} copyMode />} />
<Redirect from="*" to={ROUTES.HOME} />
</Switch>
</>
......
......@@ -17,6 +17,8 @@ const ROUTES = {
ADD_EVENT_TO_GROUP: ({ groupId }) => `/add-event?${queryStringMaker({ groupId })}`,
EVENT: '/event/:_id',
EVENT_MAKE: (id) => `/event/${id}`,
EVENT_COPY: '/copy-event/:_id',
EVENT_COPY_MAKE: (id) => `/copy-event/${id}`,
EVENT_EDIT: '/edit-event/:_id',
EVENT_EDIT_MAKE: (id) => `/edit-event/${id}`,
};
......
......@@ -4,7 +4,7 @@ import i18n from 'meteor/universe:i18n';
import moment from 'moment';
import { makeStyles } from '@material-ui/core/styles';
import { editEvent, deleteEvent } from '../../api/events/methods';
import { editEvent, createEvent, deleteEvent } from '../../api/events/methods';
import { useObjectState } from '../../api/utils/hooks';
import Groups from '../../api/groups/groups';
......@@ -29,7 +29,7 @@ const useStyles = makeStyles((theme) => ({
},
}));
const EditEvent = ({ history, match: { params } }) => {
const EditEvent = ({ history, match: { params }, copyMode }) => {
const goHome = () => history.push(ROUTES.HOME);
const classes = useStyles();
const [state, setState] = useObjectState(initialState);
......@@ -135,6 +135,13 @@ const EditEvent = ({ history, match: { params } }) => {
});
}
if (copyMode) {
// if duplicating an event, reset all participants status (except organizer)
allParticipants = allParticipants.map((part) => {
return part._id === organizerId ? part : { ...part, status: 1 };
});
}
const data = {
...rest,
groups,
......@@ -149,12 +156,13 @@ const EditEvent = ({ history, match: { params } }) => {
? moment.utc(`${endDate} 00:00`).add(1, 'days').format()
: moment(`${endDate} ${endTime}`).format(),
};
editEvent.call({ data }, (error) => {
const callMethod = copyMode ? createEvent : editEvent;
callMethod.call({ data }, (error) => {
setLoading(false);
if (error) {
msg.error(error.reason || error.message);
} else {
msg.success(i18n.__('pages.EditEvent.eventCreated'));
msg.success(i18n.__(copyMode ? 'pages.EditEvent.eventCopied' : 'pages.EditEvent.eventCreated'));
history.push('/');
}
});
......@@ -208,7 +216,8 @@ const EditEvent = ({ history, match: { params } }) => {
props: { color: 'primary', disabled: !isValid },
key: 'third',
},
]}
// delete button is not available in event copy mode
].filter((button) => copyMode === false || button.key !== 'first')}
>
{loading ? (
<Spinner inside />
......@@ -229,4 +238,5 @@ export default EditEvent;
EditEvent.propTypes = {
history: PropTypes.objectOf(PropTypes.any).isRequired,
match: PropTypes.objectOf(PropTypes.any).isRequired,
copyMode: PropTypes.bool.isRequired,
};