add feature add and delete range

This commit is contained in:
Julien Fastré 2022-06-24 18:52:28 +02:00
parent 75b2f6419e
commit 3ea630748a
7 changed files with 137 additions and 35 deletions

View File

@ -1,5 +1,5 @@
import {EventInput} from '@fullcalendar/vue3';
import {DateTime, User} from 'ChillMainAssets/types';
import {DateTime, User, UserAssociatedInterface} from 'ChillMainAssets/types';
export interface CalendarRange {
id: number;
@ -12,6 +12,12 @@ export interface CalendarRange {
updatedBy: User;
}
export interface CalendarRangeEdit {
user: UserAssociatedInterface,
startDate: DateTime,
endDate: DateTime
}
export interface Calendar {
id: number;
}
@ -23,4 +29,11 @@ export interface CalendarRemote {
title: string
}
export type EventInputCalendarRange = EventInput & {
id: string,
userId: number,
calendarRangeId: number,
is: "range"
};
export {};

View File

@ -2,6 +2,7 @@ import {COLORS} from '../const';
import {ISOToDatetime} from 'ChillMainAssets/chill/js/date';
import {DateTime, User} from 'ChillMainAssets/types';
import {CalendarRange, CalendarRemote} from 'ChillCalendarAssets/types';
import type {EventInputCalendarRange} from 'ChillCalendarAssets/types';
import {EventInput} from '@fullcalendar/vue3';
export interface UserData {
@ -63,7 +64,7 @@ export const createUserData = (user: User, colorIndex: number): UserData => {
}
// TODO move this function to a more global namespace, as it is in use in MyCalendarRange app
export const calendarRangeToFullCalendarEvent = (entity: CalendarRange): EventInput & {id: string} => {
export const calendarRangeToFullCalendarEvent = (entity: CalendarRange): EventInputCalendarRange => {
return {
id: `range_${entity.id}`,
title: "(" + entity.user.text + ")",

View File

@ -48,7 +48,7 @@ import frLocale from '@fullcalendar/core/locales/fr';
import FullCalendar from '@fullcalendar/vue3';
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import {EventApi} from "@fullcalendar/core";
import {EventApi, DateSelectArg} from "@fullcalendar/core";
const store = useStore(key);
@ -64,7 +64,10 @@ const baseOptions = ref<CalendarOptions>({
initialView: 'timeGridWeek',
initialDate: new Date(),
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,
selectMirror: false,
editable: true,
slotMinTime: "08:00:00",
@ -121,15 +124,25 @@ const calendarOptions = computed((): CalendarOptions => {
/**
* launched when the calendar range date change
*/
function onDatesSet(event: DatesSetArg) {
function onDatesSet(event: DatesSetArg): void {
store.dispatch('fullCalendar/setCurrentDatesView', {start: event.start, end: event.end});
}
function onDateSelect(event: DateSelectArg): void {
store.dispatch('calendarRanges/createRange', {start: event.start, end: event.end});
}
/**
* When a calendar range is deleted
*/
function onClickDelete(event: EventApi) {
function onClickDelete(event: EventApi): void {
console.log('onClickDelete', event);
if (event.extendedProps.is !== 'range') {
return;
}
store.dispatch('calendarRanges/deleteRange', event.extendedProps.calendarRangeId);
}

View File

@ -24,6 +24,7 @@ export interface State {
calendarRanges: CalendarRangesState,
calendarRemotes: CalendarRemotesState,
fullCalendar: FullCalendarState,
me: MeState,
}
export const key: InjectionKey<Store<State>> = Symbol();

View File

@ -1,20 +1,24 @@
import {State} from './../index';
import {ActionContext, Module} from 'vuex';
import {CalendarRange/*, CalendarRangeEvent*/} from 'ChillCalendarAssets/types';
import {CalendarRange, CalendarRangeEdit} from "ChillCalendarAssets/types";
import {fetchCalendarRangeForUser} from 'ChillCalendarAssets/vuejs/Calendar/api';
import {calendarRangeToFullCalendarEvent/*, CalendarRangeEvent*/} from 'ChillCalendarAssets/vuejs/Calendar/store/utils';
import {EventInput, EventSource} from '@fullcalendar/vue3';
import {calendarRangeToFullCalendarEvent} from 'ChillCalendarAssets/vuejs/Calendar/store/utils';
import {UserAssociatedInterface} from "../../../../../../../ChillMainBundle/Resources/public/types";
import {EventInput} from '@fullcalendar/vue3';
import {makeFetch} from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
import {datetimeToISO} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import type {EventInputCalendarRange} from "ChillCalendarAssets/types";
export interface CalendarRangesState {
ranges: EventInput[],
rangesLoaded: {start: number, end: number}[],
ranges: (EventInput | EventInputCalendarRange) [],
rangesLoaded: { start: number, end: number }[],
rangesIndex: Set<string>,
key: number
}
type Context = ActionContext<CalendarRangesState, State>;
export default <Module<CalendarRangesState, State>> {
export default <Module<CalendarRangesState, State>>{
namespaced: true,
state: (): CalendarRangesState => ({
ranges: [],
@ -23,7 +27,7 @@ export default <Module<CalendarRangesState, State>> {
key: 0
}),
getters: {
isRangeLoaded: (state: CalendarRangesState) => ({start, end}: {start: Date, end: Date}): boolean => {
isRangeLoaded: (state: CalendarRangesState) => ({start, end}: { start: Date, end: Date }): boolean => {
for (let range of state.rangesLoaded) {
if (start.getTime() === range.start && end.getTime() === range.end) {
return true;
@ -35,12 +39,12 @@ export default <Module<CalendarRangesState, State>> {
},
mutations: {
addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
console.log('addRanges', ranges);
const toAdd = ranges
.map(cr => calendarRangeToFullCalendarEvent(cr))
.map(cr => ({...cr, backgroundColor: 'white', borderColor:'#3788d8',
textColor: 'black'}))
.map(cr => ({
...cr, backgroundColor: 'white', borderColor: '#3788d8',
textColor: 'black'
}))
.filter(r => !state.rangesIndex.has(r.id));
toAdd.forEach((r) => {
@ -49,7 +53,7 @@ export default <Module<CalendarRangesState, State>> {
});
state.key = state.key + toAdd.length;
},
addExternals(state, externalEvents: (EventInput & {id: string})[] ) {
addExternals(state, externalEvents: (EventInput & { id: string })[]) {
const toAdd = externalEvents
.filter(r => !state.rangesIndex.has(r.id));
@ -59,7 +63,7 @@ export default <Module<CalendarRangesState, State>> {
});
state.key = state.key + toAdd.length;
},
addLoaded(state: CalendarRangesState, payload: {start: Date, end: Date}) {
addLoaded(state: CalendarRangesState, payload: { start: Date, end: Date }) {
state.rangesLoaded.push({start: payload.start.getTime(), end: payload.end.getTime()});
},/*
@ -72,21 +76,24 @@ export default <Module<CalendarRangesState, State>> {
state.rangesIndex.add(asEvent.id);
state.key = state.key + 1;
},
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);
}
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;
}
},
},
actions: {
fetchRanges(ctx: Context, payload: {start: Date, end: Date}): Promise<null> {
fetchRanges(ctx: Context, payload: { start: Date, end: Date }): Promise<null> {
console.log('fetchRanges', payload);
const start = payload.start;
const end = payload.end;
@ -115,6 +122,48 @@ export default <Module<CalendarRangesState, State>> {
ctx.commit('addRanges', ranges);
return Promise.resolve(null);
});
}
},
createRange(ctx, {start, end}: { start: Date, end: Date }): 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)
},
} as CalendarRangeEdit;
return makeFetch<CalendarRangeEdit, CalendarRange>('POST', url, body)
.then((newRange) => {
ctx.commit('addRange', newRange);
return Promise.resolve(null);
})
.catch((error) => {
console.log(error);
throw error;
})
},
deleteRange({commit}, calendarRangeId: number) {
const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
makeFetch<undefined, never>('DELETE', url)
.then((_) => {
commit('removeRange', calendarRangeId);
});
},
}
};

View File

@ -51,13 +51,15 @@ export interface NotFoundExceptionInterface extends TransportExceptionInterface
export interface ServerExceptionInterface extends TransportExceptionInterface {
name: 'ServerException';
message: string;
code: number;
body: string;
}
/**
* Generic api method that can be adapted to any fetch request
*/
export const makeFetch = <T, Q>(method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', url: string, body?: body | T | null, options?: FetchParams): Promise<Q> => {
export const makeFetch = <Input, Output>(method: 'POST'|'GET'|'PUT'|'PATCH'|'DELETE', url: string, body?: body | Input | null, options?: FetchParams): Promise<Output> => {
let opts = {
method: method,
headers: {
@ -128,15 +130,31 @@ function _fetchAction<T>(page: number, uri: string, params?: FetchParams): Promi
},
}).then((response) => {
if (response.ok) { return response.json(); }
if (response.status === 404) {
throw NotFoundException(response);
}
console.error(response);
throw ServerException();
if (response.status === 422) {
return response.json().then(response => {
throw ValidationException(response)
});
}
if (response.status === 403) {
throw AccessException(response);
}
if (response.status >= 500) {
return response.text().then(body => {
throw ServerException(response.status, body);
});
}
throw new Error("other network error");
}).catch((reason: any) => {
console.error(reason);
throw ServerException();
throw new Error(reason);
});
};
@ -192,9 +210,11 @@ const NotFoundException = (response: Response): NotFoundExceptionInterface => {
return error;
}
const ServerException = (): ServerExceptionInterface => {
const ServerException = (code: number, body: string): ServerExceptionInterface => {
const error = {} as ServerExceptionInterface;
error.name = 'ServerException';
error.code = code;
error.body = body;
return error;
}

View File

@ -34,3 +34,8 @@ export interface User {
user_job: Job;
// todo: mainCenter; mainJob; etc..
}
export interface UserAssociatedInterface {
type: "user";
id: number;
};