mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-18 11:14:57 +02:00
Adds date localization to the charts on the analytics page. In doing so, I have extracted the default locales that we allow the user to set into constants, so that we can reference it from other places. I have also sorted the list and added my personal favorite format (ja) to it. Because we have multiple charts on the analytics page, it felt weird that only one chart should follow your preferred format. It also aligns the existing charts' tooltip date format with the new one (`P` instead of `PPP`). In short: previously, the charts would show you only your system locale (I think), which for me defaults to en-US, regardless of what setting you'd set in your profile. Now we respect your setting as long as it's one of the default ones. Before (date formatting is en-US): <img width="1444" height="1658" alt="image" src="https://github.com/user-attachments/assets/99a893c7-efb6-4e55-b47c-9df66bf97636" /> After (date formatting is sv-SE): <img width="1383" height="1653" alt="image" src="https://github.com/user-attachments/assets/d408afd9-a8a7-46f3-8c13-9f7fde608cc4" />
276 lines
10 KiB
TypeScript
276 lines
10 KiB
TypeScript
import { useEffect, useId, useState } from 'react';
|
|
import {
|
|
Box,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
type SelectChangeEvent,
|
|
styled,
|
|
Tooltip,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import { Badge } from 'component/common/Badge/Badge';
|
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
|
import { useProfile } from 'hooks/api/getters/useProfile/useProfile';
|
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
|
import type { IUser } from 'interfaces/user';
|
|
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
|
|
import { Link } from 'react-router-dom';
|
|
import { PageContent } from 'component/common/PageContent/PageContent';
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
import { RoleBadge } from 'component/common/RoleBadge/RoleBadge';
|
|
import { useUiFlag } from 'hooks/useUiFlag';
|
|
import { ProductivityEmailSubscription } from './ProductivityEmailSubscription.tsx';
|
|
import { formatDateYMDHM } from 'utils/formatDate.ts';
|
|
import { defaultLocales } from '../../../../constants/defaultLocales.ts';
|
|
|
|
const StyledHeader = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
padding: theme.spacing(6),
|
|
borderRadius: theme.shape.borderRadiusLarge,
|
|
backgroundColor: theme.palette.background.alternative,
|
|
color: theme.palette.primary.contrastText,
|
|
marginBottom: theme.spacing(3),
|
|
boxShadow: theme.boxShadows.primaryHeader,
|
|
}));
|
|
|
|
const StyledInfo = styled('div')(() => ({
|
|
flexGrow: 1,
|
|
}));
|
|
|
|
const StyledInfoName = styled(Typography)(({ theme }) => ({
|
|
fontSize: theme.spacing(3.75),
|
|
}));
|
|
|
|
const StyledAvatar = styled(UserAvatar)(({ theme }) => ({
|
|
width: theme.spacing(9.5),
|
|
height: theme.spacing(9.5),
|
|
marginRight: theme.spacing(3),
|
|
}));
|
|
|
|
const StyledSectionLabel = styled(Typography)(({ theme }) => ({
|
|
fontSize: theme.fontSizes.mainHeader,
|
|
marginBottom: theme.spacing(3),
|
|
}));
|
|
|
|
const StyledAccess = styled('div')(({ theme }) => ({
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
gap: theme.spacing(2),
|
|
'& > div > p': {
|
|
marginBottom: theme.spacing(1.5),
|
|
},
|
|
}));
|
|
|
|
const StyledBadgeLink = styled(Link)(({ theme }) => ({
|
|
':hover,:focus-visible': {
|
|
outline: 'none',
|
|
'> *': {
|
|
outline: `2px solid ${theme.palette.primary.main}`,
|
|
},
|
|
},
|
|
}));
|
|
|
|
const StyledDivider = styled('div')(({ theme }) => ({
|
|
width: '100%',
|
|
height: '1px',
|
|
backgroundColor: theme.palette.divider,
|
|
margin: theme.spacing(3, 0),
|
|
}));
|
|
|
|
const StyledFormControl = styled(FormControl)(({ theme }) => ({
|
|
width: theme.spacing(30),
|
|
}));
|
|
|
|
const StyledInputLabel = styled(InputLabel)(({ theme }) => ({
|
|
backgroundColor: theme.palette.background.paper,
|
|
}));
|
|
|
|
interface IProfileTabProps {
|
|
user: IUser;
|
|
}
|
|
|
|
const ProjectList = styled('ul')(({ theme }) => ({
|
|
listStyle: 'none',
|
|
padding: 0,
|
|
display: 'flex',
|
|
flexFlow: 'row wrap',
|
|
gap: theme.spacing(1),
|
|
}));
|
|
|
|
const exampleDateString = '2014-09-29T14:50:46';
|
|
const exampleDate = new Date(exampleDateString);
|
|
|
|
const LocaleSelector = styled('div')(({ theme }) => ({
|
|
marginTop: theme.spacing(1.5),
|
|
display: 'flex',
|
|
flexFlow: 'row wrap',
|
|
gap: theme.spacing(1),
|
|
alignItems: 'center',
|
|
}));
|
|
|
|
export const ProfileTab = ({ user }: IProfileTabProps) => {
|
|
const { profile, refetchProfile } = useProfile();
|
|
const { locationSettings, setLocationSettings } = useLocationSettings();
|
|
const [currentLocale, setCurrentLocale] = useState<string>();
|
|
const exampleDateId = useId();
|
|
|
|
const [possibleLocales, setPossibleLocales] = useState<string[]>([
|
|
...defaultLocales,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
const found = possibleLocales.find((locale) =>
|
|
locale
|
|
.toLowerCase()
|
|
.includes(locationSettings.locale.toLowerCase()),
|
|
);
|
|
setCurrentLocale(found);
|
|
if (!found) {
|
|
setPossibleLocales((prev) => [...prev, locationSettings.locale]);
|
|
}
|
|
}, [locationSettings]);
|
|
|
|
const changeLocale = (e: SelectChangeEvent) => {
|
|
const locale = e.target.value;
|
|
setCurrentLocale(locale);
|
|
setLocationSettings({ locale });
|
|
};
|
|
|
|
const productivityReportEmailEnabled = useUiFlag('productivityReportEmail');
|
|
|
|
return (
|
|
<>
|
|
<StyledHeader>
|
|
<StyledAvatar user={user} />
|
|
<StyledInfo>
|
|
<StyledInfoName>
|
|
{user.name || user.email || user.username}
|
|
</StyledInfoName>
|
|
<Typography variant='body1'>{user.email}</Typography>
|
|
</StyledInfo>
|
|
</StyledHeader>
|
|
<PageContent>
|
|
<StyledSectionLabel>Access</StyledSectionLabel>
|
|
<StyledAccess>
|
|
<Box sx={{ width: '50%' }}>
|
|
<ConditionallyRender
|
|
condition={Boolean(profile?.rootRole)}
|
|
show={() => (
|
|
<>
|
|
<Typography variant='body2'>
|
|
Your root role
|
|
</Typography>
|
|
<RoleBadge roleId={profile?.rootRole.id!}>
|
|
{profile?.rootRole.name}
|
|
</RoleBadge>
|
|
</>
|
|
)}
|
|
/>
|
|
</Box>
|
|
<Box>
|
|
<Typography variant='body2'>Projects</Typography>
|
|
<ConditionallyRender
|
|
condition={Boolean(profile?.projects.length)}
|
|
show={
|
|
<ProjectList>
|
|
{profile?.projects.map((project) => (
|
|
<li key={project}>
|
|
<Tooltip
|
|
title='View project'
|
|
arrow
|
|
placement='bottom-end'
|
|
describeChild
|
|
>
|
|
<StyledBadgeLink
|
|
to={`/projects/${project}`}
|
|
>
|
|
<Badge
|
|
color='secondary'
|
|
icon={
|
|
<TopicOutlinedIcon />
|
|
}
|
|
>
|
|
{project}
|
|
</Badge>
|
|
</StyledBadgeLink>
|
|
</Tooltip>
|
|
</li>
|
|
))}
|
|
</ProjectList>
|
|
}
|
|
elseShow={
|
|
<Tooltip
|
|
title='You are not assigned to any projects'
|
|
arrow
|
|
describeChild
|
|
>
|
|
<Badge tabIndex={0}>No projects</Badge>
|
|
</Tooltip>
|
|
}
|
|
/>
|
|
</Box>
|
|
</StyledAccess>
|
|
<StyledDivider />
|
|
<StyledSectionLabel>Date/Time Settings</StyledSectionLabel>
|
|
<Typography variant='body2'>
|
|
This is the format used across the system for time and date
|
|
</Typography>
|
|
<LocaleSelector>
|
|
<StyledFormControl variant='outlined' size='small'>
|
|
<StyledInputLabel htmlFor='locale-select'>
|
|
Date/Time formatting
|
|
</StyledInputLabel>
|
|
<Select
|
|
aria-details={exampleDateId}
|
|
id='locale-select'
|
|
value={currentLocale || ''}
|
|
native
|
|
onChange={changeLocale}
|
|
MenuProps={{
|
|
style: {
|
|
zIndex: 9999,
|
|
},
|
|
}}
|
|
>
|
|
{possibleLocales.map((locale) => {
|
|
return (
|
|
<option key={locale} value={locale}>
|
|
{locale}
|
|
</option>
|
|
);
|
|
})}
|
|
</Select>
|
|
</StyledFormControl>
|
|
<Typography id={exampleDateId}>
|
|
Example:{' '}
|
|
<time dateTime={exampleDateString}>
|
|
{formatDateYMDHM(exampleDate, currentLocale)}
|
|
</time>
|
|
</Typography>
|
|
</LocaleSelector>
|
|
{productivityReportEmailEnabled ? (
|
|
<>
|
|
<StyledDivider />
|
|
<StyledSectionLabel>Email Settings</StyledSectionLabel>
|
|
{profile?.subscriptions && (
|
|
<ProductivityEmailSubscription
|
|
status={
|
|
profile.subscriptions.includes(
|
|
'productivity-report',
|
|
)
|
|
? 'subscribed'
|
|
: 'unsubscribed'
|
|
}
|
|
onChange={refetchProfile}
|
|
/>
|
|
)}
|
|
</>
|
|
) : null}
|
|
</PageContent>
|
|
</>
|
|
);
|
|
};
|