paint users in user groups
This commit is contained in:
parent
d5750a495d
commit
cb33155287
12 changed files with 145 additions and 41 deletions
|
|
@ -54,9 +54,15 @@
|
|||
.user {
|
||||
background: #22252b;
|
||||
border-radius: 2px;
|
||||
padding: 6px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-buttons {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.user:hover {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import cn from 'classnames/bind';
|
|||
import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
|
||||
|
||||
import Text from 'components/Text/Text';
|
||||
import WorkingHours from 'components/WorkingHours/WorkingHours';
|
||||
import GSelect from 'containers/GSelect/GSelect';
|
||||
import UserTooltip from 'containers/UserTooltip/UserTooltip';
|
||||
import { User } from 'models/user/user.types';
|
||||
|
|
@ -21,6 +22,7 @@ interface UserGroupsProps {
|
|||
onChange: (value: Array<Array<User['pk']>>) => void;
|
||||
isMultipleGroups: boolean;
|
||||
getItemData: (id: string) => ItemData;
|
||||
renderUser: (id: string) => React.ReactElement;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -30,7 +32,7 @@ const DragHandle = () => <IconButton name="draggabledots" />;
|
|||
const SortableHandleHoc = SortableHandle(DragHandle);
|
||||
|
||||
const UserGroups = (props: UserGroupsProps) => {
|
||||
const { value, onChange, isMultipleGroups, getItemData } = props;
|
||||
const { value, onChange, isMultipleGroups, getItemData, renderUser } = props;
|
||||
|
||||
const handleAddUserGroup = useCallback(() => {
|
||||
onChange([...value, []]);
|
||||
|
|
@ -86,10 +88,29 @@ const UserGroups = (props: UserGroupsProps) => {
|
|||
[items]
|
||||
);
|
||||
|
||||
const getDeleteItemHandler = (index: number) => {
|
||||
return () => {
|
||||
handleDeleteUser(index);
|
||||
};
|
||||
};
|
||||
|
||||
const renderItem = (item: Item, index: number) => (
|
||||
<li className={cx('user')}>
|
||||
{renderUser(item.item)}
|
||||
<div className={cx('user-buttons')}>
|
||||
<HorizontalGroup>
|
||||
<IconButton className={cx('delete-icon')} name="trash-alt" onClick={getDeleteItemHandler(index)} />
|
||||
<SortableHandleHoc />
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
<VerticalGroup>
|
||||
<SortableList
|
||||
renderItem={renderItem}
|
||||
axis="y"
|
||||
lockAxis="y"
|
||||
helperClass={cx('sortable-helper')}
|
||||
|
|
@ -130,32 +151,17 @@ interface SortableListProps {
|
|||
handleAddGroup: () => void;
|
||||
handleDeleteItem: (index: number) => void;
|
||||
isMultipleGroups: boolean;
|
||||
renderItem: (item: Item, index: number) => React.ReactElement;
|
||||
}
|
||||
|
||||
const SortableList = SortableContainer(
|
||||
({ items, handleAddGroup, handleDeleteItem, isMultipleGroups }: SortableListProps) => {
|
||||
const getDeleteItemHandler = (index: number) => {
|
||||
return () => {
|
||||
handleDeleteItem(index);
|
||||
};
|
||||
};
|
||||
|
||||
({ items, handleAddGroup, handleDeleteItem, isMultipleGroups, renderItem }: SortableListProps) => {
|
||||
return (
|
||||
<ul className={cx('groups')}>
|
||||
{items.map((item, index) =>
|
||||
item.type === 'item' ? (
|
||||
<SortableItem key={item.key} index={index}>
|
||||
<li className={cx('user')}>
|
||||
<div className={cx('user-title')}>
|
||||
<Text type="primary"> {item.data.name}</Text> <Text type="secondary">({item.data.desc})</Text>
|
||||
</div>
|
||||
<div className={cx('user-buttons')}>
|
||||
<HorizontalGroup>
|
||||
<IconButton className={cx('delete-icon')} name="trash-alt" onClick={getDeleteItemHandler(index)} />
|
||||
<SortableHandleHoc />
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</li>
|
||||
{renderItem(item, index)}
|
||||
</SortableItem>
|
||||
) : isMultipleGroups ? (
|
||||
<SortableItem key={item.key} index={index}>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
export interface Item {
|
||||
key: string;
|
||||
type: string;
|
||||
data: ItemData;
|
||||
data: any;
|
||||
item?: string;
|
||||
}
|
||||
|
||||
export interface ItemData {
|
||||
name: string;
|
||||
desc?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ interface WorkingHoursProps {
|
|||
startMoment: dayjs.Dayjs;
|
||||
duration: number; // in seconds
|
||||
className: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const WorkingHours: FC<WorkingHoursProps> = (props) => {
|
||||
const { timezone, workingHours, startMoment, duration, className } = props;
|
||||
const { timezone, workingHours, startMoment, duration, className, style } = props;
|
||||
|
||||
const endMoment = startMoment.add(duration, 'seconds');
|
||||
|
||||
|
|
@ -61,7 +62,14 @@ const WorkingHours: FC<WorkingHoursProps> = (props) => {
|
|||
);*/
|
||||
|
||||
return (
|
||||
<svg version="1.1" width="100%" height="28px" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="100%"
|
||||
height="28px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<defs>
|
||||
<pattern id="stripes" patternUnits="userSpaceOnUse" width="10" height="10" patternTransform="rotate(45)">
|
||||
<line x1="0" y="0" x2="0" y2="10" stroke="rgba(17, 18, 23, 0.15)" strokeWidth="10" />
|
||||
|
|
|
|||
|
|
@ -62,9 +62,8 @@
|
|||
|
||||
.empty {
|
||||
height: 28px;
|
||||
|
||||
/* background: #5f505633;
|
||||
background: #5f505633;
|
||||
border: 1px dashed #5c474d;
|
||||
color: rgba(209, 14, 92, 0.5); */
|
||||
color: rgba(209, 14, 92, 0.5);
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
.draggable {
|
||||
top: 0;
|
||||
transition: transform 500ms ease;
|
||||
transition: transform 300ms ease;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
@ -17,6 +17,20 @@
|
|||
width: 195px;
|
||||
}
|
||||
|
||||
.user-title {
|
||||
padding: 6px 10px;
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.working-hours {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.date-time-picker {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ import { observer } from 'mobx-react';
|
|||
import Draggable from 'react-draggable';
|
||||
|
||||
import Modal from 'components/Modal/Modal';
|
||||
import ScheduleSlot from 'components/ScheduleSlot/ScheduleSlot';
|
||||
import Text from 'components/Text/Text';
|
||||
import UserGroups from 'components/UserGroups/UserGroups';
|
||||
import { Item } from 'components/UserGroups/UserGroups.types';
|
||||
import WithConfirm from 'components/WithConfirm/WithConfirm';
|
||||
import WorkingHours from 'components/WorkingHours/WorkingHours';
|
||||
import RemoteSelect from 'containers/RemoteSelect/RemoteSelect';
|
||||
import { getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Rotation, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
|
|
@ -49,6 +52,7 @@ interface RotationFormProps {
|
|||
onCreate: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
shiftColor?: string;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -65,8 +69,11 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
layerPriority,
|
||||
shiftId,
|
||||
shiftMoment = dayjs().startOf('day').add(1, 'day'),
|
||||
shiftColor = '#3D71D9',
|
||||
} = props;
|
||||
|
||||
console.log('shiftColor', shiftColor);
|
||||
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const [repeatEveryValue, setRepeatEveryValue] = useState<number>(1);
|
||||
|
|
@ -109,6 +116,29 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
};
|
||||
};
|
||||
|
||||
const renderUser = (userPk: User['pk']) => {
|
||||
const name = store.userStore.items[userPk]?.username;
|
||||
const desc = store.userStore.items[userPk]?.timezone;
|
||||
const workingHours = store.userStore.items[userPk]?.working_hours;
|
||||
const timezone = store.userStore.items[userPk]?.timezone;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('user-title')}>
|
||||
<Text strong>{name}</Text> <Text type="primary">({desc})</Text>
|
||||
</div>
|
||||
<WorkingHours
|
||||
timezone={timezone}
|
||||
workingHours={workingHours}
|
||||
startMoment={dayjs(params.shift_start)}
|
||||
duration={dayjs(params.shift_end).diff(dayjs(params.shift_start), 'seconds')}
|
||||
className={cx('working-hours')}
|
||||
style={{ backgroundColor: shiftColor }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const handleDeleteClick = useCallback(() => {
|
||||
store.scheduleStore.deleteOncallShift(shiftId).then(() => {
|
||||
onDelete();
|
||||
|
|
@ -238,7 +268,13 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
|||
<IconButton variant="secondary" className={cx('drag-handler')} name="draggabledots" />
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
<UserGroups value={userGroups} onChange={setUserGroups} isMultipleGroups={true} getItemData={getUser} />
|
||||
<UserGroups
|
||||
value={userGroups}
|
||||
onChange={setUserGroups}
|
||||
isMultipleGroups={true}
|
||||
getItemData={getUser}
|
||||
renderUser={renderUser}
|
||||
/>
|
||||
{/*<hr />*/}
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ 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 WorkingHours from 'components/WorkingHours/WorkingHours';
|
||||
import { getFromString } from 'models/schedule/schedule.helpers';
|
||||
import { Rotation, Schedule, Shift } from 'models/schedule/schedule.types';
|
||||
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
|
||||
|
|
@ -40,6 +41,8 @@ interface RotationFormProps {
|
|||
startMoment: dayjs.Dayjs;
|
||||
currentTimezone: Timezone;
|
||||
scheduleId: Schedule['id'];
|
||||
shiftMoment: dayjs.Dayjs;
|
||||
shiftColor?: string;
|
||||
onCreate: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
|
|
@ -58,6 +61,7 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
|
|||
shiftId,
|
||||
startMoment,
|
||||
shiftMoment = dayjs().startOf('day').add(1, 'day'),
|
||||
shiftColor = '#C69B06',
|
||||
} = props;
|
||||
|
||||
const store = useStore();
|
||||
|
|
@ -92,6 +96,29 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
|
|||
};
|
||||
};
|
||||
|
||||
const renderUser = (userPk: User['pk']) => {
|
||||
const name = store.userStore.items[userPk]?.username;
|
||||
const desc = store.userStore.items[userPk]?.timezone;
|
||||
const workingHours = store.userStore.items[userPk]?.working_hours;
|
||||
const timezone = store.userStore.items[userPk]?.timezone;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('user-title')}>
|
||||
<Text strong>{name}</Text> <Text type="primary">({desc})</Text>
|
||||
</div>
|
||||
<WorkingHours
|
||||
timezone={timezone}
|
||||
workingHours={workingHours}
|
||||
startMoment={dayjs(params.shift_start)}
|
||||
duration={dayjs(params.shift_end).diff(dayjs(params.shift_start), 'seconds')}
|
||||
className={cx('working-hours')}
|
||||
style={{ backgroundColor: shiftColor }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const shift = store.scheduleStore.shifts[shiftId];
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -183,7 +210,13 @@ const ScheduleOverrideForm: FC<RotationFormProps> = (props) => {
|
|||
<IconButton variant="secondary" className={cx('drag-handler')} name="draggabledots" />
|
||||
</HorizontalGroup>
|
||||
</HorizontalGroup>
|
||||
<UserGroups value={userGroups} onChange={setUserGroups} isMultipleGroups={false} getItemData={getUser} />
|
||||
<UserGroups
|
||||
value={userGroups}
|
||||
onChange={setUserGroups}
|
||||
isMultipleGroups={false}
|
||||
getItemData={getUser}
|
||||
renderUser={renderUser}
|
||||
/>
|
||||
{/*<hr />*/}
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { getColor, getOverrideColor } from 'models/schedule/schedule.helpers';
|
||||
|
||||
export const findColor = (shiftId, layers, overrides) => {
|
||||
export const findColor = (shiftId, layers, overrides?) => {
|
||||
let color = undefined;
|
||||
|
||||
const layerIndex = layers ? layers.findIndex((layer) => layer.shifts.some((shift) => shift.shiftId === shiftId)) : -1;
|
||||
|
|
@ -10,7 +10,7 @@ export const findColor = (shiftId, layers, overrides) => {
|
|||
|
||||
if (layerIndex > -1 && rotationIndex > -1) {
|
||||
color = getColor(layerIndex, rotationIndex);
|
||||
} else {
|
||||
} else if (overrides) {
|
||||
const overrideIndex = overrides ? overrides.findIndex((shift) => shift.shiftId === shiftId) : -1;
|
||||
|
||||
if (overrideIndex > -1) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { Timezone } from 'models/timezone/timezone.types';
|
|||
import { SelectOption, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
||||
import { findColor } from './Rotations.helpers';
|
||||
|
||||
import styles from './Rotations.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -93,11 +95,11 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
<div className={cx('layer-title')}>
|
||||
<HorizontalGroup spacing="sm" justify="center">
|
||||
<span>Layer {layer.priority}</span>
|
||||
<Icon name="info-circle" />
|
||||
{/*<Icon name="info-circle" />*/}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<div className={cx('rotations')}>
|
||||
<TimelineMarks debug startMoment={startMoment} />
|
||||
<TimelineMarks startMoment={startMoment} />
|
||||
{!currentTimeHidden && (
|
||||
<div className={cx('current-time')} style={{ left: `${currentTimeX * 100}%` }} />
|
||||
)}
|
||||
|
|
@ -127,12 +129,12 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
<div className={cx('layer-title')}>
|
||||
<HorizontalGroup spacing="sm" justify="center">
|
||||
<span>Layer 1</span>
|
||||
<Icon name="info-circle" />
|
||||
{/* <Icon name="info-circle" />*/}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<div className={cx('header-plus-content')}>
|
||||
<div className={cx('current-time')} style={{ left: `${currentTimeX * 100}%` }} />
|
||||
<TimelineMarks debug startMoment={startMoment} />
|
||||
<TimelineMarks startMoment={startMoment} />
|
||||
<div className={cx('rotations')}>
|
||||
<Rotation
|
||||
onClick={(moment) => {
|
||||
|
|
@ -164,6 +166,7 @@ class Rotations extends Component<RotationsProps, RotationsState> {
|
|||
{shiftIdToShowRotationForm && (
|
||||
<RotationForm
|
||||
shiftId={shiftIdToShowRotationForm}
|
||||
shiftColor={findColor(shiftIdToShowRotationForm, layers)}
|
||||
scheduleId={scheduleId}
|
||||
layerPriority={layerPriority}
|
||||
startMoment={startMoment}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import { Timezone } from 'models/timezone/timezone.types';
|
|||
import { WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
|
||||
import { findColor } from './Rotations.helpers';
|
||||
|
||||
import styles from './Rotations.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -104,6 +106,7 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
|
|||
{shiftIdToShowOverrideForm && (
|
||||
<ScheduleOverrideForm
|
||||
shiftId={shiftIdToShowOverrideForm}
|
||||
shiftColor={findColor(shiftIdToShowOverrideForm, undefined, shifts)}
|
||||
scheduleId={scheduleId}
|
||||
startMoment={startMoment}
|
||||
currentTimezone={currentTimezone}
|
||||
|
|
|
|||
|
|
@ -56,4 +56,5 @@ export interface User {
|
|||
cloud_connection_status?: number;
|
||||
hidden_fields?: boolean;
|
||||
timezone: Timezone;
|
||||
working_hours: { [key: string]: [] };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue