paint users in user groups

This commit is contained in:
Maxim 2022-08-25 15:49:35 +03:00
parent d5750a495d
commit cb33155287
12 changed files with 145 additions and 41 deletions

View file

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

View file

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

View file

@ -1,11 +1,6 @@
export interface Item {
key: string;
type: string;
data: ItemData;
data: any;
item?: string;
}
export interface ItemData {
name: string;
desc?: string;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -56,4 +56,5 @@ export interface User {
cloud_connection_status?: number;
hidden_fields?: boolean;
timezone: Timezone;
working_hours: { [key: string]: [] };
}