diff --git a/app/controllers/parameters/planning_parameters_controller.rb b/app/controllers/parameters/planning_parameters_controller.rb
index 3a57a8ce..ec09ed8d 100644
--- a/app/controllers/parameters/planning_parameters_controller.rb
+++ b/app/controllers/parameters/planning_parameters_controller.rb
@@ -79,11 +79,18 @@ def get_hours_before_cancelling_activity
end
end
- def show_activity_code
- render json: { show_activity_code: Parameter.get_value("planning.card.show_activity_code", default: false) }
+ def school_planning_params
+ authorize! :manage, Parameter
+
+ render json: {
+ show_activity_code: Parameter.get_value("planning.card.show_activity_code", default: false),
+ recurrence_activated: Parameter.get_value("planning.recurrence_activated", default: false)
+ }
end
- def update_show_activity_code
+ def update_school_planning_params
+ authorize! :manage, Parameter
+
show_activity_code = Parameter.find_or_create_by(
label: "planning.card.show_activity_code",
value_type: "boolean"
@@ -91,10 +98,19 @@ def update_show_activity_code
show_activity_code.value = (params[:show_activity_code]&.to_s == "true").to_s
- saved = show_activity_code.save!
+ show_activity_code.save!
+
+ recurrence_activated = Parameter.find_or_create_by(
+ label: "planning.recurrence_activated",
+ value_type: "boolean"
+ )
+
+ recurrence_activated.value = (params[:recurrence_activated]&.to_s == "true").to_s
+
+ recurrence_activated.save!
respond_to do |format|
- format.json { render json: { success: saved } }
+ format.json { render json: { success: true } }
end
end
end
diff --git a/app/controllers/planning_controller.rb b/app/controllers/planning_controller.rb
index 0be3ae70..934968c3 100755
--- a/app/controllers/planning_controller.rb
+++ b/app/controllers/planning_controller.rb
@@ -1,6 +1,8 @@
class PlanningController < ApplicationController
skip_before_action :verify_authenticity_token, only: [:update_availabilities]
+ before_action -> { @recurrence_activated = Parameter.get_value("planning.recurrence_activated", default: false) }, only: %i[show show_generic show_all_rooms show_for_conflict show_for_room]
+
def index_for_teachers
@current_user = current_user
plannings = Planning.includes(:user).where(users: { is_teacher: true })
diff --git a/app/models/planning.rb b/app/models/planning.rb
index ff35fbca..013c857c 100755
--- a/app/models/planning.rb
+++ b/app/models/planning.rb
@@ -38,19 +38,54 @@ def update_intervals(intervals, season_id)
end
intervals.each do |i|
+ # isRecurrent if only a param on planning when new interval is created
+ if i["recurrentType"]&.present?
+ interval = TimeInterval.new(start: i["start"], end: i["end"], kind: i["kind"] || "d", is_validated: i["is_validated"] || false)
+
+ season = Season.from_interval(interval).first || Season.current
+
+ recurrence_end = season.end
+ recurrence_step = case i["recurrentType"]
+ when "weekly"
+ 1.week
+ when "biweekly"
+ 2.weeks
+ when "monthly"
+ 1.month
+ when "bimonthly"
+ 2.months
+ when "yearly"
+ 1.year
+ else
+ 1.week
+ end
+
+ holiday_dates = season.holidays.map { |h| h.date }
+
+ while interval.end < recurrence_end
+ next if holiday_dates.include?(interval.start&.to_date)
+ intervalList << interval if interval.save
+
+ interval = interval.dup
+ interval.id = nil
+ interval.start += recurrence_step
+ interval.end += recurrence_step
+ end
+ else
if i["isNew"] == true
- interval = TimeInterval.new
+ interval = TimeInterval.new
else
- interval = TimeInterval.find_or_initialize_by(id: i['id'])
+ interval = TimeInterval.find_or_initialize_by(id: i['id'])
end
interval.start = i["start"]
interval.end = i["end"]
if !season.nil?
- interval.convert_to_first_week_of_season(season)
+ interval.convert_to_first_week_of_season(season)
end
interval.kind = i["kind"] || "d"
interval.is_validated = i["is_validated"] || false
interval.save
+ end
if self.time_intervals.where(id: interval.id).none?
intervalList << interval
diff --git a/app/views/planning/show.html.erb b/app/views/planning/show.html.erb
index fc159b8f..86541a14 100755
--- a/app/views/planning/show.html.erb
+++ b/app/views/planning/show.html.erb
@@ -74,5 +74,6 @@
show_availabilities: @show_availabilities,
teacher_can_edit: @teacher_can_edit,
currentUserId: current_user&.id,
- show_activity_code: @show_activity_code
+ show_activity_code: @show_activity_code,
+ recurrenceActivated: @recurrence_activated
}) %>
diff --git a/app/views/planning/show_all_rooms.html.erb b/app/views/planning/show_all_rooms.html.erb
index 94e771a1..ca016153 100644
--- a/app/views/planning/show_all_rooms.html.erb
+++ b/app/views/planning/show_all_rooms.html.erb
@@ -2,7 +2,7 @@
<%= @rooms.select {|r| r.id == room_id.to_i}.first.label %>
<%= react_component("planning/Planning", { show_activity_code: @show_activity_code, room: @rooms.select {|r| r.id == room_id}.first, isTeacher: current_user.teacher?, isAdmin: current_user.admin?, displayOnly: true, intervals: time_intervals, rooms: @rooms, modal: false, levels: @levels, locations: @locations, room_refs: @rooms, isRoom: true, planning: time_intervals, activity_refs: @activity_refs, teachers: @teachers,
- currentUserId: current_user&.id}) %>
+ currentUserId: current_user&.id, recurrenceActivated: @recurrence_activated}) %>
<% end %>
diff --git a/app/views/planning/show_for_conflict.html.erb b/app/views/planning/show_for_conflict.html.erb
index 06713692..3a548892 100644
--- a/app/views/planning/show_for_conflict.html.erb
+++ b/app/views/planning/show_for_conflict.html.erb
@@ -16,5 +16,6 @@
season: @season,
conflict: @conflict,
currentUserId: current_user&.id,
- show_activity_code: @show_activity_code
+ show_activity_code: @show_activity_code,
+ recurrenceActivated: @recurrence_activated
}) %>
diff --git a/app/views/planning/show_for_room.html.erb b/app/views/planning/show_for_room.html.erb
index 954ae8f7..897dbe10 100755
--- a/app/views/planning/show_for_room.html.erb
+++ b/app/views/planning/show_for_room.html.erb
@@ -37,5 +37,6 @@
teachers: @teachers,
displayRaw: true,
currentUserId: current_user&.id,
- show_activity_code: @show_activity_code
+ show_activity_code: @show_activity_code,
+ recurrenceActivated: @recurrence_activated
}) %>
diff --git a/app/views/planning/show_generic.html.erb b/app/views/planning/show_generic.html.erb
index b60d1321..612eea47 100644
--- a/app/views/planning/show_generic.html.erb
+++ b/app/views/planning/show_generic.html.erb
@@ -70,6 +70,7 @@
day: @day,
generic: true,
currentUserId: current_user&.id,
- show_activity_code: @show_activity_code
+ show_activity_code: @show_activity_code,
+ recurrenceActivated: @recurrence_activated
}) %>
diff --git a/config/routes.rb b/config/routes.rb
index 00d23cd8..188bbc62 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -53,8 +53,8 @@
get "planning_parameters", to: "planning_parameters#index"
post "planning_parameters", to: "planning_parameters#update"
- get "show_activity_code", to: "planning_parameters#show_activity_code"
- post "show_activity_code", to: "planning_parameters#update_show_activity_code"
+ get "planning/school_planning_params", to: "planning_parameters#school_planning_params"
+ post "planning/school_planning_params", to: "planning_parameters#update_school_planning_params"
get "hours_before_cancelling_activity", to: "planning_parameters#get_hours_before_cancelling_activity"
post "hours_before_cancelling_activity", to: "planning_parameters#save_hours_before_cancelling_activity"
diff --git a/frontend/components/parameters/Plannings/PlanningDisplayParameters.jsx b/frontend/components/parameters/Plannings/PlanningDisplayParameters.jsx
index c48d9177..618ad9ab 100644
--- a/frontend/components/parameters/Plannings/PlanningDisplayParameters.jsx
+++ b/frontend/components/parameters/Plannings/PlanningDisplayParameters.jsx
@@ -5,6 +5,7 @@ import swal from "sweetalert2";
export default function PlanningDisplayParameters()
{
const [showActivityCode, setShowActivityCode] = React.useState(false);
+ const [recurrenceActivated, setRecurrenceActivated] = React.useState(false);
useEffect(() =>
{
@@ -12,6 +13,7 @@ export default function PlanningDisplayParameters()
.success((data) =>
{
setShowActivityCode(data.show_activity_code);
+ setRecurrenceActivated(data.recurrence_activated);
})
.error(() =>
{
@@ -20,7 +22,7 @@ export default function PlanningDisplayParameters()
type: "error",
});
})
- .get("/parameters/show_activity_code", {});
+ .get("/parameters/planning/school_planning_params", {});
}, []);
const onSubmit = (e) =>
@@ -40,7 +42,10 @@ export default function PlanningDisplayParameters()
type: "error",
});
})
- .post("/parameters/show_activity_code", { show_activity_code: showActivityCode });
+ .post("/parameters/planning/school_planning_params", {
+ show_activity_code: showActivityCode,
+ recurrence_activated: recurrenceActivated
+ });
};
return
;
diff --git a/frontend/components/planning/CreateActivityModal.jsx b/frontend/components/planning/CreateActivityModal.jsx
index b6ba2c7a..44b89314 100755
--- a/frontend/components/planning/CreateActivityModal.jsx
+++ b/frontend/components/planning/CreateActivityModal.jsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { Fragment } from "react";
import PropTypes from "prop-types";
import _ from "lodash";
@@ -6,6 +6,9 @@ const moment = require("moment");
require("moment/locale/fr");
import { getSeasonFromDate } from "./TimeIntervalHelpers";
+import { RECURRENCE_TYPES, WEEKDAYS } from "../../tools/constants";
+import { toFullDateFr, toLocaleDate } from "../../tools/format";
+import Checkbox from "../common/Checkbox";
class CreateIntervalModal extends React.Component {
constructor(props) {
@@ -16,6 +19,9 @@ class CreateIntervalModal extends React.Component {
this.state = {
kind: this.props.kind || "p",
season: detectedSeason && detectedSeason.id || "",
+ isAdminSelectIntervalRecurrence: false,
+ isRecurrent: false,
+ recurrentType: RECURRENCE_TYPES.getDefault()
};
}
@@ -26,7 +32,7 @@ class CreateIntervalModal extends React.Component {
}
handleSave() {
- const interval = { ...this.props.newInterval, kind: this.state.kind };
+ const interval = { ...this.props.newInterval, kind: this.state.kind, recurrentType: this.state.isRecurrent ? this.state.recurrentType : null };
this.props.onSave(interval, this.state.season);
this.props.closeModal();
}
@@ -36,13 +42,89 @@ class CreateIntervalModal extends React.Component {
}
- render() {
- return (
-
+ render()
+ {
+ let component;
+
+ if (this.props.currentUserIsAdmin && this.props.recurrenceActivated)
+ {
+ if (this.state.isAdminSelectIntervalRecurrence)
+ {
+
+
+ component =
+
Création d'une disponibilité
+
+
+
+
+ La disponibilité sera ajoutée pour le créneau suivant :
+ {toFullDateFr(this.props.newInterval.start)} de
+ {new Date(this.props.newInterval.start).toLocaleTimeString()} à
+ {new Date(this.props.newInterval.end).toLocaleTimeString()}
+
+
+
+
+
+ this.setState({ isRecurrent: e.target.checked })
+ }}
+ />
+
+
+ {this.state.isRecurrent &&
+ this.setState({ recurrentType: e.target.value })}>
+ {RECURRENCE_TYPES.getAll().map((type, i) =>
+ {RECURRENCE_TYPES.toString(type)}
+ )}
+
+
}
+
+
+ }
+ else
+ {
+ const date = new Date(this.props.newInterval.start);
+
+ component =
+
{toFullDateFr(date)}
+
+
+
this.props.handleCloseAndOpenDetails(this.props.newInterval)}>
+
+
+
+
+ Ajout d'un cours
+
+
+
+
this.setState({isAdminSelectIntervalRecurrence: true})}>
+
+
+
+
+ Ajout d'une disponibilité
+
+
+
+ }
+ }
+ else
+ {
+ component =
Création d'un créneau de disponibilité
-
-
-
- Annuler
-
- this.handleSave()}
- >
- Enregistrer
-
-
+
+ }
+
+ return
+ {component}
+
+
+
+ Annuler
+
+ this.handleSave()}
+ >
+ Enregistrer
+
- );
+
}
}
+CreateIntervalModal.propTypes = {
+ newInterval: PropTypes.object.isRequired,
+ seasons: PropTypes.array.isRequired,
+ closeModal: PropTypes.func.isRequired,
+ onSave: PropTypes.func.isRequired,
+ kind: PropTypes.string,
+ handleCloseAndOpenDetails: PropTypes.func,
+ currentUserIsAdmin: PropTypes.bool,
+ recurrenceActivated: PropTypes.bool
+};
+
export default CreateIntervalModal;
diff --git a/frontend/components/planning/Planning.jsx b/frontend/components/planning/Planning.jsx
index aff7ab4e..335e1310 100755
--- a/frontend/components/planning/Planning.jsx
+++ b/frontend/components/planning/Planning.jsx
@@ -320,6 +320,7 @@ class Planning extends React.Component {
: interval.end,
kind: interval.kind || "p",
uid: idGenerator.next().value,
+ recurrentType: interval.recurrentType,
}));
if (this.props.updateTimePreferences) {
@@ -345,7 +346,7 @@ class Planning extends React.Component {
});
} else {
this.commitIntervals(intervals, seasonId).then(results => {
- if (results.intervals.length === 1 && this.props.isTeacher) {
+ if (results.intervals.length === 1 && (this.props.isTeacher || this.props.isAdmin)) {
this.handleOpenDetail(results.intervals[0]);
}
})
@@ -1569,12 +1570,20 @@ class Planning extends React.Component {
contentLabel="Creation d'un créneau"
>
this.handleCreateInterval(interval, seasonId)
}
closeModal={() => this.closeCreationModal()}
seasons={this.props.seasons}
+ currentUserIsAdmin={this.props.isAdmin}
+ handleCloseAndOpenDetails={interval => {
+ this.closeCreationModal();
+ this.handleCreateInterval(interval);
+
+ //this.handleOpenDetail(interval);
+ }}
/>
diff --git a/frontend/tools/constants.js b/frontend/tools/constants.js
index d306e96c..400b2a10 100644
--- a/frontend/tools/constants.js
+++ b/frontend/tools/constants.js
@@ -80,6 +80,27 @@ export const TIME_STEPS = [
{ label: "15min", value: 0.25 },
];
+export const RECURRENCE_TYPES = {
+ DAILY: "daily",
+ WEEKLY: "weekly",
+ BIWEEKLY: "biweekly",
+ MONTHLY: "monthly",
+ BIMONTHLY: "bimonthly",
+ YEARLY: "yearly",
+ toString: function (type) {
+ return {
+ [this.DAILY]: "Tous les jours",
+ [this.WEEKLY]: "Toutes les semaines",
+ [this.BIWEEKLY]: "Toutes les deux semaines",
+ [this.MONTHLY]: "Tous les mois",
+ [this.BIMONTHLY]: "Tous les deux mois",
+ [this.YEARLY]: "Tous les ans",
+ }[type];
+ },
+ getDefault: function () {return this.WEEKLY},
+ getAll: function () {return Object.values(this).filter(v => typeof v === "string")},
+}
+
export const modalStyle = {
overlay: {
overflowY: "auto",
diff --git a/frontend/tools/format.js b/frontend/tools/format.js
index 4c02356e..37c15af5 100644
--- a/frontend/tools/format.js
+++ b/frontend/tools/format.js
@@ -2,6 +2,7 @@ import moment from "moment";
import { retrieveUserLevel } from "./obj";
import React from "react";
import {findAndGet, ISO_DATE_FORMAT} from "../components/utils";
+import { WEEKDAYS } from "./constants";
export const twoDigits = n => (n < 10 ? `0${n}` : `${n}`);
@@ -78,6 +79,12 @@ export const timeToDate = (timestr, refDate = null) => {
return date;
};
+export const toFullDateFr = date => {
+ const tmpDate = new Date(date);
+
+ return `${WEEKDAYS[tmpDate.getDay()]} ${tmpDate.getDate()} ${toMonthName(tmpDate.getMonth())} ${tmpDate.getFullYear()}`;
+}
+
export const fullname = user =>
`${(user.last_name || "").toUpperCase()} ${user.first_name}`;