Skip to content

Commit 9db4d6b

Browse files
Leshe4kaLeshe4kagermanosinHaarolean
authored
FE: Customize user timezone (#1173)
Co-authored-by: Leshe4ka <[email protected]> Co-authored-by: German Osin <[email protected]> Co-authored-by: Roman Zabaluev <[email protected]>
1 parent d26e026 commit 9db4d6b

File tree

22 files changed

+551
-59
lines changed

22 files changed

+551
-59
lines changed

frontend/src/components/ConsumerGroups/Details/ResetOffsets/Form.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import useAppParams from 'lib/hooks/useAppParams';
2323
import { useResetConsumerGroupOffsetsMutation } from 'lib/hooks/api/consumers';
2424
import { FlexFieldset, StyledForm } from 'components/common/Form/Form.styled';
2525
import ControlledSelect from 'components/common/Select/ControlledSelect';
26+
import { useTimezone } from 'lib/hooks/useTimezones';
2627

2728
import * as S from './ResetOffsets.styled';
2829

@@ -38,6 +39,7 @@ const resetTypeOptions = Object.values(ConsumerGroupOffsetsResetType).map(
3839

3940
const Form: React.FC<FormProps> = ({ defaultValues, partitions, topics }) => {
4041
const navigate = useNavigate();
42+
const { getDateInCurrentTimezone } = useTimezone();
4143
const routerParams = useAppParams<ClusterGroupParam>();
4244
const reset = useResetConsumerGroupOffsetsMutation(routerParams);
4345
const topicOptions = React.useMemo(
@@ -142,8 +144,12 @@ const Form: React.FC<FormProps> = ({ defaultValues, partitions, topics }) => {
142144
render={({ field: { onChange, onBlur, value, ref } }) => (
143145
<S.DatePickerInput
144146
ref={ref}
145-
selected={new Date(value as number)}
146-
onChange={(e: Date | null) => onChange(e?.getTime())}
147+
selected={getDateInCurrentTimezone(
148+
new Date(value as number)
149+
)}
150+
onChange={(selectedDate: Date | null) => {
151+
onChange(selectedDate?.getTime());
152+
}}
147153
onBlur={onBlur}
148154
/>
149155
)}

frontend/src/components/NavBar/NavBar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ProductHuntIcon from 'components/common/Icons/ProductHuntIcon';
1212
import { Button } from 'components/common/Button/Button';
1313
import MenuIcon from 'components/common/Icons/MenuIcon';
1414

15+
import { UserTimezone } from './UserTimezone/UserTimezone';
1516
import UserInfo from './UserInfo/UserInfo';
1617
import * as S from './NavBar.styled';
1718

@@ -73,6 +74,8 @@ const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
7374
</S.NavbarBrand>
7475
</S.NavbarBrand>
7576
<S.NavbarSocial>
77+
<UserTimezone />
78+
7679
<Select
7780
options={options}
7881
value={themeMode}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import styled from 'styled-components';
2+
3+
export const SelectedTimezoneContainer = styled.div`
4+
display: flex;
5+
justify-content: center;
6+
align-items: center;
7+
gap: 4px;
8+
`;
9+
10+
export const ContentContainer = styled.div`
11+
display: flex;
12+
width: 320px;
13+
flex-direction: column;
14+
max-height: 640px;
15+
gap: 8px;
16+
`;
17+
18+
export const InputContainer = styled.div`
19+
position: sticky;
20+
top: 0;
21+
background-color: white;
22+
z-index: 1;
23+
`;
24+
25+
export const ItemsContainer = styled.div`
26+
overflow-y: auto;
27+
`;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Button } from 'components/common/Button/Button';
2+
import ChevronDownIcon from 'components/common/Icons/ChevronDownIcon';
3+
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
4+
import React, { useMemo, useState } from 'react';
5+
import Input from 'components/common/Input/Input';
6+
import { TIMEZONES, useTimezone } from 'lib/hooks/useTimezones';
7+
8+
import * as S from './UserTimezone.styled';
9+
10+
export const UserTimezone = () => {
11+
const { currentTimezone, availableTimezones, setTimezone } = useTimezone();
12+
13+
const [searchValue, setSearchValue] = useState('');
14+
15+
const filteredTimezones = useMemo(() => {
16+
if (!searchValue.trim()) return availableTimezones;
17+
18+
const searchLower = searchValue.toLowerCase();
19+
return TIMEZONES.filter(
20+
(timezone) =>
21+
timezone.value.toLowerCase().includes(searchLower) ||
22+
timezone.offset.toLowerCase().includes(searchLower)
23+
);
24+
}, [searchValue]);
25+
26+
const handleTimezoneSelect = (timezone: typeof currentTimezone) => {
27+
setTimezone(timezone);
28+
setSearchValue('');
29+
};
30+
31+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
32+
setSearchValue(e.target.value);
33+
};
34+
35+
return (
36+
<Dropdown
37+
onClose={() => setSearchValue('')}
38+
align="center"
39+
aria-label="user-timezone-dropdown"
40+
openBtnEl={
41+
<Button buttonType="text" buttonSize="L">
42+
<S.SelectedTimezoneContainer>
43+
<p>{currentTimezone.UTCOffset}</p>
44+
<ChevronDownIcon fill="currentColor" width="16" height="16" />
45+
</S.SelectedTimezoneContainer>
46+
</Button>
47+
}
48+
>
49+
<S.ContentContainer>
50+
<S.InputContainer>
51+
<Input
52+
id="user-timezone-search"
53+
type="text"
54+
placeholder="Search timezone..."
55+
value={searchValue}
56+
onChange={handleSearchChange}
57+
inputSize="M"
58+
search
59+
/>
60+
</S.InputContainer>
61+
62+
<S.ItemsContainer>
63+
{filteredTimezones.map((timezone) => (
64+
<DropdownItem
65+
key={timezone.value}
66+
onClick={() => handleTimezoneSelect(timezone)}
67+
>
68+
{timezone.label}
69+
</DropdownItem>
70+
))}
71+
</S.ItemsContainer>
72+
</S.ContentContainer>
73+
</Dropdown>
74+
);
75+
};

frontend/src/components/Topics/Topic/Messages/Message.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { JSONPath } from 'jsonpath-plus';
99
import Ellipsis from 'components/common/Ellipsis/Ellipsis';
1010
import WarningRedIcon from 'components/common/Icons/WarningRedIcon';
1111
import Tooltip from 'components/common/Tooltip/Tooltip';
12+
import { useTimezone } from 'lib/hooks/useTimezones';
1213

1314
import MessageContent from './MessageContent/MessageContent';
1415
import * as S from './MessageContent/MessageContent.styled';
@@ -41,6 +42,7 @@ const Message: React.FC<Props> = ({
4142
keyFilters,
4243
contentFilters,
4344
}) => {
45+
const { currentTimezone } = useTimezone();
4446
const [isOpen, setIsOpen] = React.useState(false);
4547
const savedMessageJson = {
4648
Value: value,
@@ -107,7 +109,9 @@ const Message: React.FC<Props> = ({
107109
<td>{offset}</td>
108110
<td>{partition}</td>
109111
<td>
110-
<div>{formatTimestamp(timestamp)}</div>
112+
<div>
113+
{formatTimestamp({ timestamp, timezone: currentTimezone.value })}
114+
</div>
111115
</td>
112116
<S.DataCell title={key}>
113117
<Ellipsis text={renderFilteredJson(key, keyFilters)}>

frontend/src/components/Topics/Topic/Messages/MessageContent/MessageContent.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import EditorViewer from 'components/common/EditorViewer/EditorViewer';
33
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
44
import { SchemaType, TopicMessageTimestampTypeEnum } from 'generated-sources';
55
import { formatTimestamp } from 'lib/dateTimeHelpers';
6+
import { useTimezone } from 'lib/hooks/useTimezones';
67

78
import * as S from './MessageContent.styled';
89

@@ -31,6 +32,8 @@ const MessageContent: React.FC<MessageContentProps> = ({
3132
keySerde,
3233
valueSerde,
3334
}) => {
35+
const { currentTimezone } = useTimezone();
36+
3437
const [activeTab, setActiveTab] = React.useState<Tab>('content');
3538
const activeTabContent = () => {
3639
switch (activeTab) {
@@ -103,7 +106,12 @@ const MessageContent: React.FC<MessageContentProps> = ({
103106
<S.Metadata>
104107
<S.MetadataLabel>Timestamp</S.MetadataLabel>
105108
<span>
106-
<S.MetadataValue>{formatTimestamp(timestamp)}</S.MetadataValue>
109+
<S.MetadataValue>
110+
{formatTimestamp({
111+
timestamp,
112+
timezone: currentTimezone.value,
113+
})}
114+
</S.MetadataValue>
107115
<S.MetadataMeta>Timestamp type: {timestampType}</S.MetadataMeta>
108116
</span>
109117
</S.Metadata>

frontend/src/components/Topics/Topic/Messages/__test__/Message.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe('Message component', () => {
6565
expect(screen.getByText(mockMessage.value as string)).toBeInTheDocument();
6666
expect(screen.getByText(mockMessage.key as string)).toBeInTheDocument();
6767
expect(
68-
screen.getByText(formatTimestamp(mockMessage.timestamp))
68+
screen.getByText(formatTimestamp({ timestamp: mockMessage.timestamp }))
6969
).toBeInTheDocument();
7070
expect(screen.getByText(mockMessage.offset.toString())).toBeInTheDocument();
7171
expect(

frontend/src/components/Topics/Topic/Statistics/Indicators/Total.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import * as Metrics from 'components/common/Metrics';
33
import { TopicAnalysisStats } from 'generated-sources';
44
import { formatTimestamp } from 'lib/dateTimeHelpers';
5+
import { useTimezone } from 'lib/hooks/useTimezones';
56

67
const Total: React.FC<TopicAnalysisStats> = ({
78
totalMsgs,
@@ -14,14 +15,16 @@ const Total: React.FC<TopicAnalysisStats> = ({
1415
approxUniqKeys,
1516
approxUniqValues,
1617
}) => {
18+
const { currentTimezone } = useTimezone();
19+
1720
return (
1821
<Metrics.Section title="Messages">
1922
<Metrics.Indicator label="Total number">{totalMsgs}</Metrics.Indicator>
2023
<Metrics.Indicator label="Offsets min-max">
2124
{`${minOffset} - ${maxOffset}`}
2225
</Metrics.Indicator>
2326
<Metrics.Indicator label="Timestamp min-max">
24-
{`${formatTimestamp(minTimestamp)} - ${formatTimestamp(maxTimestamp)}`}
27+
{`${formatTimestamp({ timestamp: minTimestamp, timezone: currentTimezone.value })} - ${formatTimestamp({ timestamp: maxTimestamp, timezone: currentTimezone.value })}`}
2528
</Metrics.Indicator>
2629
<Metrics.Indicator label="Null keys">{nullKeys}</Metrics.Indicator>
2730
<Metrics.Indicator

frontend/src/components/Topics/Topic/Statistics/Metrics.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
1616
import { calculateTimer, formatTimestamp } from 'lib/dateTimeHelpers';
1717
import { Action, ResourceType } from 'generated-sources';
1818
import { ActionButton } from 'components/common/ActionComponent';
19+
import { useTimezone } from 'lib/hooks/useTimezones';
1920

2021
import * as S from './Statistics.styles';
2122
import Total from './Indicators/Total';
@@ -25,6 +26,7 @@ import { LabelValue } from './Statistics.styles';
2526

2627
const Metrics: React.FC = () => {
2728
const params = useAppParams<RouteParamsClusterTopic>();
29+
const { currentTimezone } = useTimezone();
2830

2931
const [isAnalyzing, setIsAnalyzing] = useState(true);
3032
const analyzeTopic = useAnalyzeTopic(params);
@@ -69,10 +71,14 @@ const Metrics: React.FC = () => {
6971
<List>
7072
<Label>Started at</Label>
7173
<LabelValue>
72-
{formatTimestamp(data.progress.startedAt, {
73-
hour: 'numeric',
74-
minute: 'numeric',
75-
second: 'numeric',
74+
{formatTimestamp({
75+
timestamp: data.progress.startedAt,
76+
format: {
77+
hour: 'numeric',
78+
minute: 'numeric',
79+
second: 'numeric',
80+
},
81+
timezone: currentTimezone.value,
7682
})}
7783
</LabelValue>
7884
<Label>Passed since start</Label>
@@ -100,7 +106,12 @@ const Metrics: React.FC = () => {
100106
return (
101107
<>
102108
<S.ActionsBar>
103-
<S.CreatedAt>{formatTimestamp(data?.result?.finishedAt)}</S.CreatedAt>
109+
<S.CreatedAt>
110+
{formatTimestamp({
111+
timezone: currentTimezone.value,
112+
timestamp: data?.result?.finishedAt,
113+
})}
114+
</S.CreatedAt>
104115
<ActionButton
105116
onClick={async () => {
106117
await analyzeTopic.mutateAsync();

frontend/src/components/Topics/Topic/Statistics/PartitionInfoRow.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'components/common/PropertiesList/PropertiesList.styled';
99
import { TopicAnalysisStats } from 'generated-sources';
1010
import { formatTimestamp } from 'lib/dateTimeHelpers';
11+
import { useTimezone } from 'lib/hooks/useTimezones';
1112

1213
import * as S from './Statistics.styles';
1314

@@ -25,6 +26,9 @@ const PartitionInfoRow: React.FC<{ row: Row<TopicAnalysisStats> }> = ({
2526
keySize,
2627
valueSize,
2728
} = row.original;
29+
30+
const { currentTimezone } = useTimezone();
31+
2832
return (
2933
<S.PartitionInfo>
3034
<div>
@@ -35,9 +39,19 @@ const PartitionInfoRow: React.FC<{ row: Row<TopicAnalysisStats> }> = ({
3539
<Label>Total size</Label>
3640
<BytesFormatted value={(keySize?.sum || 0) + (valueSize?.sum || 0)} />
3741
<Label>Min. timestamp</Label>
38-
<span>{formatTimestamp(minTimestamp)}</span>
42+
<span>
43+
{formatTimestamp({
44+
timestamp: minTimestamp,
45+
timezone: currentTimezone.value,
46+
})}
47+
</span>
3948
<Label>Max. timestamp</Label>
40-
<span>{formatTimestamp(maxTimestamp)}</span>
49+
<span>
50+
{formatTimestamp({
51+
timestamp: maxTimestamp,
52+
timezone: currentTimezone.value,
53+
})}
54+
</span>
4155
<Label>Null keys amount</Label>
4256
<span>{nullKeys}</span>
4357
<Label>Null values amount</Label>

0 commit comments

Comments
 (0)