Ajout de commentaires supplémentaires aux motifs

This commit is contained in:
2025-07-11 14:06:40 +00:00
parent 837089ff5d
commit 63d0a52ea1
392 changed files with 35466 additions and 24054 deletions

View File

@@ -1,76 +1,74 @@
import { EventInput } from "@fullcalendar/core";
import {
DateTime,
Location,
User,
UserAssociatedInterface,
DateTime,
Location,
User,
UserAssociatedInterface,
} from "../../../ChillMainBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface CalendarRange {
id: number;
endDate: DateTime;
startDate: DateTime;
user: User;
location: Location;
createdAt: DateTime;
createdBy: User;
updatedAt: DateTime;
updatedBy: User;
id: number;
endDate: DateTime;
startDate: DateTime;
user: User;
location: Location;
createdAt: DateTime;
createdBy: User;
updatedAt: DateTime;
updatedBy: User;
}
export interface CalendarRangeCreate {
user: UserAssociatedInterface;
startDate: DateTime;
endDate: DateTime;
location: Location;
user: UserAssociatedInterface;
startDate: DateTime;
endDate: DateTime;
location: Location;
}
export interface CalendarRangeEdit {
startDate?: DateTime;
endDate?: DateTime;
location?: Location;
startDate?: DateTime;
endDate?: DateTime;
location?: Location;
}
export interface Calendar {
id: number;
id: number;
}
export interface CalendarLight {
id: number;
endDate: DateTime;
startDate: DateTime;
mainUser: User;
persons: Person[];
status: "valid" | "moved" | "canceled";
id: number;
endDate: DateTime;
startDate: DateTime;
mainUser: User;
persons: Person[];
status: "valid" | "moved" | "canceled";
}
export interface CalendarRemote {
id: number;
endDate: DateTime;
startDate: DateTime;
title: string;
isAllDay: boolean;
id: number;
endDate: DateTime;
startDate: DateTime;
title: string;
isAllDay: boolean;
}
export type EventInputCalendarRange = EventInput & {
id: string;
userId: number;
userLabel: string;
calendarRangeId: number;
locationId: number;
locationName: string;
start: string;
end: string;
is: "range";
id: string;
userId: number;
userLabel: string;
calendarRangeId: number;
locationId: number;
locationName: string;
start: string;
end: string;
is: "range";
};
export function isEventInputCalendarRange(
toBeDetermined: EventInputCalendarRange | EventInput,
toBeDetermined: EventInputCalendarRange | EventInput,
): toBeDetermined is EventInputCalendarRange {
return (
typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
);
return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
}
export {};

View File

@@ -1,164 +1,146 @@
<template>
<teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2>
<div>
<div>
<div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" />
</div>
<pick-entity
:multiple="false"
:types="['user']"
:uniqid="'main_user_calendar'"
:picked="
null !== this.$store.getters.getMainUser
? [this.$store.getters.getMainUser]
: []
"
:removable-if-set="false"
:display-picked="false"
:suggested="this.suggestedUsers"
:label="'main_user'"
@add-new-entity="setMainUser"
/>
</div>
<teleport to="#mainUser">
<h2 class="chill-red">Utilisateur principal</h2>
<div>
<div>
<div v-if="null !== this.$store.getters.getMainUser">
<calendar-active :user="this.$store.getters.getMainUser" />
</div>
</teleport>
<pick-entity
:multiple="false"
:types="['user']"
:uniqid="'main_user_calendar'"
:picked="
null !== this.$store.getters.getMainUser
? [this.$store.getters.getMainUser]
: []
"
:removable-if-set="false"
:display-picked="false"
:suggested="this.suggestedUsers"
:label="'main_user'"
@add-new-entity="setMainUser"
/>
</div>
</div>
</teleport>
<concerned-groups />
<concerned-groups />
<teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8">
{{ $d(activity.startDate, "long") }} -
{{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span
>
<span v-else>(Une plage de disponibilité sélectionnée)</span>
</div>
</div>
</teleport>
<location />
<teleport to="#fullCalendar">
<div class="calendar-actives">
<template v-for="u in getActiveUsers" :key="u.id">
<calendar-active
:user="u"
:invite="this.$store.getters.getInviteForUser(u)"
/>
</template>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
<teleport to="#schedule">
<div class="row mb-3" v-if="activity.startDate !== null">
<label class="col-form-label col-sm-4">Date</label>
<div class="col-sm-8">
{{ $d(activity.startDate, "long") }} -
{{ $d(activity.endDate, "hoursOnly") }}
<span v-if="activity.calendarRange === null"
>(Pas de plage de disponibilité sélectionnée)</span
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<div class="col-sm-3 col-xs-12">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="hideWeekends"
/>
</span>
<label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
<span v-else>(Une plage de disponibilité sélectionnée)</span>
</div>
</div>
</teleport>
<location />
<teleport to="#fullCalendar">
<div class="calendar-actives">
<template v-for="u in getActiveUsers" :key="u.id">
<calendar-active
:user="u"
:invite="this.$store.getters.getInviteForUser(u)"
/>
</template>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select v-model="slotDuration" id="slotDuration" class="form-select">
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
<FullCalendar ref="fullCalendar" :options="calendarOptions">
<template #eventContent="arg">
<span>
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }}
{{ arg.event.extendedProps.locationName }}
<small>{{
arg.event.extendedProps.userLabel
}}</small></b
>
<b v-else-if="arg.event.extendedProps.is === 'current'"
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
</span>
</template>
</FullCalendar>
</teleport>
</div>
<div class="col-sm-3 col-xs-12">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="hideWeekends"
/>
</span>
<label for="showHideWE" class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<FullCalendar ref="fullCalendar" :options="calendarOptions">
<template #eventContent="arg">
<span>
<b v-if="arg.event.extendedProps.is === 'remote'">{{
arg.event.title
}}</b>
<b v-else-if="arg.event.extendedProps.is === 'range'"
>{{ arg.timeText }}
{{ arg.event.extendedProps.locationName }}
<small>{{ arg.event.extendedProps.userLabel }}</small></b
>
<b v-else-if="arg.event.extendedProps.is === 'current'"
>{{ arg.timeText }} {{ $t("current_selected") }}
</b>
<b v-else-if="arg.event.extendedProps.is === 'local'">{{
arg.event.title
}}</b>
<b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
</span>
</template>
</FullCalendar>
</teleport>
</template>
<script>
@@ -175,219 +157,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
import { mapGetters, mapState } from "vuex";
export default {
name: "App",
components: {
ConcernedGroups,
Location,
FullCalendar,
CalendarActive,
PickEntity,
name: "App",
components: {
ConcernedGroups,
Location,
FullCalendar,
CalendarActive,
PickEntity,
},
data() {
return {
errorMsg: [],
showMyCalendar: false,
slotDuration: "00:05:00",
slotMinTime: "09:00:00",
slotMaxTime: "18:00:00",
hideWeekEnds: true,
previousUser: [],
};
},
computed: {
...mapGetters(["getMainUser"]),
...mapState(["activity"]),
events() {
return this.$store.getters.getEventSources;
},
data() {
return {
errorMsg: [],
showMyCalendar: false,
slotDuration: "00:05:00",
slotMinTime: "09:00:00",
slotMaxTime: "18:00:00",
hideWeekEnds: true,
previousUser: [],
};
calendarOptions() {
return {
locale: frLocale,
plugins: [
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
dayGridPlugin,
listPlugin,
],
initialView: "timeGridWeek",
initialDate: this.$store.getters.getInitialDate,
eventSources: this.events,
selectable: true,
slotMinTime: this.slotMinTime,
slotMaxTime: this.slotMaxTime,
scrollTimeReset: false,
datesSet: this.onDatesSet,
select: this.onDateSelect,
eventChange: this.onEventChange,
eventClick: this.onEventClick,
selectMirror: true,
editable: true,
weekends: !this.hideWeekEnds,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay,listWeek",
},
views: {
timeGrid: {
slotEventOverlap: false,
slotDuration: this.slotDuration,
},
},
};
},
computed: {
...mapGetters(["getMainUser"]),
...mapState(["activity"]),
events() {
return this.$store.getters.getEventSources;
},
calendarOptions() {
return {
locale: frLocale,
plugins: [
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
dayGridPlugin,
listPlugin,
],
initialView: "timeGridWeek",
initialDate: this.$store.getters.getInitialDate,
eventSources: this.events,
selectable: true,
slotMinTime: this.slotMinTime,
slotMaxTime: this.slotMaxTime,
scrollTimeReset: false,
datesSet: this.onDatesSet,
select: this.onDateSelect,
eventChange: this.onEventChange,
eventClick: this.onEventClick,
selectMirror: true,
editable: true,
weekends: !this.hideWeekEnds,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay,listWeek",
},
views: {
timeGrid: {
slotEventOverlap: false,
slotDuration: this.slotDuration,
},
},
};
},
getActiveUsers() {
const users = [];
for (const id of this.$store.state.currentView.users.keys()) {
users.push(this.$store.getters.getUserDataById(id).user);
}
return users;
},
suggestedUsers() {
const suggested = [];
this.$data.previousUser.forEach((u) => {
if (u.id !== this.$store.getters.getMainUser.id) {
suggested.push(u);
}
});
return suggested;
},
getActiveUsers() {
const users = [];
for (const id of this.$store.state.currentView.users.keys()) {
users.push(this.$store.getters.getUserDataById(id).user);
}
return users;
},
methods: {
setMainUser({ entity }) {
const user = entity;
console.log("setMainUser APP", entity);
suggestedUsers() {
const suggested = [];
if (
user.id !== this.$store.getters.getMainUser &&
(this.$store.state.activity.calendarRange !== null ||
this.$store.state.activity.startDate !== null ||
this.$store.state.activity.endDate !== null)
) {
if (
!window.confirm(
this.$t("change_main_user_will_reset_event_data"),
)
) {
return;
}
}
this.$data.previousUser.forEach((u) => {
if (u.id !== this.$store.getters.getMainUser.id) {
suggested.push(u);
}
});
// add the previous user, if any, in the previous user list (in use for suggestion)
if (null !== this.$store.getters.getMainUser) {
const suggestedUids = new Set(
this.$data.previousUser.map((u) => u.id),
);
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
this.$data.previousUser.push(
this.$store.getters.getMainUser,
);
}
}
this.$store.dispatch("setMainUser", user);
this.$store.commit("showUserOnCalendar", {
user,
ranges: true,
remotes: true,
});
},
removeMainUser(user) {
console.log("removeMainUser APP", user);
window.alert(this.$t("main_user_is_mandatory"));
return;
},
onDatesSet(event) {
console.log("onDatesSet", event);
this.$store.dispatch("setCurrentDatesView", {
start: event.start,
end: event.end,
});
},
onDateSelect(payload) {
console.log("onDateSelect", payload);
// show an alert if changing mainUser
if (
(this.$store.getters.getMainUser !== null &&
this.$store.state.me.id !==
this.$store.getters.getMainUser.id) ||
this.$store.getters.getMainUser === null
) {
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
return;
} else {
this.$store.commit("showUserOnCalendar", {
user: this.$store.state.me,
remotes: true,
ranges: true,
});
}
}
this.$store.dispatch("setEventTimes", {
start: payload.start,
end: payload.end,
});
},
onEventChange(payload) {
console.log("onEventChange", payload);
if (this.$store.state.activity.calendarRange !== null) {
throw new Error(
"not allowed to edit a calendar associated with a calendar range",
);
}
this.$store.dispatch("setEventTimes", {
start: payload.event.start,
end: payload.event.end,
});
},
onEventClick(payload) {
if (payload.event.extendedProps.is !== "range") {
// do nothing when clicking on remote
return;
}
// show an alert if changing mainUser
if (
this.$store.getters.getMainUser !== null &&
payload.event.extendedProps.userId !==
this.$store.getters.getMainUser.id
) {
if (
!window.confirm(
this.$t("this_calendar_range_will_change_main_user"),
)
) {
return;
}
}
this.$store.dispatch("associateCalendarToRange", {
range: payload.event,
});
},
return suggested;
},
},
methods: {
setMainUser({ entity }) {
const user = entity;
console.log("setMainUser APP", entity);
if (
user.id !== this.$store.getters.getMainUser &&
(this.$store.state.activity.calendarRange !== null ||
this.$store.state.activity.startDate !== null ||
this.$store.state.activity.endDate !== null)
) {
if (
!window.confirm(this.$t("change_main_user_will_reset_event_data"))
) {
return;
}
}
// add the previous user, if any, in the previous user list (in use for suggestion)
if (null !== this.$store.getters.getMainUser) {
const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
this.$data.previousUser.push(this.$store.getters.getMainUser);
}
}
this.$store.dispatch("setMainUser", user);
this.$store.commit("showUserOnCalendar", {
user,
ranges: true,
remotes: true,
});
},
removeMainUser(user) {
console.log("removeMainUser APP", user);
window.alert(this.$t("main_user_is_mandatory"));
return;
},
onDatesSet(event) {
console.log("onDatesSet", event);
this.$store.dispatch("setCurrentDatesView", {
start: event.start,
end: event.end,
});
},
onDateSelect(payload) {
console.log("onDateSelect", payload);
// show an alert if changing mainUser
if (
(this.$store.getters.getMainUser !== null &&
this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
this.$store.getters.getMainUser === null
) {
if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
return;
} else {
this.$store.commit("showUserOnCalendar", {
user: this.$store.state.me,
remotes: true,
ranges: true,
});
}
}
this.$store.dispatch("setEventTimes", {
start: payload.start,
end: payload.end,
});
},
onEventChange(payload) {
console.log("onEventChange", payload);
if (this.$store.state.activity.calendarRange !== null) {
throw new Error(
"not allowed to edit a calendar associated with a calendar range",
);
}
this.$store.dispatch("setEventTimes", {
start: payload.event.start,
end: payload.event.end,
});
},
onEventClick(payload) {
if (payload.event.extendedProps.is !== "range") {
// do nothing when clicking on remote
return;
}
// show an alert if changing mainUser
if (
this.$store.getters.getMainUser !== null &&
payload.event.extendedProps.userId !==
this.$store.getters.getMainUser.id
) {
if (
!window.confirm(this.$t("this_calendar_range_will_change_main_user"))
) {
return;
}
}
this.$store.dispatch("associateCalendarToRange", {
range: payload.event,
});
},
},
};
</script>
<style>
.calendar-actives {
display: flex;
flex-direction: row;
flex-wrap: wrap;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.display-options {
margin-top: 1rem;
margin-top: 1rem;
}
/* for events which are range */
.fc-event.isrange {
border-width: 3px;
border-width: 3px;
}
</style>

View File

@@ -1,119 +1,105 @@
<template>
<div :style="style" class="calendar-active">
<span class="badge-user">
{{ user.text }}
<template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i
v-else-if="invite.status === 'declined'"
class="fa fa-times"
/>
<i
v-else-if="invite.status === 'pending'"
class="fa fa-question-o"
/>
<i
v-else-if="invite.status === 'tentative'"
class="fa fa-question"
/>
<span v-else="">{{ invite.status }}</span>
</template>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="rangeShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Disponibilités"
><i class="fa fa-calendar-check-o"
/></label>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="remoteShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Agenda"
><i class="fa fa-calendar"
/></label>
</span>
</div>
<div :style="style" class="calendar-active">
<span class="badge-user">
{{ user.text }}
<template v-if="invite !== null">
<i v-if="invite.status === 'accepted'" class="fa fa-check" />
<i v-else-if="invite.status === 'declined'" class="fa fa-times" />
<i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
<i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
<span v-else="">{{ invite.status }}</span>
</template>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="rangeShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Disponibilités"
><i class="fa fa-calendar-check-o"
/></label>
</span>
<span class="form-check-inline form-switch">
<input
class="form-check-input"
type="checkbox"
id="flexSwitchCheckDefault"
v-model="remoteShow"
/>
&nbsp;<label
class="form-check-label"
for="flexSwitchCheckDefault"
title="Agenda"
><i class="fa fa-calendar"
/></label>
</span>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "CalendarActive",
props: {
user: {
type: Object,
required: true,
},
invite: {
type: Object,
required: false,
default: null,
},
name: "CalendarActive",
props: {
user: {
type: Object,
required: true,
},
computed: {
style() {
return {
backgroundColor: this.$store.getters.getUserData(this.user)
.mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(
this.user,
);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(
this.user,
);
},
},
invite: {
type: Object,
required: false,
default: null,
},
},
computed: {
style() {
return {
backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
};
},
rangeShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
ranges: value,
});
},
get() {
return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
},
},
remoteShow: {
set(value) {
this.$store.commit("showUserOnCalendar", {
user: this.user,
remotes: value,
});
},
get() {
return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
},
},
},
};
</script>
<style scoped lang="scss">
.calendar-active {
margin: 0 0.25rem 0.25rem 0;
padding: 0.5rem;
margin: 0 0.25rem 0.25rem 0;
padding: 0.5rem;
border-radius: 0.5rem;
border-radius: 0.5rem;
color: var(--bs-blue);
color: var(--bs-blue);
& > .badge-user {
margin-right: 0.5rem;
}
& > .badge-user {
margin-right: 0.5rem;
}
}
</style>

View File

@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
* @return Promise
*/
export const fetchCalendarRangeForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarRange[]> => {
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
};
export const fetchCalendarRemoteForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarRemote[]> => {
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
};
export const fetchCalendarLocalForUser = (
user: User,
start: Date,
end: Date,
user: User,
start: Date,
end: Date,
): Promise<CalendarLight[]> => {
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
const dateFrom = datetimeToISO(start);
const dateTo = datetimeToISO(end);
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
};

View File

@@ -1,17 +1,17 @@
const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
];
export { COLORS };

View File

@@ -1,117 +1,117 @@
import { COLORS } from "../const";
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import {
DateTime,
User,
DateTime,
User,
} from "../../../../../../ChillMainBundle/Resources/public/types";
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
import type { EventInputCalendarRange } from "../../../types";
import { EventInput } from "@fullcalendar/core";
export interface UserData {
user: User;
calendarRanges: CalendarRange[];
calendarRangesLoaded: {}[];
remotes: CalendarRemote[];
remotesLoaded: {}[];
locals: CalendarRemote[];
localsLoaded: {}[];
mainColor: string;
user: User;
calendarRanges: CalendarRange[];
calendarRangesLoaded: {}[];
remotes: CalendarRemote[];
remotesLoaded: {}[];
locals: CalendarRemote[];
localsLoaded: {}[];
mainColor: string;
}
export const addIdToValue = (string: string, id: number): string => {
const array = string ? string.split(",") : [];
array.push(id.toString());
const str = array.join();
return str;
const array = string ? string.split(",") : [];
array.push(id.toString());
const str = array.join();
return str;
};
export const removeIdFromValue = (string: string, id: number) => {
let array = string.split(",");
array = array.filter((el) => el !== id.toString());
const str = array.join();
return str;
let array = string.split(",");
array = array.filter((el) => el !== id.toString());
const str = array.join();
return str;
};
/*
* Assign missing keys for the ConcernedGroups component
*/
export const mapEntity = (entity: EventInput): EventInput => {
const calendar = { ...entity };
Object.assign(calendar, { thirdParties: entity.professionals });
const calendar = { ...entity };
Object.assign(calendar, { thirdParties: entity.professionals });
if (entity.startDate !== null) {
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
}
if (entity.endDate !== null) {
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
}
if (entity.startDate !== null) {
calendar.startDate = ISOToDatetime(entity.startDate.datetime);
}
if (entity.endDate !== null) {
calendar.endDate = ISOToDatetime(entity.endDate.datetime);
}
if (entity.calendarRange !== null) {
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
}
if (entity.calendarRange !== null) {
calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
}
return calendar;
return calendar;
};
export const createUserData = (user: User, colorIndex: number): UserData => {
const colorId = colorIndex % COLORS.length;
const colorId = colorIndex % COLORS.length;
return {
user: user,
calendarRanges: [],
calendarRangesLoaded: [],
remotes: [],
remotesLoaded: [],
locals: [],
localsLoaded: [],
mainColor: COLORS[colorId],
};
return {
user: user,
calendarRanges: [],
calendarRangesLoaded: [],
remotes: [],
remotesLoaded: [],
locals: [],
localsLoaded: [],
mainColor: COLORS[colorId],
};
};
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
export const calendarRangeToFullCalendarEvent = (
entity: CalendarRange,
entity: CalendarRange,
): EventInputCalendarRange => {
return {
id: `range_${entity.id}`,
title: "(" + entity.user.text + ")",
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
userId: entity.user.id,
userLabel: entity.user.label,
calendarRangeId: entity.id,
locationId: entity.location.id,
locationName: entity.location.name,
is: "range",
};
return {
id: `range_${entity.id}`,
title: "(" + entity.user.text + ")",
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
userId: entity.user.id,
userLabel: entity.user.label,
calendarRangeId: entity.id,
locationId: entity.location.id,
locationName: entity.location.name,
is: "range",
};
};
export const remoteToFullCalendarEvent = (
entity: CalendarRemote,
entity: CalendarRemote,
): EventInput & { id: string } => {
return {
id: `range_${entity.id}`,
title: entity.title,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: entity.isAllDay,
is: "remote",
};
return {
id: `range_${entity.id}`,
title: entity.title,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: entity.isAllDay,
is: "remote",
};
};
export const localsToFullCalendarEvent = (
entity: CalendarLight,
entity: CalendarLight,
): EventInput & { id: string; originId: number } => {
return {
id: `local_${entity.id}`,
title: entity.persons.map((p) => p.text).join(", "),
originId: entity.id,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
is: "local",
};
return {
id: `local_${entity.id}`,
title: entity.persons.map((p) => p.text).join(", "),
originId: entity.id,
start: entity.startDate.datetime8601,
end: entity.endDate.datetime8601,
allDay: false,
is: "local",
};
};

View File

@@ -1,58 +1,50 @@
<template>
<div class="btn-group" role="group">
<button
id="btnGroupDrop1"
type="button"
class="btn btn-misc dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
<div class="btn-group" role="group">
<button
id="btnGroupDrop1"
type="button"
class="btn btn-misc dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<template v-if="status === Statuses.PENDING">
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
</template>
<template v-else-if="status === Statuses.ACCEPTED">
<span class="fa fa-check"></span> {{ $t("Accepted") }}
</template>
<template v-else-if="status === Statuses.DECLINED">
<span class="fa fa-times"></span> {{ $t("Declined") }}
</template>
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
<span class="fa fa-question"></span> {{ $t("Tentative") }}
</template>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED">
<a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
>
<template v-if="status === Statuses.PENDING">
<span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
</template>
<template v-else-if="status === Statuses.ACCEPTED">
<span class="fa fa-check"></span> {{ $t("Accepted") }}
</template>
<template v-else-if="status === Statuses.DECLINED">
<span class="fa fa-times"></span> {{ $t("Declined") }}
</template>
<template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
<span class="fa fa-question"></span> {{ $t("Tentative") }}
</template>
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
<li v-if="status !== Statuses.ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.ACCEPTED)"
><i class="fa fa-check" aria-hidden="true"></i>
{{ $t("Accept") }}</a
>
</li>
<li v-if="status !== Statuses.DECLINED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i>
{{ $t("Decline") }}</a
>
</li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i>
{{ $t("Tentatively_accept") }}</a
>
</li>
<li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i>
{{ $t("Set_pending") }}</a
>
</li>
</ul>
</div>
</li>
<li v-if="status !== Statuses.DECLINED">
<a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
>
</li>
<li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
<a
class="dropdown-item"
@click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
>
</li>
<li v-if="status !== Statuses.PENDING">
<a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
>
</li>
</ul>
</div>
</template>
<script lang="ts">
@@ -64,69 +56,67 @@ const PENDING = "pending";
const TENTATIVELY_ACCEPTED = "tentative";
const i18n = {
messages: {
fr: {
Give_an_answer: "Répondre",
Accepted: "Accepté",
Declined: "Refusé",
Tentative: "Accepté provisoirement",
Accept: "Accepter",
Decline: "Refuser",
Tentatively_accept: "Accepter provisoirement",
Set_pending: "Ne pas répondre",
},
messages: {
fr: {
Give_an_answer: "Répondre",
Accepted: "Accepté",
Declined: "Refusé",
Tentative: "Accepté provisoirement",
Accept: "Accepter",
Decline: "Refuser",
Tentatively_accept: "Accepter provisoirement",
Set_pending: "Ne pas répondre",
},
},
};
export default defineComponent({
name: "Answer",
i18n,
props: {
calendarId: { type: Number, required: true },
status: {
type: String as PropType<
"accepted" | "declined" | "pending" | "tentative"
>,
required: true,
},
name: "Answer",
i18n,
props: {
calendarId: { type: Number, required: true },
status: {
type: String as PropType<
"accepted" | "declined" | "pending" | "tentative"
>,
required: true,
},
emits: {
statusChanged(
payload: "accepted" | "declined" | "pending" | "tentative",
) {
return true;
},
},
emits: {
statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
return true;
},
data() {
return {
Statuses: {
ACCEPTED,
DECLINED,
PENDING,
TENTATIVELY_ACCEPTED,
},
};
},
methods: {
changeStatus: function (
newStatus: "accepted" | "declined" | "pending" | "tentative",
) {
console.log("changeStatus", newStatus);
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
window
.fetch(url, {
method: "POST",
})
.then((r: Response) => {
if (!r.ok) {
console.error("could not confirm answer", newStatus);
return;
}
console.log("answer sent", newStatus);
this.$emit("statusChanged", newStatus);
});
},
},
data() {
return {
Statuses: {
ACCEPTED,
DECLINED,
PENDING,
TENTATIVELY_ACCEPTED,
},
};
},
methods: {
changeStatus: function (
newStatus: "accepted" | "declined" | "pending" | "tentative",
) {
console.log("changeStatus", newStatus);
const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
window
.fetch(url, {
method: "POST",
})
.then((r: Response) => {
if (!r.ok) {
console.error("could not confirm answer", newStatus);
return;
}
console.log("answer sent", newStatus);
this.$emit("statusChanged", newStatus);
});
},
},
});
</script>

View File

@@ -1,225 +1,177 @@
<template>
<div class="row">
<div class="col-sm">
<label class="form-label">{{ $t("created_availabilities") }}</label>
<vue-multiselect
v-model="pickedLocation"
:options="locations"
:label="'name'"
:track-by="'id'"
:selectLabel="'Presser \'Entrée\' pour choisir'"
:selectedLabel="'Choisir'"
:deselectLabel="'Presser \'Entrée\' pour enlever'"
:placeholder="'Choisir'"
></vue-multiselect>
</div>
<div class="row">
<div class="col-sm">
<label class="form-label">{{ $t("created_availabilities") }}</label>
<vue-multiselect
v-model="pickedLocation"
:options="locations"
:label="'name'"
:track-by="'id'"
:selectLabel="'Presser \'Entrée\' pour choisir'"
:selectedLabel="'Choisir'"
:deselectLabel="'Presser \'Entrée\' pour enlever'"
:placeholder="'Choisir'"
></vue-multiselect>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select
v-model="slotDuration"
id="slotDuration"
class="form-select"
>
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select
v-model="slotMinTime"
id="slotMinTime"
class="form-select"
>
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select
v-model="slotMaxTime"
id="slotMaxTime"
class="form-select"
>
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<div class="col-xs-12 col-sm-3">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="showWeekends"
/>
</span>
<label
for="showHideWE"
class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<div
class="display-options row justify-content-between"
style="margin-top: 1rem"
>
<div class="col-sm-9 col-xs-12">
<div class="input-group mb-3">
<label class="input-group-text" for="slotDuration"
>Durée des créneaux</label
>
<select v-model="slotDuration" id="slotDuration" class="form-select">
<option value="00:05:00">5 minutes</option>
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select v-model="slotMinTime" id="slotMinTime" class="form-select">
<option value="00:00:00">0h</option>
<option value="01:00:00">1h</option>
<option value="02:00:00">2h</option>
<option value="03:00:00">3h</option>
<option value="04:00:00">4h</option>
<option value="05:00:00">5h</option>
<option value="06:00:00">6h</option>
<option value="07:00:00">7h</option>
<option value="08:00:00">8h</option>
<option value="09:00:00">9h</option>
<option value="10:00:00">10h</option>
<option value="11:00:00">11h</option>
<option value="12:00:00">12h</option>
</select>
<label class="input-group-text" for="slotMaxTime">À</label>
<select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
<option value="12:00:00">12h</option>
<option value="13:00:00">13h</option>
<option value="14:00:00">14h</option>
<option value="15:00:00">15h</option>
<option value="16:00:00">16h</option>
<option value="17:00:00">17h</option>
<option value="18:00:00">18h</option>
<option value="19:00:00">19h</option>
<option value="20:00:00">20h</option>
<option value="21:00:00">21h</option>
<option value="22:00:00">22h</option>
<option value="23:00:00">23h</option>
<option value="23:59:59">24h</option>
</select>
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }: { event: EventApi }">
<span :class="eventClasses">
<b v-if="event.extendedProps.is === 'remote'">{{
event.title
}}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
{{ event.extendedProps.locationName }}</b
>
<b v-else-if="event.extendedProps.is === 'local'">{{
event.title
}}</b>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
>
</a>
</span>
<div class="col-xs-12 col-sm-3">
<div class="float-end">
<div class="form-check input-group">
<span class="input-group-text">
<input
id="showHideWE"
class="mt-0"
type="checkbox"
v-model="showWeekends"
/>
</span>
<label for="showHideWE" class="form-check-label input-group-text"
>Week-ends</label
>
</div>
</div>
</div>
</div>
<FullCalendar :options="calendarOptions" ref="calendarRef">
<template v-slot:eventContent="{ event }: { event: EventApi }">
<span :class="eventClasses">
<b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
{{ event.extendedProps.locationName }}</b
>
<b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
<b v-else>no 'is'</b>
<a
v-if="event.extendedProps.is === 'range'"
class="fa fa-fw fa-times delete"
@click.prevent="onClickDelete(event)"
>
</a>
</span>
</template>
</FullCalendar>
<div id="copy-widget">
<div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div>
<div class="col-xs-12 col-sm-9 col-md-2">
<select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
<option value="day">{{ $t("from_day_to_day") }}</option>
<option value="week">
{{ $t("from_week_to_week") }}
</option>
</select>
</div>
<template v-if="dayOrWeek === 'day'">
<div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyFrom" />
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<input class="form-control" type="date" v-model="copyTo" />
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyDay">
{{ $t("copy_range") }}
</button>
</div>
</template>
</FullCalendar>
<div id="copy-widget">
<div class="container mt-2 mb-2">
<div class="row justify-content-between align-items-center mb-4">
<div class="col-xs-12 col-sm-3 col-md-2">
<h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
</div>
<div class="col-xs-12 col-sm-9 col-md-2">
<select
v-model="dayOrWeek"
id="dayOrWeek"
class="form-select"
>
<option value="day">{{ $t("from_day_to_day") }}</option>
<option value="week">
{{ $t("from_week_to_week") }}
</option>
</select>
</div>
<template v-if="dayOrWeek === 'day'">
<div class="col-xs-12 col-sm-3 col-md-3">
<input
class="form-control"
type="date"
v-model="copyFrom"
/>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<input
class="form-control"
type="date"
v-model="copyTo"
/>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button
class="btn btn-action float-end"
@click="copyDay"
>
{{ $t("copy_range") }}
</button>
</div>
</template>
<template v-else>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyFromWeek"
id="copyFromWeek"
class="form-select"
>
<option
v-for="w in lastWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyToWeek"
id="copyToWeek"
class="form-select"
>
<option
v-for="w in nextWeeks"
:value="w.value"
:key="w.value"
>
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button
class="btn btn-action float-end"
@click="copyWeek"
>
{{ $t("copy_range") }}
</button>
</div>
</template>
</div>
</div>
<template v-else>
<div class="col-xs-12 col-sm-3 col-md-3">
<select
v-model="copyFromWeek"
id="copyFromWeek"
class="form-select"
>
<option v-for="w in lastWeeks" :value="w.value" :key="w.value">
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
<i class="fa fa-angle-double-right"></i>
</div>
<div class="col-xs-12 col-sm-3 col-md-3">
<select v-model="copyToWeek" id="copyToWeek" class="form-select">
<option v-for="w in nextWeeks" :value="w.value" :key="w.value">
{{ w.text }}
</option>
</select>
</div>
<div class="col-xs-12 col-sm-5 col-md-1">
<button class="btn btn-action float-end" @click="copyWeek">
{{ $t("copy_range") }}
</button>
</div>
</template>
</div>
</div>
</div>
<!-- not directly seen, but include in a modal -->
<edit-location ref="editLocation"></edit-location>
<!-- not directly seen, but include in a modal -->
<edit-location ref="editLocation"></edit-location>
</template>
<script setup lang="ts">
import type {
CalendarOptions,
DatesSetArg,
EventInput,
CalendarOptions,
DatesSetArg,
EventInput,
} from "@fullcalendar/core";
import { computed, ref, onMounted } from "vue";
import { useStore } from "vuex";
@@ -227,14 +179,14 @@ import { key } from "./store";
import FullCalendar from "@fullcalendar/vue3";
import frLocale from "@fullcalendar/core/locales/fr";
import interactionPlugin, {
EventResizeDoneArg,
EventResizeDoneArg,
} from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import {
EventApi,
DateSelectArg,
EventDropArg,
EventClickArg,
EventApi,
DateSelectArg,
EventDropArg,
EventClickArg,
} from "@fullcalendar/core";
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
import VueMultiselect from "vue-multiselect";
@@ -255,96 +207,96 @@ const copyFromWeek = ref<string | null>(null);
const copyToWeek = ref<string | null>(null);
interface Weeks {
value: string | null;
text: string;
value: string | null;
text: string;
}
const getMonday = (week: number): Date => {
const lastMonday = new Date();
lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
);
return lastMonday;
const lastMonday = new Date();
lastMonday.setDate(
lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
);
return lastMonday;
};
const dateOptions: Intl.DateTimeFormatOptions = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
};
const lastWeeks = computed((): Weeks[] =>
Array.from(Array(30).keys()).map((w) => {
const lastMonday = getMonday(15 - w);
return {
value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
Array.from(Array(30).keys()).map((w) => {
const lastMonday = getMonday(15 - w);
return {
value: dateToISO(lastMonday),
text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
);
const nextWeeks = computed((): Weeks[] =>
Array.from(Array(52).keys()).map((w) => {
const nextMonday = getMonday(w + 1);
return {
value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
Array.from(Array(52).keys()).map((w) => {
const nextMonday = getMonday(w + 1);
return {
value: dateToISO(nextMonday),
text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
};
}),
);
const formatDate = (datetime: string) => {
console.log(typeof datetime);
return ISOToDate(datetime);
console.log(typeof datetime);
return ISOToDate(datetime);
};
const baseOptions = ref<CalendarOptions>({
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
initialView: "timeGridWeek",
initialDate: new Date(),
scrollTimeReset: false,
selectable: true,
// when the dates are changes in the fullcalendar view OR when new events are added
datesSet: onDatesSet,
// when a date is selected
select: onDateSelect,
// when a event is resized
eventResize: onEventDropOrResize,
// when an event is moved
eventDrop: onEventDropOrResize,
// when an event si clicked
eventClick: onEventClick,
selectMirror: false,
editable: true,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay",
},
locale: frLocale,
plugins: [interactionPlugin, timeGridPlugin],
initialView: "timeGridWeek",
initialDate: new Date(),
scrollTimeReset: false,
selectable: true,
// when the dates are changes in the fullcalendar view OR when new events are added
datesSet: onDatesSet,
// when a date is selected
select: onDateSelect,
// when a event is resized
eventResize: onEventDropOrResize,
// when an event is moved
eventDrop: onEventDropOrResize,
// when an event si clicked
eventClick: onEventClick,
selectMirror: false,
editable: true,
headerToolbar: {
left: "prev,next today",
center: "title",
right: "timeGridWeek,timeGridDay",
},
});
const ranges = computed<EventInput[]>(() => {
return store.state.calendarRanges.ranges;
return store.state.calendarRanges.ranges;
});
const locations = computed<Location[]>(() => {
return store.state.locations.locations;
return store.state.locations.locations;
});
const pickedLocation = computed<Location | null>({
get(): Location | null {
return (
store.state.locations.locationPicked ||
store.state.locations.currentLocation
);
},
set(newLocation: Location | null): void {
store.commit("locations/setLocationPicked", newLocation, {
root: true,
});
},
get(): Location | null {
return (
store.state.locations.locationPicked ||
store.state.locations.currentLocation
);
},
set(newLocation: Location | null): void {
store.commit("locations/setLocationPicked", newLocation, {
root: true,
});
},
});
/**
@@ -373,116 +325,116 @@ const sources = computed<EventSourceInput[]>(() => {
*/
const calendarOptions = computed((): CalendarOptions => {
return {
...baseOptions.value,
weekends: showWeekends.value,
slotDuration: slotDuration.value,
events: ranges.value,
slotMinTime: slotMinTime.value,
slotMaxTime: slotMaxTime.value,
};
return {
...baseOptions.value,
weekends: showWeekends.value,
slotDuration: slotDuration.value,
events: ranges.value,
slotMinTime: slotMinTime.value,
slotMaxTime: slotMaxTime.value,
};
});
/**
* launched when the calendar range date change
*/
function onDatesSet(event: DatesSetArg): void {
store.dispatch("fullCalendar/setCurrentDatesView", {
start: event.start,
end: event.end,
});
store.dispatch("fullCalendar/setCurrentDatesView", {
start: event.start,
end: event.end,
});
}
function onDateSelect(event: DateSelectArg): void {
if (null === pickedLocation.value) {
window.alert(
"Indiquez une localisation avant de créer une période de disponibilité.",
);
return;
}
if (null === pickedLocation.value) {
window.alert(
"Indiquez une localisation avant de créer une période de disponibilité.",
);
return;
}
store.dispatch("calendarRanges/createRange", {
start: event.start,
end: event.end,
location: pickedLocation.value,
});
store.dispatch("calendarRanges/createRange", {
start: event.start,
end: event.end,
location: pickedLocation.value,
});
}
/**
* When a calendar range is deleted
*/
function onClickDelete(event: EventApi): void {
if (event.extendedProps.is !== "range") {
return;
}
if (event.extendedProps.is !== "range") {
return;
}
store.dispatch(
"calendarRanges/deleteRange",
event.extendedProps.calendarRangeId,
);
store.dispatch(
"calendarRanges/deleteRange",
event.extendedProps.calendarRangeId,
);
}
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
if (payload.event.extendedProps.is !== "range") {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,
start: payload.event.start,
end: payload.event.end,
});
store.dispatch("calendarRanges/patchRangeTime", {
calendarRangeId: payload.event.extendedProps.calendarRangeId,
start: payload.event.start,
end: payload.event.end,
});
}
function onEventClick(payload: EventClickArg): void {
// @ts-ignore TS does not recognize the target. But it does exists.
if (payload.jsEvent.target.classList.contains("delete")) {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
// @ts-ignore TS does not recognize the target. But it does exists.
if (payload.jsEvent.target.classList.contains("delete")) {
return;
}
if (payload.event.extendedProps.is !== "range") {
return;
}
editLocation.value?.startEdit(payload.event);
editLocation.value?.startEdit(payload.event);
}
function copyDay() {
if (null === copyFrom.value || null === copyTo.value) {
return;
}
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
from: ISOToDate(copyFrom.value),
to: ISOToDate(copyTo.value),
});
if (null === copyFrom.value || null === copyTo.value) {
return;
}
store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
from: ISOToDate(copyFrom.value),
to: ISOToDate(copyTo.value),
});
}
function copyWeek() {
if (null === copyFromWeek.value || null === copyToWeek.value) {
return;
}
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
fromMonday: ISOToDate(copyFromWeek.value),
toMonday: ISOToDate(copyToWeek.value),
});
if (null === copyFromWeek.value || null === copyToWeek.value) {
return;
}
store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
fromMonday: ISOToDate(copyFromWeek.value),
toMonday: ISOToDate(copyToWeek.value),
});
}
onMounted(() => {
copyFromWeek.value = dateToISO(getMonday(0));
copyToWeek.value = dateToISO(getMonday(1));
copyFromWeek.value = dateToISO(getMonday(0));
copyToWeek.value = dateToISO(getMonday(1));
});
</script>
<style scoped>
#copy-widget {
position: sticky;
bottom: 0px;
background-color: white;
z-index: 9999999999;
padding: 0.25rem 0 0.25rem;
position: sticky;
bottom: 0px;
background-color: white;
z-index: 9999999999;
padding: 0.25rem 0 0.25rem;
}
div.copy-chevron {
text-align: center;
font-size: x-large;
width: 2rem;
text-align: center;
font-size: x-large;
width: 2rem;
}
</style>

View File

@@ -1,28 +1,28 @@
<template>
<component :is="Teleport" to="body">
<modal v-if="showModal" @close="closeModal">
<template v-slot:header>
<h3>{{ "Modifier le lieu" }}</h3>
</template>
<component :is="Teleport" to="body">
<modal v-if="showModal" @close="closeModal">
<template v-slot:header>
<h3>{{ "Modifier le lieu" }}</h3>
</template>
<template v-slot:body>
<div></div>
<label>Localisation</label>
<vue-multiselect
v-model="location"
:options="locations"
:label="'name'"
:track-by="'id'"
></vue-multiselect>
</template>
<template v-slot:body>
<div></div>
<label>Localisation</label>
<vue-multiselect
v-model="location"
:options="locations"
:label="'name'"
:track-by="'id'"
></vue-multiselect>
</template>
<template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }}
</button>
</template>
</modal>
</component>
<template v-slot:footer>
<button class="btn btn-save" @click="saveAndClose">
{{ "Enregistrer" }}
</button>
</template>
</modal>
</component>
</template>
<script setup lang="ts">
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
const Teleport = teleport_ as new () => {
$props: VNodeProps & TeleportProps;
$props: VNodeProps & TeleportProps;
};
const store = useStore(key);
@@ -50,37 +50,37 @@ const showModal = ref(false);
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
const locations = computed<Location[]>(() => {
return store.state.locations.locations;
return store.state.locations.locations;
});
const startEdit = function (event: EventApi): void {
console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value =
store.getters["locations/getLocationById"](
event.extendedProps.locationId,
) || null;
console.log("startEditing", event);
calendarRangeId.value = event.extendedProps.calendarRangeId;
location.value =
store.getters["locations/getLocationById"](
event.extendedProps.locationId,
) || null;
console.log("new location value", location.value);
console.log("calendar range id", calendarRangeId.value);
showModal.value = true;
console.log("new location value", location.value);
console.log("calendar range id", calendarRangeId.value);
showModal.value = true;
};
const saveAndClose = function (e: Event): void {
console.log("saveEditAndClose", e);
console.log("saveEditAndClose", e);
store
.dispatch("calendarRanges/patchRangeLocation", {
location: location.value,
calendarRangeId: calendarRangeId.value,
})
.then((_) => {
showModal.value = false;
});
store
.dispatch("calendarRanges/patchRangeLocation", {
location: location.value,
calendarRangeId: calendarRangeId.value,
})
.then((_) => {
showModal.value = false;
});
};
const closeModal = function (_: any): void {
showModal.value = false;
showModal.value = false;
};
defineExpose({ startEdit });

View File

@@ -1,27 +1,27 @@
const appMessages = {
fr: {
created_availabilities: "Lieu des plages de disponibilités créées",
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends",
copy_range: "Copier",
copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer",
by: "Par",
main_user_concerned: "Utilisateur concerné",
dateFrom: "De",
dateTo: "à",
day: "Jour",
week: "Semaine",
month: "Mois",
today: "Aujourd'hui",
},
fr: {
created_availabilities: "Lieu des plages de disponibilités créées",
edit_your_calendar_range: "Planifiez vos plages de disponibilités",
show_my_calendar: "Afficher mon calendrier",
show_weekends: "Afficher les week-ends",
copy_range: "Copier",
copy_range_from_to: "Copier les plages",
from_day_to_day: "d'un jour à l'autre",
from_week_to_week: "d'une semaine à l'autre",
copy_range_how_to:
"Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
new_range_to_save: "Nouvelles plages à enregistrer",
update_range_to_save: "Plages à modifier",
delete_range_to_save: "Plages à supprimer",
by: "Par",
main_user_concerned: "Utilisateur concerné",
dateFrom: "De",
dateTo: "à",
day: "Jour",
week: "Semaine",
month: "Mois",
today: "Aujourd'hui",
},
};
export { appMessages };

View File

@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
import { useI18n } from "vue-i18n";
futureStore().then((store) => {
const i18n = _createI18n(appMessages, false);
const i18n = _createI18n(appMessages, false);
const app = createApp({
template: `<app></app>`,
})
.use(store, key)
.use(i18n)
.component("app", App2)
.mount("#myCalendar");
const app = createApp({
template: `<app></app>`,
})
.use(store, key)
.use(i18n)
.component("app", App2)
.mount("#myCalendar");
});

View File

@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
import calendarRemotes, {
CalendarRemotesState,
CalendarRemotesState,
} from "./modules/calendarRemotes";
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
@@ -15,42 +15,40 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
const debug = process.env.NODE_ENV !== "production";
export interface State {
calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState;
me: MeState;
locations: LocationState;
calendarRanges: CalendarRangesState;
calendarRemotes: CalendarRemotesState;
calendarLocals: CalendarLocalsState;
fullCalendar: FullCalendarState;
me: MeState;
locations: LocationState;
}
export const key: InjectionKey<Store<State>> = Symbol();
const futureStore = function (): Promise<Store<State>> {
return whoami().then((user: User) => {
const store = createStore<State>({
strict: debug,
modules: {
me,
fullCalendar,
calendarRanges,
calendarRemotes,
calendarLocals,
locations,
},
mutations: {},
});
store.commit("me/setWhoAmi", user, { root: true });
store
.dispatch("locations/getLocations", null, { root: true })
.then((_) => {
return store.dispatch("locations/getCurrentLocation", null, {
root: true,
});
});
return Promise.resolve(store);
return whoami().then((user: User) => {
const store = createStore<State>({
strict: debug,
modules: {
me,
fullCalendar,
calendarRanges,
calendarRemotes,
calendarLocals,
locations,
},
mutations: {},
});
store.commit("me/setWhoAmi", user, { root: true });
store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
return store.dispatch("locations/getCurrentLocation", null, {
root: true,
});
});
return Promise.resolve(store);
});
};
export default futureStore;

View File

