color scheme for avatar
This commit is contained in:
parent
63d2fbf715
commit
d7eb7107b7
5 changed files with 99 additions and 83 deletions
|
|
@ -64,9 +64,6 @@ class ScheduleFinal extends Component<ScheduleFinalProps, ScheduleOverridesState
|
|||
: store.scheduleStore.events[scheduleId]?.['override']?.[getFromString(startMoment)];
|
||||
const currentTimeHidden = currentTimeX < 0 || currentTimeX > 1;
|
||||
|
||||
/* console.log('shifts', toJS(shifts));
|
||||
console.log('layers', toJS(layers)); */
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cx('root')}>
|
||||
|
|
|
|||
|
|
@ -20,12 +20,6 @@ export const getRandomUser = () => {
|
|||
|
||||
export const getTitle = (user: User) => {
|
||||
return user ? user.username.split(' ')[0] : null;
|
||||
return user
|
||||
? user.username
|
||||
.split(' ')
|
||||
.map((word) => word.charAt(0).toUpperCase())
|
||||
.join('')
|
||||
: null;
|
||||
};
|
||||
|
||||
export const getOuRanges = (shift: Shift, user: User) => {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import cn from 'classnames/bind';
|
||||
import React from 'react';
|
||||
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import styles from './ColorfulUserCircle.module.scss';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -18,23 +19,33 @@ export default function ColorfulUserCircle({
|
|||
renderAvatar: () => JSX.Element;
|
||||
renderIcon: () => JSX.Element;
|
||||
}) {
|
||||
if (!colors?.length) return null;
|
||||
return <div className={cx('root')}>{colors?.length ? renderSVG() : renderAvatarIcon()}</div>;
|
||||
|
||||
return (
|
||||
<div className={cx('root')}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width={width} height={height} viewBox="-10 -10 220 220">
|
||||
<g fill="none" stroke-width="15" transform="translate(100,100)">
|
||||
{renderColorPaths(colors)}
|
||||
</g>
|
||||
</svg>
|
||||
<div className={cx('avatar')}>{renderAvatar()}</div>
|
||||
<div className={cx('icon')}>{renderIcon()}</div>
|
||||
</div>
|
||||
);
|
||||
function renderAvatarIcon() {
|
||||
return (
|
||||
<>
|
||||
<div className={cx('avatar')}>{renderAvatar()}</div>
|
||||
<div className={cx('icon')}>{renderIcon()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSVG() {
|
||||
return (
|
||||
<>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width={width} height={height} viewBox="-10 -10 220 220">
|
||||
<g fill="none" strokeWidth="15" transform="translate(100,100)">
|
||||
{renderColorPaths(colors)}
|
||||
</g>
|
||||
</svg>
|
||||
{renderAvatarIcon()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderColorPaths(colors: string[]) {
|
||||
const colorSchemeList = colors;
|
||||
if (colors.length === 1) colorSchemeList.push(colors[0]);
|
||||
if (colors.length === 1) {colorSchemeList.push(colors[0]);}
|
||||
|
||||
const stepAngle = (2 * Math.PI) / colors.length;
|
||||
const radius = 100;
|
||||
|
|
|
|||
|
|
@ -3,23 +3,36 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { HorizontalGroup, InlineSwitch, Tooltip } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import dayjs from 'dayjs';
|
||||
import { toJS } from 'mobx';
|
||||
import moment from 'moment';
|
||||
|
||||
import Avatar from 'components/Avatar/Avatar';
|
||||
import ScheduleUserDetails from 'components/ScheduleUserDetails/ScheduleUserDetails';
|
||||
import Text from 'components/Text/Text';
|
||||
import { findColor } from 'containers/Rotations/Rotations.helpers';
|
||||
import { IsOncallIcon } from 'icons';
|
||||
import { getColor, getFromString, getOverrideColor } from 'models/schedule/schedule.helpers';
|
||||
|
||||
import { Event, Layer } from 'models/schedule/schedule.types';
|
||||
import { Timezone } from 'models/timezone/timezone.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { getStartOfWeek } from 'pages/schedule/Schedule.helpers';
|
||||
import { RootStore } from 'state';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './UsersTimezones.module.css';
|
||||
import ColorfulUserCircle from './ColorfulUserCircle';
|
||||
|
||||
import styles from './UsersTimezones.module.css';
|
||||
|
||||
|
||||
|
||||
interface UsersTimezonesProps {
|
||||
userIds: Array<User['pk']>;
|
||||
tz: Timezone;
|
||||
onTzChange: (tz: Timezone) => void;
|
||||
onCallNow: Array<Partial<User>>;
|
||||
scheduleId: string;
|
||||
|
||||
onTzChange: (tz: Timezone) => void;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
|
@ -29,7 +42,7 @@ const hoursToSplit = 3;
|
|||
const jLimit = 24 / hoursToSplit;
|
||||
|
||||
const UsersTimezones: FC<UsersTimezonesProps> = (props) => {
|
||||
const { userIds, tz, onTzChange, onCallNow } = props;
|
||||
const { userIds, tz, onTzChange, onCallNow, scheduleId } = props;
|
||||
|
||||
const store = useStore();
|
||||
|
||||
|
|
@ -87,7 +100,13 @@ const UsersTimezones: FC<UsersTimezonesProps> = (props) => {
|
|||
</div>
|
||||
<div className={cx('users')}>
|
||||
<div className={cx('current-time')} style={{ left: `${currentTimeX}%` }} />
|
||||
<UserAvatars users={users} onCallNow={onCallNow} onTzChange={onTzChange} currentMoment={currentMoment} />
|
||||
<UserAvatars
|
||||
users={users}
|
||||
onCallNow={onCallNow}
|
||||
onTzChange={onTzChange}
|
||||
currentMoment={currentMoment}
|
||||
scheduleId={scheduleId}
|
||||
/>
|
||||
</div>
|
||||
<div className={cx('time-stripe')}>
|
||||
<div className={cx('current-user-stripe')} />
|
||||
|
|
@ -115,12 +134,13 @@ const UsersTimezones: FC<UsersTimezonesProps> = (props) => {
|
|||
interface UserAvatarsProps {
|
||||
users: User[];
|
||||
currentMoment: dayjs.Dayjs;
|
||||
scheduleId: string;
|
||||
onTzChange: (timezone: Timezone) => void;
|
||||
onCallNow: Array<Partial<User>>;
|
||||
}
|
||||
|
||||
const UserAvatars = (props: UserAvatarsProps) => {
|
||||
const { users, currentMoment, onTzChange, onCallNow } = props;
|
||||
const { users, currentMoment, onTzChange, onCallNow, scheduleId } = props;
|
||||
const userGroups = useMemo(() => {
|
||||
return users
|
||||
.reduce((memo, user) => {
|
||||
|
|
@ -165,6 +185,7 @@ const UserAvatars = (props: UserAvatarsProps) => {
|
|||
xPos={xPos}
|
||||
users={group.users}
|
||||
currentMoment={currentMoment}
|
||||
scheduleId={scheduleId}
|
||||
onCallNow={onCallNow}
|
||||
/>
|
||||
);
|
||||
|
|
@ -178,6 +199,7 @@ interface AvatarGroupProps {
|
|||
xPos: number;
|
||||
currentMoment: dayjs.Dayjs;
|
||||
utcOffset: number;
|
||||
scheduleId: string;
|
||||
onSetActiveUtcOffset: (utcOffset: number | undefined) => void;
|
||||
activeUtcOffset: number;
|
||||
onTzChange: (timezone: Timezone) => void;
|
||||
|
|
@ -198,8 +220,11 @@ const AvatarGroup = (props: AvatarGroupProps) => {
|
|||
onSetActiveUtcOffset,
|
||||
activeUtcOffset,
|
||||
onCallNow,
|
||||
scheduleId,
|
||||
} = props;
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const active = !isNaN(activeUtcOffset) && activeUtcOffset === utcOffset;
|
||||
|
||||
const translateLeft = -AVATAR_WIDTH / 2;
|
||||
|
|
@ -226,6 +251,7 @@ const AvatarGroup = (props: AvatarGroupProps) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const colorSchemeMapping = getColorSchemeMappingForUsers(store, scheduleId);
|
||||
const width = active ? users.length * AVATAR_WIDTH + (users.length - 1) * AVATAR_GAP : AVATAR_WIDTH;
|
||||
|
||||
return (
|
||||
|
|
@ -239,6 +265,7 @@ const AvatarGroup = (props: AvatarGroupProps) => {
|
|||
>
|
||||
{users.map((user, index, array) => {
|
||||
const isOncall = onCallNow.some((onCallUser) => user.pk === onCallUser.pk);
|
||||
const colorSchemeList = colorSchemeMapping[user.pk] ? Array.from(colorSchemeMapping[user.pk]) : [];
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
|
@ -254,12 +281,11 @@ const AvatarGroup = (props: AvatarGroupProps) => {
|
|||
opacity: active ? 1 : Math.max(1 - index * 0.25, 0.25),
|
||||
visibility: !active && index >= LIMIT ? 'hidden' : 'visible',
|
||||
zIndex: array.length - index - 1,
|
||||
/* opacity: userHour >= 9 && userHour < 18 ? 1 : 0.5,*/
|
||||
}}
|
||||
onClick={getAvatarClickHandler(user.timezone)}
|
||||
>
|
||||
<ColorfulUserCircle
|
||||
colors={['red']}
|
||||
colors={colorSchemeList}
|
||||
width={32}
|
||||
height={32}
|
||||
renderAvatar={() => <Avatar src={user.avatar} size="large" />}
|
||||
|
|
@ -283,4 +309,43 @@ const AvatarGroup = (props: AvatarGroupProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
function getColorSchemeMappingForUsers(store: RootStore, scheduleId: string): { [userId: string]: Set<string> } {
|
||||
const startMoment = getStartOfWeek(store.currentTimezone);
|
||||
|
||||
const shifts: Array<{ shiftId: string; events: Event[] }> = false
|
||||
? store.scheduleStore.finalPreview
|
||||
: (store.scheduleStore.events[scheduleId]?.['final']?.[getFromString(startMoment)] as any);
|
||||
|
||||
const layers = store.scheduleStore.rotationPreview
|
||||
? 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 usersColorSchemeHash: { [userId: string]: Set<string> } = {};
|
||||
|
||||
if (!shifts?.length || !layers?.length) {return usersColorSchemeHash;}
|
||||
|
||||
shifts.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId, false));
|
||||
shifts.forEach(({ events }, rotationIndex) => populateUserHashSet(events, rotationIndex, true));
|
||||
|
||||
return usersColorSchemeHash;
|
||||
|
||||
function populateUserHashSet(events: Event[], id: string | number, isOverride: boolean) {
|
||||
events.forEach((event) => {
|
||||
event.users.forEach((user) => {
|
||||
if (!usersColorSchemeHash[user.pk]) {
|
||||
usersColorSchemeHash[user.pk] = new Set<string>();
|
||||
}
|
||||
|
||||
usersColorSchemeHash[user.pk].add(
|
||||
isOverride ? getOverrideColor(id as number) : findColor(id as string, layers, overrides)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersTimezones;
|
||||
|
|
|
|||
|
|
@ -96,8 +96,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
|
||||
render() {
|
||||
const { store } = this.props;
|
||||
const { startMoment, schedulePeriodType, renderType, shiftIdToShowRotationForm, shiftIdToShowOverridesForm } =
|
||||
this.state;
|
||||
const { startMoment, shiftIdToShowRotationForm, shiftIdToShowOverridesForm } = this.state;
|
||||
const { query } = this.props;
|
||||
const { id: scheduleId } = query;
|
||||
|
||||
|
|
@ -119,26 +118,6 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
<Text.Title editable editModalTitle="Schedule name" level={3} onTextChange={this.handleNameChange}>
|
||||
{schedule?.name}
|
||||
</Text.Title>
|
||||
{/*<ScheduleCounter
|
||||
type="link"
|
||||
count={5}
|
||||
tooltipTitle="Used in escalations"
|
||||
tooltipContent={
|
||||
<>
|
||||
<PluginLink query={{ page: 'integrations', id: 'CXBEG63MBJMDL' }}>Grafana 1</PluginLink>
|
||||
<br />
|
||||
<PluginLink query={{ page: 'integrations', id: 'CXBEG63MBJMDL' }}>Grafana 2</PluginLink>
|
||||
<br />
|
||||
<PluginLink query={{ page: 'integrations', id: 'CXBEG63MBJMDL' }}>Grafana 3</PluginLink>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ScheduleCounter
|
||||
type="warning"
|
||||
count={2}
|
||||
tooltipTitle="Warnings"
|
||||
tooltipContent="Schedule has unassigned time periods during next 7 days"
|
||||
/>*/}
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup>
|
||||
{users && (
|
||||
|
|
@ -147,11 +126,6 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
<UserTimezoneSelect value={currentTimezone} users={users} onChange={this.handleTimezoneChange} />
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
{/*<ScheduleQuality quality={0.89} />*/}
|
||||
{/*<ToolbarButton icon="copy" tooltip="Copy" />
|
||||
<ToolbarButton icon="brackets-curly" tooltip="Code" />
|
||||
<ToolbarButton icon="share-alt" tooltip="Share" />
|
||||
<ToolbarButton icon="cog" tooltip="Settings" />*/}
|
||||
<WithConfirm>
|
||||
<ToolbarButton icon="trash-alt" tooltip="Delete" onClick={this.handleDelete} />
|
||||
</WithConfirm>
|
||||
|
|
@ -170,6 +144,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
}
|
||||
tz={currentTimezone}
|
||||
onTzChange={this.handleTimezoneChange}
|
||||
scheduleId={scheduleId}
|
||||
/>
|
||||
</div>
|
||||
<div className={cx('controls')}>
|
||||
|
|
@ -190,32 +165,6 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
|
||||
</div>
|
||||
</HorizontalGroup>
|
||||
{/*<HorizontalGroup width="auto">
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'Day', value: 'day' },
|
||||
{
|
||||
label: 'Week',
|
||||
value: 'week',
|
||||
},
|
||||
{ label: 'Month', value: 'month' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
]}
|
||||
value={schedulePeriodType}
|
||||
onChange={this.handleShedulePeriodTypeChange}
|
||||
/>
|
||||
<RadioButtonGroup
|
||||
options={[
|
||||
{ label: 'Timeline', value: 'timeline' },
|
||||
{
|
||||
label: 'Grid',
|
||||
value: 'grid',
|
||||
},
|
||||
]}
|
||||
value={renderType}
|
||||
onChange={this.handleRenderTypeChange}
|
||||
/>
|
||||
</HorizontalGroup>*/}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
{/* <div className={'current-time'} />*/}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue