basic structure for using modules in stores and convert to TS

This commit is contained in:
Julien Fastré 2022-06-20 09:49:53 +02:00
parent 6c4f116e85
commit 8c99fc0089
16 changed files with 457 additions and 93 deletions

View File

@ -0,0 +1,54 @@
import {EventInput} from '@fullcalendar/vue3';
export interface DateTime {
datetime: string;
datetime8601: string
}
export interface Job {
id: number;
interface: "user_job";
label: {
"fr": string; // could have other key. How to do that in ts ?
}
}
export interface Center {
id: number;
interface: "center";
name: string;
}
export interface Scope {
id: number;
interface: "scope";
name: {
"fr": string
}
}
export interface User {
interface: "user";
id: number;
username: string;
text: string;
email: string;
user_job: Job;
// todo: mainCenter; mainJob; etc..
}
export interface CalendarRange {
id: number;
endDate: DateTime;
startdate: DateTime;
user: User;
createdAt: DateTime;
createdBy: User;
updatedAt: DateTime;
updatedBy: User;
}
export interface Calendar {
id: number;
}

View File

@ -0,0 +1,5 @@
export function whoami(): import('ChillCalendarAssets/types').User;
export function fetchCalendarRangeForUser(user: import('ChillCalendarAssets/types').User, start: Date, end: Date): import('ChillCalendarAssets/types').CalendarRange[];
export function fetchCalendarRemoteForUser(user: import('ChillCalendarAssets/types').User, start: Date, end: Date): import('ChillCalendarAssets/types').Calendar[];

View File

@ -0,0 +1,2 @@
export function calendarRangeToFullCalendarEvent(entity: import('ChillCalendarAssets/types').CalendarRange): import('@fullcalendar/vue3').EventInput;

View File

@ -50,6 +50,7 @@ const createUserData = (user, colorIndex) => {
} }
} }
// TODO move this function to a more global namespace, as it is in use in MyCalendarRange app
const calendarRangeToFullCalendarEvent = (entity) => { const calendarRangeToFullCalendarEvent = (entity) => {
return { return {
id: `range_${entity.id}`, id: `range_${entity.id}`,

View File

@ -11,7 +11,7 @@
<label class="form-check-label" for="weekends">{{ $t('show_weekends') }}</label> <label class="form-check-label" for="weekends">{{ $t('show_weekends') }}</label>
</div> </div>
<FullCalendar ref="fullCalendar" :options="{...calendarOptions, eventSources: [this.rangeSource, this.appointmentSource]}"> <FullCalendar ref="fullCalendar" :options="calendarOptions">
<template v-slot:eventContent='arg' > <template v-slot:eventContent='arg' >
<span class='calendarRangeItems'> <span class='calendarRangeItems'>
<b v-if="arg.event.extendedProps.myCalendar" style="text-decoration: underline" >{{ arg.timeText }}</b> <b v-if="arg.event.extendedProps.myCalendar" style="text-decoration: underline" >{{ arg.timeText }}</b>
@ -119,20 +119,61 @@ export default {
myCalendarClickedEvent: null, myCalendarClickedEvent: null,
lastNewDate: null, lastNewDate: null,
disableCopyDayButton: true, disableCopyDayButton: true,
calendarOptions: { showWeekends: false,
eventSources: [],
}
},
computed: {
...mapState({
rangesToCopy: state => state.rangesToCopy
}),
//...mapGetters(),
/*, 'appointmentSource'*/
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
},
get() {
return this.showMyCalendar;
}
},
events() {
let e = this.$store.getters.getEventSources;
console.log('events', e);
e.push(
{
id: 'fake',
events: [
{
start: '2022-06-17T12:00:00+02:00',
end: '2022-06-17T14:00:00+02:00',
title: Math.random().toString(36).slice(2, 7),
}
]
}
);
return e;
},
calendarOptions() {
return {
locale: frLocale, locale: frLocale,
plugins: [ dayGridPlugin, interactionPlugin, timeGridPlugin ], plugins: [ dayGridPlugin, interactionPlugin, timeGridPlugin ],
initialView: 'timeGridWeek', initialView: 'timeGridWeek',
initialDate: window.startDate !== undefined ? window.startDate : new Date(), initialDate: window.startDate !== undefined ? window.startDate : new Date(),
selectable: true, selectable: true,
select: this.onDateSelect, select: this.onDateSelect,
datesSet: this.onDatesSet,
eventChange: this.onEventChange, eventChange: this.onEventChange,
eventDrop: this.onEventDropOrResize, eventDrop: this.onEventDropOrResize,
eventResize: this.onEventDropOrResize, eventResize: this.onEventDropOrResize,
eventClick: this.onEventClick, eventClick: this.onEventClick,
eventSources: this.eventSources,
selectMirror: false, selectMirror: false,
editable: true, editable: true,
weekends: false, weekends: this.showWeekends,
slotDuration: '00:15:00', slotDuration: '00:15:00',
slotMinTime: "08:00:00", slotMinTime: "08:00:00",
slotMaxTime: "19:00:00", slotMaxTime: "19:00:00",
@ -142,31 +183,25 @@ export default {
center: 'title', center: 'title',
right: 'dayGridMonth timeGridWeek timeGridDay' right: 'dayGridMonth timeGridWeek timeGridDay'
}, },
}, };
} },
},
computed: {
...mapState({
rangesToCopy: state => state.rangesToCopy
}),
...mapGetters(['rangeSource', 'appointmentSource']),
showMyCalendarWidget: {
set(value) {
this.toggleMyCalendar(value);
},
get() {
return this.showMyCalendar;
}
}
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'fetchRanges', //'fetchRanges',
'fetchAppointments', //'fetchAppointments',
'postRange', 'postRange',
'patchRange', 'patchRange',
'deleteRange' 'deleteRange'
]), ]),
onDatesSet(event) {
console.log('onDatesSet', event);
this.$store.dispatch('fullCalendar/setCurrentDatesView', {start: event.start, end: event.end})
.then(source => {
console.log('onDatesSet finished');
this.eventSources.push(source);
});
},
openModal() { openModal() {
this.modal.showModal = true; this.modal.showModal = true;
}, },
@ -184,7 +219,7 @@ export default {
this.showMyCalendar = value; this.showMyCalendar = value;
}, },
toggleWeekends: function() { toggleWeekends: function() {
this.calendarOptions.weekends = !this.calendarOptions.weekends; this.showWeekends = !this.showWeekends;
}, },
replaceDate(from, to) { replaceDate(from, to) {
@ -271,10 +306,8 @@ export default {
} }
}, },
mounted() { mounted() {
this.fetchRanges()
if (this.showMyCalendar) { if (this.showMyCalendar) {
this.fetchAppointments(this.userId) // this.fetchAppointments(this.userId)
} }
} }
} }

