split Rotations to overrides, rotations and final
This commit is contained in:
parent
0174c973e3
commit
6da94edb3f
13 changed files with 256 additions and 80 deletions
|
|
@ -17,7 +17,6 @@ import styles from './Rotations.module.css';
|
|||
const cx = cn.bind(styles);
|
||||
|
||||
interface RotationsProps {
|
||||
title: string;
|
||||
startMoment: dayjs.Dayjs;
|
||||
currentTimezone: Timezone;
|
||||
}
|
||||
|
|
@ -36,24 +35,25 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { title, startMoment, currentTimezone } = this.props;
|
||||
const { startMoment, currentTimezone } = this.props;
|
||||
const { layerIdToCreateRotation } = this.state;
|
||||
|
||||
const layers = [
|
||||
{ id: 0, title: 'Layer 1' },
|
||||
/* { id: 1, title: 'Layer 2' },
|
||||
/*{ id: 1, title: 'Layer 2' },
|
||||
{ id: 2, title: 'Layer 3' },
|
||||
{ id: 3, title: 'Layer 4' },*/
|
||||
{ id: 3, title: 'Layer 4' }*/
|
||||
,
|
||||
];
|
||||
|
||||
const rotations = [{} /*, {}*/];
|
||||
const rotations = [{} /* {}*/];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<div className={cx('title')}>{title}</div>
|
||||
<div className={cx('title')}>Rotations</div>
|
||||
<ValuePicker
|
||||
label="Add rotation"
|
||||
options={layers.map(({ title, id }) => ({
|
||||
|
|
@ -77,7 +77,7 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
</div>
|
||||
<div className={cx('header-plus-content')}>
|
||||
<div className={cx('current-time')} />
|
||||
<TimelineMarks startMoment={startMoment} />
|
||||
<TimelineMarks debug startMoment={startMoment} />
|
||||
<div className={cx('rotations')}>
|
||||
{rotations.map((rotation, rotationIndex) => (
|
||||
<Rotation
|
||||
|
|
|
|||
65
grafana-plugin/src/components/Rotations/ScheduleFinal.tsx
Normal file
65
grafana-plugin/src/components/Rotations/ScheduleFinal.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, Input, ValuePicker } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import RotationForm from 'components/RotationForm/RotationForm';
|
||||
import ScheduleOverrideForm from 'components/ScheduleOverrideForm/ScheduleOverrideForm';
|
||||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import Rotation from 'containers/Rotation/Rotation';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
||||
import styles from './Rotations.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ScheduleOverridesProps extends WithStoreProps {}
|
||||
|
||||
interface ScheduleOverridesState {}
|
||||
|
||||
@observer
|
||||
class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverridesState> {
|
||||
state: ScheduleOverridesState = {};
|
||||
|
||||
render() {
|
||||
const { title, startMoment, currentTimezone } = this.props;
|
||||
const { showAddOverrideForm, searchTerm } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<div className={cx('title')}>Final schedule</div>
|
||||
<Input
|
||||
prefix={<Icon name="search" />}
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={this.onSearchTermChangeCallback}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<div className={cx('header-plus-content')}>
|
||||
<div className={cx('current-time')} />
|
||||
<TimelineMarks startMoment={startMoment} />
|
||||
<div className={cx('rotations')}>
|
||||
<Rotation
|
||||
id="final"
|
||||
startMoment={startMoment}
|
||||
currentTimezone={currentTimezone}
|
||||
layerIndex={0}
|
||||
rotationIndex={0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
onSearchTermChangeCallback = () => {};
|
||||
}
|
||||
|
||||
export default withMobXProviderContext(ScheduleOverrides);
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import { Button, HorizontalGroup, Icon, ValuePicker } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import OverrideForm from 'components/OverrideForm/OverrideForm';
|
||||
import RotationForm from 'components/RotationForm/RotationForm';
|
||||
import ScheduleOverrideForm from 'components/ScheduleOverrideForm/ScheduleOverrideForm';
|
||||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import Rotation from 'containers/Rotation/Rotation';
|
||||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
||||
import styles from './Rotations.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ScheduleOverridesProps extends WithStoreProps {}
|
||||
|
||||
interface ScheduleOverridesState {}
|
||||
|
||||
@observer
|
||||
class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverridesState> {
|
||||
state: ScheduleOverridesState = {};
|
||||
|
||||
render() {
|
||||
const { title, startMoment, currentTimezone } = this.props;
|
||||
const { showAddOverrideForm } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('header')}>
|
||||
<HorizontalGroup justify="space-between">
|
||||
<div className={cx('title')}>Overrides</div>
|
||||
<Button icon="plus" onClick={this.handleAddOverride} variant="secondary">
|
||||
Add override
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<div className={cx('header-plus-content')}>
|
||||
<div className={cx('current-time')} />
|
||||
<TimelineMarks startMoment={startMoment} />
|
||||
<div className={cx('rotations')}>
|
||||
<Rotation id="override" color="#C69B06" startMoment={startMoment} currentTimezone={currentTimezone} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx('add-rotations-layer')}>Add override +</div>
|
||||
</div>
|
||||
{showAddOverrideForm && (
|
||||
<ScheduleOverrideForm
|
||||
onHide={() => {
|
||||
this.setState({ showAddOverrideForm: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
handleAddOverride = () => {
|
||||
this.setState({ showAddOverrideForm: true });
|
||||
};
|
||||
}
|
||||
|
||||
export default withMobXProviderContext(ScheduleOverrides);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.root {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React, { FC } from 'react';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import styles from './ScheduleOverrideForm.module.css';
|
||||
|
||||
interface ScheduleOverrideFormProps {
|
||||
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const ScheduleOverrideForm: FC<ScheduleOverrideFormProps> = props => {
|
||||
const { } = props;
|
||||
|
||||
return (
|
||||
<div className={cx('root')} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ScheduleOverrideForm;
|
||||
|
|
@ -24,12 +24,13 @@ interface ScheduleSlotProps {
|
|||
shift: Shift;
|
||||
startMoment: dayjs.Dayjs;
|
||||
currentTimezone: Timezone;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {
|
||||
const { index, layerIndex, rotationIndex, shift, startMoment, currentTimezone } = props;
|
||||
const { index, layerIndex, rotationIndex, shift, startMoment, currentTimezone, color: propColor } = props;
|
||||
const { duration, users } = shift;
|
||||
|
||||
const isGap = !users.length;
|
||||
|
|
@ -44,12 +45,15 @@ const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {
|
|||
<div className={cx('stack')} style={{ width: `${width * 100}%` /*left: `${x * 100}%`*/ }}>
|
||||
{!isGap ? (
|
||||
users.map((pk, userIndex) => {
|
||||
const label = index === 0 && userIndex == 0 && getLabel(layerIndex, rotationIndex);
|
||||
const label =
|
||||
!isNaN(layerIndex) && !isNaN(rotationIndex) && index === 0 && userIndex === 0
|
||||
? getLabel(layerIndex, rotationIndex)
|
||||
: null;
|
||||
const storeUser = store.userStore.items[pk];
|
||||
|
||||
const inactive = false;
|
||||
|
||||
const color = getColor(layerIndex, rotationIndex);
|
||||
const color = propColor || getColor(layerIndex, rotationIndex);
|
||||
const title = getTitle(storeUser);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import styles from './TimelineMarks.module.css';
|
|||
|
||||
interface TimelineMarksProps {
|
||||
startMoment: dayjs.Dayjs;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const TimelineMarks: FC<TimelineMarksProps> = (props) => {
|
||||
const { startMoment } = props;
|
||||
const { startMoment, debug } = props;
|
||||
|
||||
const momentsToRender = useMemo(() => {
|
||||
const hoursToSplit = 12;
|
||||
|
|
@ -32,26 +33,30 @@ const TimelineMarks: FC<TimelineMarksProps> = (props) => {
|
|||
return momentsToRender;
|
||||
}, [startMoment]);
|
||||
|
||||
const cuts = [];
|
||||
for (let i = 0; i < 24 * 7; i++) {
|
||||
cuts.push({});
|
||||
}
|
||||
cuts.push({});
|
||||
const cuts = useMemo(() => {
|
||||
const cuts = [];
|
||||
for (let i = 0; i <= 24 * 7; i++) {
|
||||
cuts.push({});
|
||||
}
|
||||
return cuts;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
<svg version="1.1" width="100%" height="6px" xmlns="http://www.w3.org/2000/svg" className={cx('debug-scale')}>
|
||||
{cuts.map((cut, index) => (
|
||||
<line
|
||||
x1={`${(index * 100) / (24 * 7)}%`}
|
||||
strokeWidth={1}
|
||||
y1="0"
|
||||
x2={`${(index * 100) / (24 * 7)}%`}
|
||||
y2="6px"
|
||||
stroke="rgba(204, 204, 220, 0.65)"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
{debug && (
|
||||
<svg version="1.1" width="100%" height="6px" xmlns="http://www.w3.org/2000/svg" className={cx('debug-scale')}>
|
||||
{cuts.map((cut, index) => (
|
||||
<line
|
||||
x1={`${(index * 100) / (24 * 7)}%`}
|
||||
strokeWidth={1}
|
||||
y1="0"
|
||||
x2={`${(index * 100) / (24 * 7)}%`}
|
||||
y2="6px"
|
||||
stroke="rgba(204, 204, 220, 0.65)"
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
)}
|
||||
{momentsToRender.map((m, i) => {
|
||||
return (
|
||||
<div key={i} className={cx('weekday')}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { getRandomTimezone } from 'components/UsersTimezones/UsersTimezones.helpers';
|
||||
|
||||
export const getRandomGroups = () => {
|
||||
return [
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
.root {
|
||||
transition: background-color 300ms;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.root:last-child{
|
||||
|
|
|
|||
|
|
@ -19,34 +19,45 @@ interface ScheduleSlotState {}
|
|||
|
||||
interface RotationProps {
|
||||
id: RotationType['id'];
|
||||
layerIndex: number;
|
||||
rotationIndex: number;
|
||||
label: string;
|
||||
startMoment: dayjs.Dayjs;
|
||||
currentTimezone: Timezone;
|
||||
layerIndex?: number;
|
||||
rotationIndex?: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
const Rotation: FC<RotationProps> = observer((props) => {
|
||||
const { id, layerIndex, rotationIndex, label, startMoment, currentTimezone } = props;
|
||||
const { id, layerIndex, rotationIndex, label, startMoment, currentTimezone, color } = props;
|
||||
|
||||
const store = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
store.scheduleStore.updateRotation(id);
|
||||
}, []);
|
||||
const startMomentString = startMoment.utc().format('YYYY-MM-DDTHH:mm:ss.000Z');
|
||||
|
||||
store.scheduleStore.updateRotation(id, startMomentString);
|
||||
}, [startMoment]);
|
||||
|
||||
const rotation = store.scheduleStore.rotations[id];
|
||||
|
||||
if (!rotation) {
|
||||
return <LoadingPlaceholder text="Loading shifts..." />;
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
<LoadingPlaceholder text="Loading shifts..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const base = 60 * 24 * 7; // in minutes
|
||||
const { shifts } = rotation;
|
||||
|
||||
const firstShift = shifts[0];
|
||||
|
||||
const firstShiftOffset = firstShift.start.diff(startMoment, 'minutes');
|
||||
|
||||
const base = 60 * 24 * 7; // in minutes only
|
||||
const utcOffset = dayjs().tz(currentTimezone).utcOffset();
|
||||
|
||||
const x = utcOffset / base;
|
||||
|
||||
const { shifts } = rotation;
|
||||
const x = (firstShiftOffset + utcOffset) / base;
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
|
|
@ -63,6 +74,7 @@ const Rotation: FC<RotationProps> = observer((props) => {
|
|||
rotationIndex={rotationIndex}
|
||||
startMoment={startMoment}
|
||||
currentTimezone={currentTimezone}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import dayjs from 'dayjs';
|
||||
import { omit, reject } from 'lodash-es';
|
||||
import { action, observable, toJS } from 'mobx';
|
||||
import ReactCSSTransitionGroup from 'react-transition-group'; // ES6
|
||||
|
||||
import BaseStore from 'models/base_store';
|
||||
import { makeRequest } from 'network';
|
||||
|
|
@ -10,6 +11,34 @@ import { Rotation, Schedule, ScheduleEvent } from './schedule.types';
|
|||
|
||||
const DEFAULT_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
|
||||
|
||||
function getUsers() {
|
||||
const rnd = Math.random();
|
||||
/*
|
||||
|
||||
if (rnd > 0.66) {
|
||||
return [];
|
||||
}
|
||||
*/
|
||||
|
||||
const users = [
|
||||
'U5WE86241LNEA',
|
||||
'U9XM1G7KTE3KW',
|
||||
'UYKS64M6C59XM',
|
||||
'UFFIRDUFXA6W3',
|
||||
'UPRMSTP9LCADE',
|
||||
'UR6TVJWZYV19M',
|
||||
'UHRMQQ7KETPCS',
|
||||
];
|
||||
|
||||
/* if (rnd > 0.33) {
|
||||
return [users[Math.floor(Math.random() * users.length)], users[Math.floor(Math.random() * users.length)]];
|
||||
}*/
|
||||
|
||||
return ['UPRMSTP9LCADE', 'UHRMQQ7KETPCS'];
|
||||
|
||||
return [users[Math.floor(Math.random() * users.length)]];
|
||||
}
|
||||
|
||||
export class ScheduleStore extends BaseStore {
|
||||
@observable
|
||||
searchResult: { [key: string]: Array<Schedule['id']> } = {};
|
||||
|
|
@ -114,42 +143,14 @@ export class ScheduleStore extends BaseStore {
|
|||
});
|
||||
}
|
||||
|
||||
async updateRotation(rotationId: Rotation['id'], from?: string) {
|
||||
async updateRotation(rotationId: Rotation['id'], fromString: string) {
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
function getUsers() {
|
||||
const rnd = Math.random();
|
||||
/*
|
||||
|
||||
if (rnd > 0.66) {
|
||||
return [];
|
||||
}
|
||||
*/
|
||||
|
||||
const users = [
|
||||
'U5WE86241LNEA',
|
||||
'U9XM1G7KTE3KW',
|
||||
'UYKS64M6C59XM',
|
||||
'UFFIRDUFXA6W3',
|
||||
'UPRMSTP9LCADE',
|
||||
'UR6TVJWZYV19M',
|
||||
'UHRMQQ7KETPCS',
|
||||
];
|
||||
|
||||
/* if (rnd > 0.33) {
|
||||
return [users[Math.floor(Math.random() * users.length)], users[Math.floor(Math.random() * users.length)]];
|
||||
}*/
|
||||
|
||||
return ['UPRMSTP9LCADE', 'UHRMQQ7KETPCS'];
|
||||
|
||||
return [users[Math.floor(Math.random() * users.length)]];
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (!from) {
|
||||
from = dayjs().startOf('week').format('YYYY-MM-DDTHH:mm:ss');
|
||||
if (!fromString) {
|
||||
fromString = dayjs().startOf('week').format('YYYY-MM-DDTHH:mm:ss.000Z');
|
||||
}
|
||||
|
||||
const startMoment = dayjs(`${from}.000Z`).utc();
|
||||
const startMoment = dayjs(fromString).utc();
|
||||
|
||||
const shifts = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ export const getRandomUsers = (count = 5) => {
|
|||
//name: getRandomUser(),
|
||||
pk: i,
|
||||
name: [
|
||||
'Hypothetical UTC user',
|
||||
'Some Etc/Universal user',
|
||||
'Matias Bordese',
|
||||
'Michael Derynck',
|
||||
'Yulia Shanyrova',
|
||||
|
|
@ -640,7 +640,7 @@ export const getRandomUsers = (count = 5) => {
|
|||
][i],
|
||||
//tz: getRandomTimezone(),
|
||||
tz: [
|
||||
'UTC',
|
||||
'Etc/Universal',
|
||||
'America/Montevideo',
|
||||
'America/Vancouver',
|
||||
'Europe/Amsterdam',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import Draggable from 'react-draggable';
|
|||
// import Rotations from 'components/Rotations/Rotations';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Rotations from 'components/Rotations/Rotations';
|
||||
import ScheduleFinal from 'components/Rotations/ScheduleFinal';
|
||||
import ScheduleOverrides from 'components/Rotations/ScheduleOverrides';
|
||||
import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter';
|
||||
import ScheduleQuality from 'components/ScheduleQuality/ScheduleQuality';
|
||||
import Text from 'components/Text/Text';
|
||||
|
|
@ -37,7 +39,7 @@ interface SchedulePageState {
|
|||
currentTimezone: Timezone;
|
||||
}
|
||||
|
||||
const INITIAL_TIMEZONE = 'UTC';
|
||||
const INITIAL_TIMEZONE = 'Etc/Universal'; // todo check why doesn't work
|
||||
|
||||
@observer
|
||||
class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState> {
|
||||
|
|
@ -55,8 +57,6 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
store.userStore.updateItems();
|
||||
}
|
||||
|
||||
componentDidUpdate() {}
|
||||
|
||||
render() {
|
||||
const { startMoment, schedulePeriodType, renderType, users, currentTimezone } = this.state;
|
||||
const { query } = this.props;
|
||||
|
|
@ -156,9 +156,9 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
</div>
|
||||
{/* <div className={'current-time'} />*/}
|
||||
<div className={cx('rotations')}>
|
||||
{/*<Rotations startMoment={startMoment} title="Final schedule" />*/}
|
||||
<Rotations currentTimezone={currentTimezone} startMoment={startMoment} title="Rotations" />
|
||||
{/* <Rotations startMoment={startMoment} title="Overrides" />*/}
|
||||
<ScheduleFinal currentTimezone={currentTimezone} startMoment={startMoment} />
|
||||
<Rotations currentTimezone={currentTimezone} startMoment={startMoment} />
|
||||
<ScheduleOverrides currentTimezone={currentTimezone} startMoment={startMoment} />
|
||||
</div>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue