add override preview

This commit is contained in:
Maxim 2022-08-23 12:21:54 +03:00
parent 1c6376f8f4
commit 9858054841
8 changed files with 207 additions and 63 deletions

View file

@ -46,6 +46,7 @@ interface RotationFormProps {
shiftId: Shift['id'] | 'new';
onCreate: () => void;
onUpdate: () => void;
onDelete: () => void;
}
const cx = cn.bind(styles);
@ -53,7 +54,8 @@ const cx = cn.bind(styles);
const startOfDay = dayjs().startOf('day').add(1, 'day');
const RotationForm: FC<RotationFormProps> = observer((props) => {
const { onHide, onCreate, startMoment, currentTimezone, scheduleId, onUpdate, layerPriority, shiftId } = props;
const { onHide, onCreate, startMoment, currentTimezone, scheduleId, onUpdate, onDelete, layerPriority, shiftId } =
props;
const [isOpen, setIsOpen] = useState<boolean>(true);
@ -79,8 +81,7 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
const handleDeleteClick = useCallback(() => {
store.scheduleStore.deleteOncallShift(shiftId).then(() => {
onHide();
onUpdate();
onDelete();
});
}, []);
@ -100,7 +101,7 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
until: endLess ? null : getUTCString(rotationEnd, currentTimezone),
shift_start: getUTCString(shiftStart, currentTimezone),
shift_end: getUTCString(shiftEnd, currentTimezone),
rolling_users: userGroups.filter((group) => group.length),
rolling_users: userGroups,
interval: repeatEveryValue,
frequency: repeatEveryPeriod,
by_day: repeatEveryPeriod === 1 ? selectedDays : null,
@ -125,23 +126,17 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
const handleCreate = useCallback(() => {
if (shiftId === 'new') {
store.scheduleStore.createRotation(scheduleId, false, params).then(() => {
onHide();
onCreate();
});
} else {
store.scheduleStore.updateRotation(shiftId, params).then(() => {
onHide();
onUpdate();
});
}
}, [shiftId, params]);
}, [scheduleId, shiftId, params]);
const handleChange = useDebouncedCallback(() => {
store.scheduleStore
.updateRotationPreview(scheduleId, shiftId, getFromString(startMoment), false, params)
.finally(() => {
setIsOpen(true);
});
store.scheduleStore.updateRotationPreview(scheduleId, shiftId, getFromString(startMoment), false, params);
}, 1000);
useEffect(handleChange, [params]);
@ -173,10 +168,6 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
setRepeatEveryValue(option.value);
}, []);
const handleRepeatEveryPeriodChange = useCallback((option) => {
setRepeatEveryPeriod(option.value);
}, []);
const moment = dayjs();
return (

View file

@ -1,4 +1,4 @@
import React, { FC, useCallback, useEffect, useState } from 'react';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { dateTime, DateTime } from '@grafana/data';
import {
@ -20,12 +20,14 @@ import Modal from 'components/Modal/Modal';
import Text from 'components/Text/Text';
import UserGroups from 'components/UserGroups/UserGroups';
import WithConfirm from 'components/WithConfirm/WithConfirm';
import { getFromString } from 'models/schedule/schedule.helpers';
import { Rotation, Schedule, Shift } from 'models/schedule/schedule.types';
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
import { Timezone } from 'models/timezone/timezone.types';
import { User } from 'models/user/user.types';
import { getDateTime, getUTCString } from 'pages/schedule/Schedule.helpers';
import { useStore } from 'state/useStore';
import { useDebouncedCallback } from 'utils/hooks';
import { RotationCreateData } from './RotationForm.types';
@ -34,18 +36,20 @@ import styles from './RotationForm.module.css';
interface RotationFormProps {
onHide: () => void;
shiftId: Shift['id'] | 'new';
startMoment: dayjs.Dayjs;
currentTimezone: Timezone;
scheduleId: Schedule['id'];
onCreate: () => void;
onUpdate: () => void;
onDelete: () => void;
}
const cx = cn.bind(styles);
const startOfDay = dayjs().startOf('day');
const startOfDay = dayjs().startOf('day').add(1, 'day');
const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
const { onHide, onCreate, currentTimezone, scheduleId, onUpdate, shiftId } = props;
const { onHide, onCreate, currentTimezone, scheduleId, onUpdate, onDelete, shiftId, startMoment } = props;
const store = useStore();
@ -71,6 +75,17 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
}
}, [shiftId]);
const params = useMemo(
() => ({
rotation_start: getUTCString(shiftStart, currentTimezone),
shift_start: getUTCString(shiftStart, currentTimezone),
shift_end: getUTCString(shiftEnd, currentTimezone),
rolling_users: userGroups,
frequency: null,
}),
[currentTimezone, shiftStart, shiftEnd, userGroups]
);
useEffect(() => {
if (shift) {
setShiftStart(getDateTime(shift.shift_start));
@ -83,32 +98,28 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
const handleDeleteClick = useCallback(() => {
store.scheduleStore.deleteOncallShift(shiftId).then(() => {
onHide();
onUpdate();
onDelete();
});
}, []);
const handleCreate = useCallback(() => {
const params = {
title: 'Override ' + Math.floor(Math.random() * 100),
rotation_start: getUTCString(shiftStart, currentTimezone),
shift_start: getUTCString(shiftStart, currentTimezone),
shift_end: getUTCString(shiftEnd, currentTimezone),
rolling_users: userGroups,
frequency: null,
};
if (shiftId === 'new') {
store.scheduleStore.createRotation(scheduleId, true, params).then(() => {
onHide();
onCreate();
});
} else {
store.scheduleStore.updateRotation(shiftId, params).then(() => {
onHide();
onUpdate();
});
}
}, [shiftStart, shiftEnd, userGroups]);
}, [scheduleId, shiftId, params]);
const handleChange = useDebouncedCallback(() => {
store.scheduleStore.updateRotationPreview(scheduleId, shiftId, getFromString(startMoment), true, params);
}, 1000);
useEffect(handleChange, [params]);
return (
<Modal
@ -122,7 +133,7 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
>
<VerticalGroup>
<HorizontalGroup justify="space-between">
<Text size="medium">{shiftId === 'new' ? 'New Override' : shift?.title}</Text>
<Text size="medium">{shiftId === 'new' ? 'New Override' : shift?.id}</Text>
<HorizontalGroup>
<IconButton disabled variant="secondary" tooltip="Copy" name="copy" />
<IconButton disabled variant="secondary" tooltip="Code" name="brackets-curly" />

View file

@ -28,6 +28,7 @@ interface RotationsProps extends WithStoreProps {
onClick: (id: Shift['id'] | 'new') => void;
onCreate: () => void;
onUpdate: () => void;
onDelete: () => void;
}
interface RotationsState {
@ -42,7 +43,7 @@ class Rotations extends Component<RotationsProps, RotationsState> {
};
render() {
const { scheduleId, startMoment, currentTimezone, onCreate, onUpdate, store, onClick } = this.props;
const { scheduleId, startMoment, currentTimezone, onCreate, onUpdate, onDelete, store, onClick } = this.props;
const { shiftIdToShowRotationForm, layerPriority } = this.state;
const base = 7 * 24 * 60; // in minutes
@ -161,21 +162,35 @@ class Rotations extends Component<RotationsProps, RotationsState> {
layerPriority={layerPriority}
startMoment={startMoment}
currentTimezone={currentTimezone}
onHide={this.handleRotationFormHide}
onUpdate={onUpdate}
onCreate={onCreate}
onHide={() => {
this.hideRotationForm();
store.scheduleStore.clearPreview();
}}
onUpdate={() => {
this.hideRotationForm();
onUpdate();
}}
onCreate={() => {
this.hideRotationForm();
onCreate();
}}
onDelete={() => {
this.hideRotationForm();
onDelete();
}}
/>
)}
</>
);
}
handleRotationFormHide = () => {
hideRotationForm = () => {
const { store } = this.props;
store.scheduleStore.rotationPreview = undefined;
store.scheduleStore.finalPreview = undefined;
this.setState({ shiftIdToShowRotationForm: undefined, layerPriority: undefined });
};

View file

@ -8,7 +8,7 @@ import { observer } from 'mobx-react';
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
import Rotation from 'containers/Rotation/Rotation';
import { getColor, getFromString } from 'models/schedule/schedule.helpers';
import { getColor, getFromString, getOverrideColor } from 'models/schedule/schedule.helpers';
import { Layer, Schedule } from 'models/schedule/schedule.types';
import { Timezone } from 'models/timezone/timezone.types';
import { WithStoreProps } from 'state/types';
@ -52,6 +52,9 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
? store.scheduleStore.rotationPreview
: (store.scheduleStore.events[scheduleId]?.['rotation']?.[getFromString(startMoment)] as Layer[]);
const overrides = store.scheduleStore.overridePreview
? store.scheduleStore.overridePreview
: store.scheduleStore.events[scheduleId]?.['override']?.[getFromString(startMoment)];
const currentTimeHidden = currentTimeX < 0 || currentTimeX > 1;
/* console.log('shifts', toJS(shifts));
@ -79,6 +82,8 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
<div className={cx('rotations')}>
{shifts && shifts.length ? (
shifts.map(({ shiftId, events }, index) => {
let color = undefined;
const layerIndex = layers
? layers.findIndex((layer) => layer.shifts.some((shift) => shift.shiftId === shiftId))
: -1;
@ -86,7 +91,15 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
const rotationIndex =
layerIndex > -1 ? layers[layerIndex].shifts.findIndex((shift) => shift.shiftId === shiftId) : -1;
console.log(layerIndex, rotationIndex);
if (layerIndex > -1 && rotationIndex > -1) {
color = getColor(layerIndex, rotationIndex);
} else {
const overrideIndex = overrides ? overrides.findIndex((shift) => shift.shiftId === shiftId) : -1;
if (overrideIndex > -1) {
color = getOverrideColor(overrideIndex);
}
}
return (
<Rotation
@ -94,7 +107,7 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
events={events}
startMoment={startMoment}
currentTimezone={currentTimezone}
color={getColor(layerIndex, rotationIndex)}
color={color}
/>
);
})

View file

@ -25,6 +25,7 @@ interface ScheduleOverridesProps extends WithStoreProps {
scheduleId: Schedule['id'];
onCreate: () => void;
onUpdate: () => void;
onDelete: () => void;
}
interface ScheduleOverridesState {
@ -38,10 +39,12 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
};
render() {
const { scheduleId, startMoment, currentTimezone, onCreate, onUpdate, store } = this.props;
const { scheduleId, startMoment, currentTimezone, onCreate, onUpdate, onDelete, store } = this.props;
const { shiftIdToShowOverrideForm } = this.state;
const shifts = store.scheduleStore.events[scheduleId]?.['override']?.[getFromString(startMoment)];
const shifts = store.scheduleStore.overridePreview
? store.scheduleStore.overridePreview
: store.scheduleStore.events[scheduleId]?.['override']?.[getFromString(startMoment)];
const base = 7 * 24 * 60; // in minutes
const diff = dayjs().tz(currentTimezone).diff(startMoment, 'minutes');
@ -98,12 +101,28 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
<ScheduleOverrideForm
shiftId={shiftIdToShowOverrideForm}
scheduleId={scheduleId}
startMoment={startMoment}
currentTimezone={currentTimezone}
onHide={() => {
this.setState({ shiftIdToShowOverrideForm: undefined });
this.handleHide();
store.scheduleStore.clearPreview();
}}
onUpdate={() => {
this.handleHide();
onUpdate();
}}
onCreate={() => {
this.handleHide();
onCreate();
}}
onDelete={() => {
this.handleHide();
onDelete();
}}
onUpdate={onUpdate}
onCreate={onCreate}
/>
)}
</>
@ -117,6 +136,12 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
handleAddOverride = () => {
this.setState({ shiftIdToShowOverrideForm: 'new' });
};
handleHide = () => {
const { store } = this.props;
this.setState({ shiftIdToShowOverrideForm: undefined });
};
}
export default withMobXProviderContext(ScheduleOverrides);

View file

@ -89,10 +89,12 @@ export const enrichLayers = (
shiftId: Shift['id'] | 'new',
priority: Shift['priority_level']
) => {
/*const event = newEvents.find((event) => !event.is_gap);
if (event) {
shiftId = event.shift.pk;
}*/
if (shiftId === 'new') {
const event = newEvents.find((event) => !event.is_gap);
if (event) {
shiftId = event.shift.pk;
}
}
const updatingLayer = {
priority,
@ -135,6 +137,31 @@ export const enrichLayers = (
return layers;
};
export const enrichOverrides = (
overrides: Array<{ shiftId: Shift['id']; events: Event[] }>,
newEvents: Event[],
shiftId: Shift['id']
) => {
if (shiftId === 'new') {
const event = newEvents.find((event) => !event.is_gap);
if (event) {
shiftId = event.shift.pk;
}
}
const newShift = { shiftId, events: fillGaps(newEvents) };
const index = overrides.findIndex((shift) => shift.shiftId === shiftId);
if (index > -1) {
overrides[index] = newShift;
} else {
overrides.push(newShift);
}
return overrides;
};
const L1_COLORS = ['#3D71D9', '#6D609C', '#4D3B72', '#8214A0'];
const L2_COLORS = ['#3CB979', '#188343', '#84362A', '#521913'];

View file

@ -11,7 +11,14 @@ import { makeRequest } from 'network';
import { RootStore } from 'state';
import { SelectOption } from 'state/types';
import { enrichLayers, fillGaps, getFromString, splitToLayers, splitToShiftsAndFillGaps } from './schedule.helpers';
import {
enrichLayers,
enrichOverrides,
fillGaps,
getFromString,
splitToLayers,
splitToShiftsAndFillGaps,
} from './schedule.helpers';
import { Events, Rotation, RotationType, Schedule, ScheduleEvent, Shift, Event, Layer } from './schedule.types';
const DEFAULT_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
@ -72,11 +79,14 @@ export class ScheduleStore extends BaseStore {
};
} = {};
@observable
finalPreview?: Array<{ shiftId: Shift['id']; events: Event[] }>;
@observable
rotationPreview?: Layer[];
@observable
finalPreview?: Array<{ shiftId: Shift['id']; events: Event[] }>;
overridePreview?: Array<{ shiftId: Shift['id']; events: Event[] }>;
@observable
scheduleToScheduleEvents: {
@ -209,6 +219,11 @@ export class ScheduleStore extends BaseStore {
}).catch(this.onApiError);
if (isOverride) {
this.overridePreview = enrichOverrides(
[...this.events[scheduleId]?.['override']?.[fromString]],
response.rotation,
shiftId
);
} else {
const layers = enrichLayers(
[...(this.events[scheduleId]?.['rotation']?.[fromString] as Layer[])],
@ -220,7 +235,14 @@ export class ScheduleStore extends BaseStore {
this.rotationPreview = layers;
}
this.finalPreview = splitToShiftsAndFillGaps(response.final).filter((shift) => shift.shiftId !== shiftId);
this.finalPreview = splitToShiftsAndFillGaps(response.final); /*.filter((shift) => shift.shiftId !== shiftId);*/
}
@action
clearPreview() {
this.finalPreview = undefined;
this.rotationPreview = undefined;
this.overridePreview = undefined;
}
async updateRotation(shiftId: Shift['id'], params: Partial<Shift>) {

View file

@ -190,14 +190,16 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
currentTimezone={currentTimezone}
startMoment={startMoment}
onCreate={this.handleCreateRotation}
onUpdate={this.updateEvents}
onUpdate={this.handleUpdateRotation}
onDelete={this.handleDeleteRotation}
/>
<ScheduleOverrides
scheduleId={scheduleId}
currentTimezone={currentTimezone}
startMoment={startMoment}
onCreate={this.handleCreateOverride}
onUpdate={this.updateEvents}
onUpdate={this.handleUpdateOverride}
onDelete={this.handleDeleteOverride}
/>
</div>
</VerticalGroup>
@ -213,9 +215,11 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
const { startMoment } = this.state;
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'rotation');
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'override');
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'final');
return Promise.all([
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'rotation'),
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'override'),
store.scheduleStore.updateEvents(scheduleId, getFromString(startMoment), 'final'),
]);
};
handleCreateRotation = () => {
@ -224,13 +228,49 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
query: { id: scheduleId },
} = this.props;
this.updateEvents();
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleCreateOverride = () => {
const { store } = this.props;
this.updateEvents();
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleUpdateRotation = () => {
const { store } = this.props;
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleDeleteRotation = () => {
const { store } = this.props;
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleDeleteOverride = () => {
const { store } = this.props;
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleUpdateOverride = () => {
const { store } = this.props;
this.updateEvents().then(() => {
store.scheduleStore.clearPreview();
});
};
handleTimezoneChange = (value: Timezone) => {