View File

@ -1,28 +1,43 @@
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods'; import {makeFetch, fetchResults} from 'ChillMainAssets/lib/api/apiMethods';
import {fetchCalendarRangeForUser} from 'ChillCalendarAssets/vuejs/Calendar/api';
import {ActionContext} from 'vuex';
import {State} from './index';
type Context = ActionContext<State, State>;
const actions = { const actions = {
fetchRanges({commit}, payload = null) { setCurrentDatesView(, {start, end}) {
const url = payload ? `/api/1.0/calendar/calendar-range-available.json?user=${payload.userId}&start=${payload.dateToCopy}` : commit('setCurrentDatesView', {start, end});
`/api/1.0/calendar/calendar-range-available.json?user=${window.userId}` dispatch('fetchRanges');
return makeFetch('GET', url) },
.then((response) => { fetchRanges({commit, state}, payload = null) {
if (payload) { if (state.me === null) {
return response.results; console.log('me is not there');
} else { return Promise.resolve();
const ranges = response.results.map(range => ( }
{
start: range.startDate.datetime, if (state.currentView.start === null || state.currentView.end === null) {
end: range.endDate.datetime, console.log('current view dates are null');
calendarRangeId: range.id, return Promise.resolve();
toDelete: false }
}
)); if (getters.isRangeLoaded) {
commit('setRanges', ranges) console.log('range already loaded');
} return Promise.resolve();
}
commit('addLoaded', {start: state.currentView.start, end: state.currentView.end});
return fetchCalendarRangeForUser(state.me, state.currentView.start, state.currentView.end)
.then(ranges => {
commit('setRanges', ranges);
return Promise.resolve();
}) })
.catch((error) => { .catch(err => {
console.log(error); console.error(err);
return Promise.resolve();
}) })
;
}, },
postRange({commit}, payload) { postRange({commit}, payload) {
const url = `/api/1.0/calendar/calendar-range.json?`; const url = `/api/1.0/calendar/calendar-range.json?`;

View File

@ -18,7 +18,16 @@ const getters = {
} else { } else {
return null return null
} }
} },
isRangeLoaded: (state) => (start, end) => {
for (let {rStart, rEnd} of state.rangesLoaded) {
if (start === rStart && end === rEnd) {
return true;
}
}
return false;
},
}; };
export default getters; export default getters;