@@ -8,109 +8,99 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const";
export interface CalendarLocalsState {
locals: EventInput[];
localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>;
key: number;
locals: EventInput[];
localsLoaded: { start: number; end: number }[];
localsIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarLocalsState, State>;
export default {
namespaced: true,
state: (): CalendarLocalsState => ({
locals: [],
localsLoaded: [],
localsIndex: new Set<string>(),
key: 0,
}),
getters: {
isLocalsLoaded:
(state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
namespaced: true,
state: (): CalendarLocalsState => ({
locals: [],
localsLoaded: [],
localsIndex: new Set<string>(),
key: 0,
}),
getters: {
isLocalsLoaded:
(state: CalendarLocalsState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.localsLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
return false;
},
return false;
},
},
mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log("addLocals", ranges);
const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
},
mutations: {
addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
console.log("addLocals", ranges);
const toAdd = ranges
.map((cr) => localsToFullCalendarEvent(cr))
.filter((r) => !state.localsIndex.has(r.id));
toAdd.forEach((r) => {
state.localsIndex.add(r.id);
state.locals.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarLocalsState,
payload: { start: Date; end: Date },
) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
state.localsLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
actions: {
fetchLocals(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
},
actions: {
fetchLocals(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.getters.isLocalsLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isLocalsLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarLocalForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => localsToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
.then((remotes: CalendarLight[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => localsToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null);
});
},
return Promise.resolve(null);
});
},
},
} as Module<CalendarLocalsState, State>;

View File

@@ -1,10 +1,10 @@
import { State } from "./../index";
import { ActionContext, Module } from "vuex";
import {
CalendarRange,
CalendarRangeCreate,
CalendarRangeEdit,
isEventInputCalendarRange,
CalendarRange,
CalendarRangeCreate,
CalendarRangeEdit,
isEventInputCalendarRange,
} from "../../../../types";
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
@@ -12,369 +12,332 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
import { EventInput } from "@fullcalendar/core";
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import {
datetimeToISO,
dateToISO,
ISOToDatetime,
datetimeToISO,
dateToISO,
ISOToDatetime,
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import type { EventInputCalendarRange } from "../../../../types";
export interface CalendarRangesState {
ranges: (EventInput | EventInputCalendarRange)[];
rangesLoaded: { start: number; end: number }[];
rangesIndex: Set<string>;
key: number;
ranges: (EventInput | EventInputCalendarRange)[];
rangesLoaded: { start: number; end: number }[];
rangesIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarRangesState, State>;
export default {
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
rangesLoaded: [],
rangesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRangeLoaded:
(state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
rangesLoaded: [],
rangesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRangeLoaded:
(state: CalendarRangesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.rangesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
return false;
},
getRangesOnDate:
(state: CalendarRangesState) =>
(date: Date): EventInputCalendarRange[] => {
const founds = [];
const dateStr = dateToISO(date) as string;
return false;
},
getRangesOnDate:
(state: CalendarRangesState) =>
(date: Date): EventInputCalendarRange[] => {
const founds = [];
const dateStr = dateToISO(date) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
return founds;
},
getRangesOnWeek:
(state: CalendarRangesState) =>
(mondayDate: Date): EventInputCalendarRange[] => {
const founds = [];
for (const d of Array.from(Array(7).keys())) {
const dateOfWeek = new Date(mondayDate);
dateOfWeek.setDate(mondayDate.getDate() + d);
const dateStr = dateToISO(dateOfWeek) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
}
return founds;
},
getRangesOnWeek:
(state: CalendarRangesState) =>
(mondayDate: Date): EventInputCalendarRange[] => {
const founds = [];
for (const d of Array.from(Array(7).keys())) {
const dateOfWeek = new Date(mondayDate);
dateOfWeek.setDate(mondayDate.getDate() + d);
const dateStr = dateToISO(dateOfWeek) as string;
for (const range of state.ranges) {
if (
isEventInputCalendarRange(range) &&
range.start.startsWith(dateStr)
) {
founds.push(range);
}
}
}
return founds;
},
return founds;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
const toAdd = ranges
.map((cr) => calendarRangeToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
}))
.filter((r) => !state.rangesIndex.has(r.id));
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addExternals(
state: CalendarRangesState,
externalEvents: (EventInput & { id: string })[],
) {
const toAdd = externalEvents.filter(
(r) => !state.rangesIndex.has(r.id),
);
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRangesState,
payload: { start: Date; end: Date },
) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) =>
r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) =>
!(
r.calendarRangeId === calendarRangeId &&
r.is === "range"
),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
}
},
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
},
toAdd.forEach((r) => {
state.rangesIndex.add(r.id);
state.ranges.push(r);
});
state.key = state.key + toAdd.length;
},
actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
},
createRange(
ctx: Context,
{
start,
end,
location,
}: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
},
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
location: {
id: location.id,
type: "location",
},
} as CalendarRangeCreate;
return makeFetch<CalendarRangeCreate, CalendarRange>(
"POST",
url,
body,
)
.then((newRange) => {
ctx.commit("addRange", newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
},
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
patchRangeTime(
ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
patchRangeLocation(
ctx,
{
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>(
"PATCH",
url,
body,
)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(
to.getFullYear(),
to.getMonth(),
to.getDate(),
);
const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(
ctx.dispatch("createRange", { start, end, location }),
);
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
state.rangesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges.push({
...asEvent,
backgroundColor: "white",
borderColor: "#3788d8",
textColor: "black",
});
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
removeRange(state: CalendarRangesState, calendarRangeId: number) {
const found = state.ranges.find(
(r) => r.calendarRangeId === calendarRangeId && r.is === "range",
);
if (found !== undefined) {
state.ranges = state.ranges.filter(
(r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
);
if (typeof found.id === "string") {
// should always be true
state.rangesIndex.delete(found.id);
}
state.key = state.key + 1;
}
},
updateRange(state: CalendarRangesState, range: CalendarRange) {
const found = state.ranges.find(
(r) => r.calendarRangeId === range.id && r.is === "range",
);
const newEvent = calendarRangeToFullCalendarEvent(range);
if (found !== undefined) {
found.start = newEvent.start;
found.end = newEvent.end;
found.locationId = range.location.id;
found.locationName = range.location.name;
}
state.key = state.key + 1;
},
},
actions: {
fetchRanges(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters["me/getMe"],
start,
end,
).then((ranges: CalendarRange[]) => {
ctx.commit("addRanges", ranges);
return Promise.resolve(null);
});
},
createRange(
ctx: Context,
{ start, end, location }: { start: Date; end: Date; location: Location },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range.json?`;
if (ctx.rootState.me.me === null) {
throw new Error("user is currently null");
}
const body = {
user: {
id: ctx.rootState.me.me.id,
type: "user",
},
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
location: {
id: location.id,
type: "location",
},
} as CalendarRangeCreate;
return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
.then((newRange) => {
ctx.commit("addRange", newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
throw error;
});
},
deleteRange(ctx: Context, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>("DELETE", url).then(() => {
ctx.commit("removeRange", calendarRangeId);
});
},
patchRangeTime(
ctx,
{
calendarRangeId,
start,
end,
}: { calendarRangeId: number; start: Date; end: Date },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
startDate: {
datetime: datetimeToISO(start),
},
endDate: {
datetime: datetimeToISO(end),
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
patchRangeLocation(
ctx,
{
location,
calendarRangeId,
}: { location: Location; calendarRangeId: number },
): Promise<null> {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
const body = {
location: {
id: location.id,
type: "location",
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
.then((range) => {
ctx.commit("updateRange", range);
return Promise.resolve(null);
})
.catch((error) => {
console.error(error);
return Promise.resolve(null);
});
},
copyFromDayToAnotherDay(
ctx,
{ from, to }: { from: Date; to: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnDate"](from);
const promises = [];
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const end = new Date(ISOToDatetime(r.end) as Date);
end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
copyFromWeekToAnotherWeek(
ctx: Context,
{ fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
): Promise<null> {
const rangesToCopy: EventInputCalendarRange[] =
ctx.getters["getRangesOnWeek"](fromMonday);
const promises = [];
const diffTime = toMonday.getTime() - fromMonday.getTime();
for (const r of rangesToCopy) {
const start = new Date(ISOToDatetime(r.start) as Date);
const end = new Date(ISOToDatetime(r.end) as Date);
start.setTime(start.getTime() + diffTime);
end.setTime(end.getTime() + diffTime);
const location = ctx.rootGetters["locations/getLocationById"](
r.locationId,
);
promises.push(ctx.dispatch("createRange", { start, end, location }));
}
return Promise.all(promises).then(() => Promise.resolve(null));
},
},
} as Module<CalendarRangesState, State>;

View File

@@ -8,109 +8,102 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
import { COLORS } from "../../../Calendar/const";
export interface CalendarRemotesState {
remotes: EventInput[];
remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>;
key: number;
remotes: EventInput[];
remotesLoaded: { start: number; end: number }[];
remotesIndex: Set<string>;
key: number;
}
type Context = ActionContext<CalendarRemotesState, State>;
export default {
namespaced: true,
state: (): CalendarRemotesState => ({
remotes: [],
remotesLoaded: [],
remotesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRemotesLoaded:
(state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) {
if (
start.getTime() === range.start &&
end.getTime() === range.end
) {
return true;
}
}
namespaced: true,
state: (): CalendarRemotesState => ({
remotes: [],
remotesLoaded: [],
remotesIndex: new Set<string>(),
key: 0,
}),
getters: {
isRemotesLoaded:
(state: CalendarRemotesState) =>
({ start, end }: { start: Date; end: Date }): boolean => {
for (const range of state.remotesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
}
}
return false;
},
return false;
},
},
mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log("addRemotes", ranges);
const toAdd = ranges
.map((cr) => remoteToFullCalendarEvent(cr))
.filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
},
mutations: {
addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
console.log("addRemotes", ranges);
const toAdd = ranges
.map((cr) => remoteToFullCalendarEvent(cr))
.filter((r) => !state.remotesIndex.has(r.id));
toAdd.forEach((r) => {
state.remotesIndex.add(r.id);
state.remotes.push(r);
});
state.key = state.key + toAdd.length;
},
addLoaded(
state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
addLoaded(
state: CalendarRemotesState,
payload: { start: Date; end: Date },
) {
state.remotesLoaded.push({
start: payload.start.getTime(),
end: payload.end.getTime(),
});
},
actions: {
fetchRemotes(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
},
actions: {
fetchRemotes(
ctx: Context,
payload: { start: Date; end: Date },
): Promise<null> {
const start = payload.start;
const end = payload.end;
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.rootGetters["me/getMe"] === null) {
return Promise.resolve(null);
}
if (ctx.getters.isRemotesLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRemotesLoaded({ start, end })) {
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit("addLoaded", {
start: start,
end: end,
});
ctx.commit("addLoaded", {
start: start,
end: end,
});
return fetchCalendarRemoteForUser(
ctx.rootGetters["me/getMe"],
start,
end,
)
.then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => remoteToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
.then((remotes: CalendarRemote[]) => {
// to be add when reactivity problem will be solve ?
//ctx.commit('addRemotes', remotes);
const inputs = remotes
.map((cr) => remoteToFullCalendarEvent(cr))
.map((cr) => ({
...cr,
backgroundColor: COLORS[0],
textColor: "black",
editable: false,
}));
ctx.commit("calendarRanges/addExternals", inputs, {
root: true,
});
return Promise.resolve(null);
})
.catch((e: TransportExceptionInterface) => {
console.error(e);
return Promise.resolve(null);
});
},
return Promise.resolve(null);
});
},
},
} as Module<CalendarRemotesState, State>;

View File

@@ -2,77 +2,77 @@ import { State } from "./../index";
import { ActionContext } from "vuex";
export interface FullCalendarState {
currentView: {
start: Date | null;
end: Date | null;
};
key: number;
currentView: {
start: Date | null;
end: Date | null;
};
key: number;
}
type Context = ActionContext<FullCalendarState, State>;
export default {
namespaced: true,
state: (): FullCalendarState => ({
currentView: {
start: null,
end: null,
},
key: 0,
}),
mutations: {
setCurrentDatesView: function (
state: FullCalendarState,
payload: { start: Date; end: Date },
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
namespaced: true,
state: (): FullCalendarState => ({
currentView: {
start: null,
end: null,
},
actions: {
setCurrentDatesView(
ctx: Context,
{ start, end }: { start: Date | null; end: Date | null },
): Promise<null> {
console.log("dispatch setCurrentDatesView", { start, end });
if (
ctx.state.currentView.start !== start ||
ctx.state.currentView.end !== end
) {
ctx.commit("setCurrentDatesView", { start, end });
}
if (start !== null && end !== null) {
return Promise.all([
ctx
.dispatch(
"calendarRanges/fetchRanges",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarRemotes/fetchRemotes",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarLocals/fetchLocals",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
]).then((_) => Promise.resolve(null));
} else {
return Promise.resolve(null);
}
},
key: 0,
}),
mutations: {
setCurrentDatesView: function (
state: FullCalendarState,
payload: { start: Date; end: Date },
): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
increaseKey: function (state: FullCalendarState): void {
state.key = state.key + 1;
},
},
actions: {
setCurrentDatesView(
ctx: Context,
{ start, end }: { start: Date | null; end: Date | null },
): Promise<null> {
console.log("dispatch setCurrentDatesView", { start, end });
if (
ctx.state.currentView.start !== start ||
ctx.state.currentView.end !== end
) {
ctx.commit("setCurrentDatesView", { start, end });
}
if (start !== null && end !== null) {
return Promise.all([
ctx
.dispatch(
"calendarRanges/fetchRanges",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarRemotes/fetchRemotes",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
ctx
.dispatch(
"calendarLocals/fetchLocals",
{ start, end },
{ root: true },
)
.then((_) => Promise.resolve(null)),
]).then((_) => Promise.resolve(null));
} else {
return Promise.resolve(null);
}
},
},
};

View File

@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
export interface LocationState {
locations: Location[];
locationPicked: Location | null;
currentLocation: Location | null;
locations: Location[];
locationPicked: Location | null;
currentLocation: Location | null;
}
export default {
namespaced: true,
state: (): LocationState => {
return {
locations: [],
locationPicked: null,
currentLocation: null,
};
namespaced: true,
state: (): LocationState => {
return {
locations: [],
locationPicked: null,
currentLocation: null,
};
},
getters: {
getLocationById:
(state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
},
getters: {
getLocationById:
(state) =>
(id: number): Location | undefined => {
return state.locations.find((l) => l.id === id);
},
},
mutations: {
setLocations(state, locations): void {
state.locations = locations;
},
setLocationPicked(state, location: Location | null): void {
if (null === location) {
state.locationPicked = null;
return;
}
setLocationPicked(state, location: Location | null): void {
if (null === location) {
state.locationPicked = null;
return;
}
state.locationPicked =
state.locations.find((l) => l.id === location.id) || null;
},
setCurrentLocation(state, location: Location | null): void {
if (null === location) {
state.currentLocation = null;
return;
}
state.locationPicked =
state.locations.find((l) => l.id === location.id) || null;
},
setCurrentLocation(state, location: Location | null): void {
if (null === location) {
state.currentLocation = null;
return;
}
state.currentLocation =
state.locations.find((l) => l.id === location.id) || null;
},
state.currentLocation =
state.locations.find((l) => l.id === location.id) || null;
},
actions: {
getLocations(ctx): Promise<void> {
return getLocations().then((locations) => {
ctx.commit("setLocations", locations);
return Promise.resolve();
});
},
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
},
actions: {
getLocations(ctx): Promise<void> {
return getLocations().then((locations) => {
ctx.commit("setLocations", locations);
return Promise.resolve();
});
},
getCurrentLocation(ctx): Promise<void> {
return whereami().then((location) => {
ctx.commit("setCurrentLocation", location);
});
},
},
} as Module<LocationState, State>;

View File

@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
import { ActionContext } from "vuex";
export interface MeState {
me: User | null;
me: User | null;
}
type Context = ActionContext<MeState, State>;
export default {
namespaced: true,
state: (): MeState => ({
me: null,
}),
getters: {
getMe: function (state: MeState): User | null {
return state.me;
},
namespaced: true,
state: (): MeState => ({
me: null,
}),
getters: {
getMe: function (state: MeState): User | null {
return state.me;
},
mutations: {
setWhoAmi(state: MeState, me: User) {
state.me = me;
},
},
mutations: {
setWhoAmi(state: MeState, me: User) {
state.me = me;
},
},
};

View File

@@ -1,51 +1,51 @@
<template>
<div>
<h2 class="chill-red">
{{ $t("choose_your_calendar_user") }}
</h2>
<VueMultiselect
name="field"
id="calendarUserSelector"
v-model="value"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_user')"
:multiple="true"
:close-on-select="false"
:allow-empty="true"
:model-value="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectUsers"
@remove="unSelectUsers"
@close="coloriseSelectedValues"
:options="options"
/>
</div>
<div class="form-check">
<input
type="checkbox"
id="myCalendar"
class="form-check-input"
v-model="showMyCalendarWidget"
/>
<label class="form-check-label" for="myCalendar">{{
$t("show_my_calendar")
}}</label>
</div>
<div class="form-check">
<input
type="checkbox"
id="weekends"
class="form-check-input"
@click="toggleWeekends"
/>
<label class="form-check-label" for="weekends">{{
$t("show_weekends")
}}</label>
</div>
<div>
<h2 class="chill-red">
{{ $t("choose_your_calendar_user") }}
</h2>
<VueMultiselect
name="field"
id="calendarUserSelector"
v-model="value"
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_user')"
:multiple="true"
:close-on-select="false"
:allow-empty="true"
:model-value="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
@select="selectUsers"
@remove="unSelectUsers"
@close="coloriseSelectedValues"
:options="options"
/>
</div>
<div class="form-check">
<input
type="checkbox"
id="myCalendar"
class="form-check-input"
v-model="showMyCalendarWidget"
/>
<label class="form-check-label" for="myCalendar">{{
$t("show_my_calendar")
}}</label>
</div>
<div class="form-check">
<input
type="checkbox"
id="weekends"
class="form-check-input"
@click="toggleWeekends"
/>
<label class="form-check-label" for="weekends">{{
$t("show_weekends")
}}</label>
</div>
</template>
<script>
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
@@ -53,206 +53,183 @@ import VueMultiselect from "vue-multiselect";
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
const COLORS = [
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
/* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f",
];
export default {
name: "CalendarUserSelector",
components: { VueMultiselect },
props: [
"users",
"updateEventsSource",
"calendarEvents",
"showMyCalendar",
"toggleMyCalendar",
"toggleWeekends",
],
data() {
return {
errorMsg: [],
value: [],
options: [],
};
name: "CalendarUserSelector",
components: { VueMultiselect },
props: [
"users",
"updateEventsSource",
"calendarEvents",
"showMyCalendar",
"toggleMyCalendar",
"toggleWeekends",
],
data() {
return {
errorMsg: [],
value: [],
options: [],
};
},
computed: {
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
},
computed: {
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
this.updateEventsSource();
},
get() {
return this.showMyCalendar;
},
},
},
methods: {
init() {
this.fetchData();
},
methods: {
init() {
this.fetchData();
},
fetchData() {
fetchCalendarRanges()
.then(
(calendarRanges) =>
new Promise((resolve, reject) => {
let results = calendarRanges.results;
fetchData() {
fetchCalendarRanges()
.then(
(calendarRanges) =>
new Promise((resolve, reject) => {
let results = calendarRanges.results;
let users = [];
let users = [];
results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(
users.length / COLORS.length,
);
let colorIndex =
users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find(
(u) => u.id === me.id,
);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
new Promise(
(resolve, reject) => {
let results =
calendar.results;
let events =
results.map(
(i) => ({
start: i
.startDate
.datetime,
end: i
.endDate
.datetime,
}),
);
let calendarEventsCurrentUser =
{
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user =
calendarEventsCurrentUser;
this.selectUsers(
currentUser,
);
resolve();
},
),
);
resolve();
}),
);
resolve();
}),
)
.catch((error) => {
this.errorMsg.push(error.message);
});
},
transName(value) {
return `${value.username}`;
},
coloriseSelectedValues() {
let tags = document.querySelectorAll(
"div.multiselect__tags-wrap",
)[0];
if (tags.hasChildNodes()) {
let children = tags.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
this.users.selected.forEach((u) => {
if (child.hasChildNodes()) {
if (child.firstChild.innerText == u.username) {
child.style.background = u.color;
child.firstChild.style.color = "#444444";
}
}
});
}
results.forEach((i) => {
if (!users.some((j) => i.user.id === j.id)) {
let ratio = Math.floor(users.length / COLORS.length);
let colorIndex = users.length - ratio * COLORS.length;
users.push({
id: i.user.id,
username: i.user.username,
color: COLORS[colorIndex],
});
}
}
},
selectEvents() {
let selectedUsersId = this.users.selected.map((a) => a.id);
this.calendarEvents.selected = this.calendarEvents.loaded.filter(
(a) => selectedUsersId.includes(a.id),
);
},
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter(
(a) => a.id != value.id,
);
this.selectEvents();
this.updateEventsSource();
},
});
let calendarEvents = [];
users.forEach((u) => {
let arr = results
.filter((i) => i.user.id === u.id)
.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
calendarRangeId: i.id,
sourceColor: u.color,
//display: 'background' // can be an option for the disponibility
}));
calendarEvents.push({
events: arr,
color: u.color,
textColor: "#444444",
editable: false,
id: u.id,
});
});
this.users.loaded = users;
this.options = users;
this.calendarEvents.loaded = calendarEvents;
whoami().then(
(me) =>
new Promise((resolve, reject) => {
this.users.logged = me;
let currentUser = users.find((u) => u.id === me.id);
this.value = currentUser;
fetchCalendar(currentUser.id).then(
(calendar) =>
new Promise((resolve, reject) => {
let results = calendar.results;
let events = results.map((i) => ({
start: i.startDate.datetime,
end: i.endDate.datetime,
}));
let calendarEventsCurrentUser = {
events: events,
color: "darkblue",
id: 1000,
editable: false,
};
this.calendarEvents.user = calendarEventsCurrentUser;
this.selectUsers(currentUser);
resolve();
}),
);
resolve();
}),
);
resolve();
}),
)
.catch((error) => {
this.errorMsg.push(error.message);
});
},
mounted() {
this.init();
transName(value) {
return `${value.username}`;
},
coloriseSelectedValues() {
let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
if (tags.hasChildNodes()) {
let children = tags.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.ELEMENT_NODE) {
this.users.selected.forEach((u) => {
if (child.hasChildNodes()) {
if (child.firstChild.innerText == u.username) {
child.style.background = u.color;
child.firstChild.style.color = "#444444";
}
}
});
}
}
}
},
selectEvents() {
let selectedUsersId = this.users.selected.map((a) => a.id);
this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
selectedUsersId.includes(a.id),
);
},
selectUsers(value) {
this.users.selected.push(value);
this.coloriseSelectedValues();
this.selectEvents();
this.updateEventsSource();
},
unSelectUsers(value) {
this.users.selected = this.users.selected.filter((a) => a.id != value.id);
this.selectEvents();
this.updateEventsSource();
},
},
mounted() {
this.init();
},
};
</script>