split Rotations to overrides, rotations and final

This commit is contained in:
Maxim 2022-07-19 11:22:34 +01:00
parent 0174c973e3
commit 6da94edb3f
13 changed files with 256 additions and 80 deletions

View file

@ -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

View 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);

View file

@ -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);

View file

@ -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;

View file

@ -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 (

View file

@ -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')}>

View file

@ -1,5 +1,3 @@
import { getRandomTimezone } from 'components/UsersTimezones/UsersTimezones.helpers';
export const getRandomGroups = () => {
return [
[

View file

@ -1,5 +1,6 @@
.root {
transition: background-color 300ms;
min-height: 28px;
}
.root:last-child{

View file

@ -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}
/>
);
})}

View file

@ -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++) {

View file

@ -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',

View file

@ -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>