View File

@ -0,0 +1,11 @@
import 'es6-promise/auto';
import { MeState } from './modules/me';
import { FullCalendarState } from './modules/fullcalendar';
import { CalendarRangesState } from './modules/calendarRanges';
export interface State {
me: MeState;
fullCalendar: FullCalendarState;
calendarRanges: CalendarRangesState;
}
declare const store: import("vuex").Store<State>;
export default store;

View File

@ -1,21 +0,0 @@
import 'es6-promise/auto';
import {createStore} from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
const debug = process.env.NODE_ENV !== 'production';
const store = createStore({
strict: debug,
state: {
ranges: [],
appointments: [],
appointmentsShown: true
},
getters,
mutations,
actions,
});
export default store;

View File

@ -0,0 +1,63 @@
import 'es6-promise/auto';
import Vuex from 'vuex';
//import actions from './actions';
//import getters from './getters';
//import mutations from './mutations';
import {User} from 'ChillCalendarAssets/types';
import me, {MeState} from './modules/me';
import fullCalendar, {FullCalendarState} from './modules/fullcalendar';
import calendarRanges, {CalendarRangesState} from './modules/calendarRanges';
import {whoami} from 'ChillCalendarAssets/vuejs/Calendar/api';
const debug = process.env.NODE_ENV !== 'production';
export interface State {
/*
appointments: Calendar[],
appointmentsShown: boolean,
startDate: Date|null,
endDate: Date|null,
*/
me: MeState,
fullCalendar: FullCalendarState,
calendarRanges: CalendarRangesState,
}
const store = new Vuex.Store<State>({
strict: debug,
modules: {
me,
fullCalendar,
calendarRanges,
},
getters: {
getEventSources(state, getters) {
let s = [
getters["calendarRanges/getRangeSource"],
];
console.log('getEventSources', s);
return s;
}
}
/*
state: {
appointments: [],
appointmentsShown: true,
startDate: null,
endDate: null,
},
*/
//getters,
//mutations,
//actions,
});
whoami().then((me: User) => {
store.commit('me/setWhoAmi', me, {root: true})
store.dispatch('calendarRanges/fetchRanges', null, {root: true});
});
export default store;

View File

@ -0,0 +1,108 @@
import {State} from './../index';
import {ActionContext} from 'vuex';
import {CalendarRange/*, CalendarRangeEvent*/} from 'ChillCalendarAssets/types';
import {fetchCalendarRangeForUser} from 'ChillCalendarAssets/vuejs/Calendar/api';
import {calendarRangeToFullCalendarEvent/*, CalendarRangeEvent*/} from 'ChillCalendarAssets/vuejs/Calendar/store/utils';
import {EventInput} from '@fullcalendar/vue3';
export interface CalendarRangesState {
ranges: EventInput[],
rangesLoaded: {start: Date, end: Date}[],
rangesIndex: Set<string>,
}
export interface RangeSource {
events: EventInput[],
borderColor: string,
backgroundColor: string,
textColor: string
}
type Context = ActionContext<CalendarRangesState, State>;
export default {
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
rangesLoaded: [],
rangesIndex: new Set<string>
}),
getters: {
getRangeSource(state: CalendarRangesState): RangeSource {
let o = {
events: state.ranges,
borderColor: "#3788d8",
backgroundColor: '#ffffff',
textColor: '#444444',
}
console.log('getRangeSource', o);
return o;
},
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
console.log('addRanges', ranges);
const toAdd = ranges
.map(cr => calendarRangeToFullCalendarEvent(cr))
.filter(r => !state.rangesIndex.has(r.id));
state.ranges.push(...toAdd);
toAdd.forEach((r) => {state.rangesIndex.add(r.id)});
console.log('ranges', state.ranges);
},
addLoaded(state: CalendarRangesState, payload: {start: Date, end: Date}) {
state.rangesLoaded.push({start: payload.start, end: payload.end});
},/*
setRangesToCopy(state: State, payload: CalendarRange[]) {
state.rangesToCopy = payload
},*/
addRange(state: CalendarRangesState, payload: CalendarRange) {
const asEvent = calendarRangeToFullCalendarEvent(payload);
state.ranges = [...state.ranges, asEvent];
state.rangesIndex.add(asEvent.id);
},
removeRange(state: CalendarRangesState, payload: EventInput) {
state.ranges = state.ranges.filter(
(r) => r.id !== payload.id
);
if (typeof payload.id === "string") {
state.rangesIndex.delete(payload.id);
}
},
},
actions: {
fetchRanges(ctx: Context): Promise<RangeSource> {
console.log('fetchRanges');
if (ctx.rootGetters['me/getMe'] === null) {
console.log('me is not there');
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.rootState.fullCalendar.currentView.start === null || ctx.rootState.fullCalendar.currentView.end === null) {
console.log('current view dates are null');
return Promise.resolve(ctx.getters.getRangeSource);
}
if (ctx.getters.isRangeLoaded) {
console.log('range already loaded');
return Promise.resolve(ctx.getters.getRangeSource);
}
ctx.commit('addLoaded', {
start: ctx.rootState.fullCalendar.currentView.start,
end: ctx.rootState.fullCalendar.currentView.end,
});
return fetchCalendarRangeForUser(
ctx.rootGetters['me/getMe'],
ctx.rootState.fullCalendar.currentView.start,
ctx.rootState.fullCalendar.currentView.end
)
.then((ranges: CalendarRange[]) => {
ctx.commit('addRanges', ranges);
return Promise.resolve(ctx.getters.getRangeSource);
});
}
}
};

View File

@ -0,0 +1,38 @@
import {State} from './../index';
import {ActionContext} from 'vuex';
import {RangeSource} from './calendarRanges';
export interface FullCalendarState {
currentView: {
start: Date|null,
end: Date|null,
}
}
type Context = ActionContext<FullCalendarState, State>;
export default {
namespaced: true,
state: (): FullCalendarState => ({
currentView: {
start: null,
end: null,
}
}),
mutations: {
setCurrentDatesView: function(state: FullCalendarState, payload: {start: Date, end: Date}): void {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
},
actions: {
setCurrentDatesView(ctx: Context, p: {start: Date, end: Date}): Promise<RangeSource> {
console.log('dispatch setCurrentDatesView', p);
ctx.commit('setCurrentDatesView', {start: p.start, end: p.end});
return ctx.dispatch('calendarRanges/fetchRanges', null, {root: true});
},
}
}

View File

@ -0,0 +1,29 @@
import {State} from './../index';
import {User} from 'ChillCalendarAssets/types';
import {ActionContext} from 'vuex';
export interface MeState {
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;
},
},
mutations: {
setWhoAmi(state: MeState, me: User) {
state.me = me;
},
}
};

View File

@ -1,25 +0,0 @@
const mutations = {
setRanges(state, payload) {
state.ranges = payload;
},
setRangesToCopy(state, payload) {
state.rangesToCopy = payload
},
addRange(state, payload) {
state.ranges = [...state.ranges, payload];
},
removeRange(state, payload) {
const filteredCollection = state.ranges.filter(
(r) => r.calendarRangeId !== payload
)
state.ranges = filteredCollection;
},
setAppointments(state, payload) {
state.appointments = payload;
},
setAppointmentShown(state, payload) {
state.appointmentsShown = payload
}
};
export default mutations;

View File

@ -0,0 +1,39 @@
import {State} from './index';
import {CalendarRange, Calendar, User} from 'ChillCalendarAssets/types';
const mutations = {
setCurrentDatesView(state: State, payload: {start: Date, end: Date}) {
state.currentView.start = payload.start;
state.currentView.end = payload.end;
},
addRanges(state: State, ranges: CalendarRange[]) {
console.log('addRanges', ranges);
const toAdd = ranges.filter(r => !state.rangesIndex.has(r.id));
state.ranges.push(...toAdd);
toAdd.forEach((r) => {state.rangesIndex.add(r.id)});
},
addLoaded(state: State, payload: {start: Date, end: Date}) {
state.rangesLoaded.push({start: payload.start, end: payload.end});
},/*
setRangesToCopy(state: State, payload: CalendarRange[]) {
state.rangesToCopy = payload
},*/
addRange(state: State, payload: CalendarRange) {
state.ranges = [...state.ranges, payload];
},
removeRange(state: State, payload: CalendarRange) {
const filteredCollection = state.ranges.filter(
(r) => r.calendarRangeId !== payload
)
state.ranges = filteredCollection;
},
setAppointments(state: State, payload: Calendar) {
state.appointments = payload;
},
setAppointmentShown(state: State, payload: boolean) {
state.appointmentsShown = payload
}
};
export default mutations;

View File

@ -0,0 +1,3 @@
export function fetchResults<T>(uri: string, params: {item_per_page?: number}): Promise<T[]>;
export function makeFetch<T, B>(method: "GET"|"POST"|"PATCH"|"DELETE", url: string, body: B, options: {[key: string]: string}): Promise<T>;