Skip to content

Commit 116d8fa

Browse files
authored
Merge pull request #791 from buerokratt/v3.3.1
merging v3.3.1 to test
2 parents 65eaa30 + c9dcd98 commit 116d8fa

17 files changed

Lines changed: 410 additions & 82 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- liquibase formatted sql
2+
-- changeset 1AhmedYasser:1785269433
3+
CREATE TYPE overview_date_preference_unit AS ENUM (
4+
'day',
5+
'week',
6+
'month',
7+
'period'
8+
);
9+
10+
CREATE TABLE public."user_overview_date_preference" (
11+
id int8 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
12+
user_id_code text NOT NULL,
13+
unit overview_date_preference_unit NOT NULL DEFAULT 'week',
14+
created_at timestamp NOT NULL DEFAULT now(),
15+
CONSTRAINT user_overview_date_preference_pkey PRIMARY KEY (id),
16+
CONSTRAINT user_overview_date_preference_user_id_code_key UNIQUE (user_id_code)
17+
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SELECT unit
2+
FROM user_overview_date_preference
3+
WHERE user_id_code = :user_id_code;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
INSERT INTO user_overview_date_preference (user_id_code, unit)
2+
VALUES (:user_id_code, :unit)
3+
ON CONFLICT (user_id_code)
4+
DO UPDATE SET
5+
unit = EXCLUDED.unit
6+
RETURNING user_id_code, unit;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
declaration:
2+
call: declare
3+
version: 0.1
4+
description: "Decription placeholder for 'DATE-PREFERENCE'"
5+
method: get
6+
accepts: json
7+
returns: json
8+
namespace: analytics
9+
allowlist:
10+
header:
11+
- field: cookie
12+
type: string
13+
description: "Cookie field"
14+
15+
check_if_authorized:
16+
template: "[#ANALYTICS_PROJECT_LAYER]/check-user-authority"
17+
requestType: templates
18+
headers:
19+
cookie: ${incoming.headers.cookie}
20+
result: authority_result
21+
22+
get_date_preference:
23+
call: http.post
24+
args:
25+
url: "[#ANALYTICS_RESQL]/select-user-overview-date-preference-unit"
26+
body:
27+
user_id_code: ${authority_result.idCode}
28+
result: preference
29+
30+
return_value:
31+
return: ${preference.response.body}
32+
next: end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
declaration:
2+
call: declare
3+
version: 0.1
4+
description: "Decription placeholder for 'DATE-PREFERENCE'"
5+
method: post
6+
accepts: json
7+
returns: json
8+
namespace: analytics
9+
allowlist:
10+
body:
11+
- field: unit
12+
type: string
13+
description: "Body field 'unit'"
14+
header:
15+
- field: cookie
16+
type: string
17+
description: "Cookie field"
18+
19+
check_for_required_parameters:
20+
switch:
21+
- condition: ${incoming.body == null || incoming.body.unit == null}
22+
next: return_incorrect_request
23+
next: extract_request_data
24+
25+
extract_request_data:
26+
assign:
27+
cookie: ${incoming.headers.cookie}
28+
unit: ${incoming.body.unit}
29+
next: check_if_authorized
30+
31+
check_if_authorized:
32+
template: "[#ANALYTICS_PROJECT_LAYER]/check-user-authority"
33+
requestType: templates
34+
headers:
35+
cookie: ${cookie}
36+
result: authority_result
37+
38+
update_date_preference:
39+
call: http.post
40+
args:
41+
url: "[#ANALYTICS_RESQL]/update-date-preference"
42+
body:
43+
user_id_code: ${authority_result.idCode}
44+
unit: ${unit}
45+
result: result
46+
next: return_value
47+
48+
return_value:
49+
return: ${result.response.body}
50+
next: end
51+
52+
return_incorrect_request:
53+
status: 400
54+
return: 'missing parameters'
55+
next: end

‎GUI/src/components/FormElements/FormDatepicker/index.tsx‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ import i18next from 'i18next';
1313
registerLocale('et-EE', et);
1414

1515
type FormDatepickerProps = ControllerRenderProps & {
16-
label: string;
17-
name: string;
18-
hideLabel?: boolean;
19-
disabled?: boolean;
20-
placeholder?: string;
21-
timePicker?: boolean;
22-
monthPicker?: boolean;
23-
portalId?: string;
24-
trailingIcon?: ReactNode;
16+
readonly label: string;
17+
readonly name: string;
18+
readonly hideLabel?: boolean;
19+
readonly disabled?: boolean;
20+
readonly placeholder?: string;
21+
readonly timePicker?: boolean;
22+
readonly monthPicker?: boolean;
23+
readonly portalId?: string;
24+
readonly trailingIcon?: ReactNode;
25+
readonly minDate?: Date;
26+
readonly maxDate?: Date;
2527
}
2628

2729
const FormDatepicker = forwardRef<any, FormDatepickerProps>((

‎GUI/src/components/overview/OverviewBarChart/index.tsx‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import React, { useEffect, useRef, useState } from 'react';
22
import { Bar, BarChart, CartesianGrid, LabelList, Tooltip, XAxis, YAxis } from 'recharts';
33
import { useTranslation } from 'react-i18next';
4+
import { endOfWeek } from 'date-fns';
5+
import { MdFlag } from 'react-icons/md';
6+
import Icon from '../../Icon';
7+
import TooltipWrapper from '../../TooltipWrapper';
48
import { formatDate, getDistributionYAxisTicks } from '../../../util/charts-utils';
5-
import { DateRange, formatOverviewDate, OverviewUnit } from '../../../util/overview-date-utils';
9+
import { DateRange, formatOverviewDate, isLongPeriod, OverviewUnit, WEEK_OPTIONS } from '../../../util/overview-date-utils';
610
import { OVERVIEW_AXIS_STROKE, OVERVIEW_BLUE, OVERVIEW_GREEN, OVERVIEW_TICK_FILL } from '../../../util/overview-colors';
711
import { createStackedBarShape } from './barShapes';
812
import { useOverviewChartData } from './useOverviewChartData';
@@ -22,9 +26,10 @@ type Props = {
2226

2327
const OverviewBarChart = ({ range, unit }: Props) => {
2428
const { t } = useTranslation();
25-
const buckets = useOverviewChartData(range, unit);
29+
const { buckets, period } = useOverviewChartData(range, unit);
2630
const ref = useRef<HTMLDivElement>(null);
2731
const [width, setWidth] = useState<number>(0);
32+
const isHourly = period === 'hour';
2833

2934
useEffect(() => {
3035
const handleResize = () => setWidth(ref.current?.clientWidth ?? 0);
@@ -37,7 +42,17 @@ const OverviewBarChart = ({ range, unit }: Props) => {
3742
const yAxisMax = roundUpToTen(maxTotal);
3843
const yAxisTicks = getDistributionYAxisTicks(yAxisMax);
3944

40-
const tickFormatter = (value: number) => formatDate(new Date(value), unit === 'day' ? 'HH:mm' : 'dd.MM');
45+
const tickFormatter = (value: number) => formatDate(new Date(value), isHourly ? 'HH:mm' : 'dd.MM');
46+
47+
const tooltipLabelFormatter = (value: number) => {
48+
const date = new Date(value);
49+
if (isHourly) return formatDate(date, 'HH:mm');
50+
if (period === 'week') {
51+
const weekEnd = endOfWeek(date, WEEK_OPTIONS);
52+
return `${formatDate(date, 'dd.MM.yyyy')} - ${formatDate(weekEnd, 'dd.MM.yyyy')}`;
53+
}
54+
return formatDate(date, 'dd-MM-yyyy');
55+
};
4156

4257
const totalCount = buckets.reduce((sum, bucket) => sum + bucket.burokratt + bucket.csa, 0);
4358

@@ -50,7 +65,16 @@ const OverviewBarChart = ({ range, unit }: Props) => {
5065
return (
5166
<div ref={ref} className="overview-bar-chart">
5267
<div className="overview-bar-chart__header">
53-
<h2 className="overview-bar-chart__title">{title}</h2>
68+
<h2 className="overview-bar-chart__title">
69+
{title}
70+
{isLongPeriod(range) && (
71+
<span className="overview-bar-chart__title-warning">
72+
<TooltipWrapper enabled text={t('overview.longPeriodWarning')}>
73+
<Icon icon={<MdFlag />} size="small" />
74+
</TooltipWrapper>
75+
</span>
76+
)}
77+
</h2>
5478
<div className="overview-bar-chart__legend">
5579
{LEGEND_ITEMS.map(({ key, color }) => (
5680
<div key={key} className="overview-bar-chart__legend-item">
@@ -60,7 +84,7 @@ const OverviewBarChart = ({ range, unit }: Props) => {
6084
))}
6185
</div>
6286
</div>
63-
<BarChart width={width} height={width / 3.76} data={buckets} barSize={unit === 'day' ? 8 : 20} margin={{ top: 20, right: 20, bottom: 30 }}>
87+
<BarChart width={width} height={width / 3.76} data={buckets} barSize={isHourly ? 8 : 20} margin={{ top: 20, right: 20, bottom: 30 }}>
6488
<CartesianGrid
6589
vertical={false}
6690
horizontalValues={yAxisTicks.filter((tick) => tick > 0)}
@@ -73,7 +97,7 @@ const OverviewBarChart = ({ range, unit }: Props) => {
7397
type="number"
7498
domain={['dataMin', 'dataMax']}
7599
scale="time"
76-
padding={{ left: unit === 'day' ? 8 : 14, right: unit === 'day' ? 8 : 14 }}
100+
padding={{ left: isHourly ? 8 : 14, right: isHourly ? 8 : 14 }}
77101
axisLine={{ stroke: OVERVIEW_AXIS_STROKE }}
78102
tickLine={false}
79103
tick={{ fill: OVERVIEW_TICK_FILL, fontSize: 12 }}
@@ -88,7 +112,7 @@ const OverviewBarChart = ({ range, unit }: Props) => {
88112
/>
89113
<Tooltip
90114
cursor={{ fill: 'rgba(151, 153, 164, 0.12)' }}
91-
labelFormatter={(value) => formatDate(new Date(value as number), unit === 'day' ? 'HH:mm' : 'dd-MM-yyyy')}
115+
labelFormatter={(value) => tooltipLabelFormatter(value as number)}
92116
/>
93117
<Bar dataKey="burokratt" stackId="total" fill={OVERVIEW_BLUE} shape={createStackedBarShape('burokratt')} />
94118
<Bar dataKey="csa" stackId="total" fill={OVERVIEW_GREEN} shape={createStackedBarShape('csa')}>

‎GUI/src/components/overview/OverviewBarChart/styles.scss‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@
1818
}
1919

2020
&__title {
21+
display: inline-flex;
22+
align-items: center;
23+
gap: 6px;
2124
margin: 0;
2225
color: get-color(black-coral-6);
2326
font-size: $veera-font-size-100;
2427
font-weight: 400;
2528
text-transform: uppercase;
2629
}
2730

31+
&__title-warning {
32+
display: inline-flex;
33+
color: get-color(dark-tangerine-10);
34+
}
35+
2836
&__legend {
2937
display: flex;
3038
flex-shrink: 0;

‎GUI/src/components/overview/OverviewBarChart/useOverviewChartData.ts‎

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
11
import { useEffect, useState } from 'react';
2-
import { eachDayOfInterval, eachHourOfInterval, format, formatISO } from 'date-fns';
2+
import { eachDayOfInterval, eachHourOfInterval, eachWeekOfInterval, format, formatISO, startOfWeek } from 'date-fns';
33
import { Methods, request } from '../../../util/axios-client';
44
import { getDomainsArray } from '../../../util/multiDomain-utils';
55
import { getShowTestData } from '../../../util/testChat-utils';
66
import { getTotalChats } from '../../../resources/api-constants';
7-
import { DateRange, OverviewUnit } from '../../../util/overview-date-utils';
8-
import { OverviewChartRequestData, TotalChatsOverviewResponse } from '../../../types/overview-api';
7+
import { DateRange, isWeeklyBucketPeriod, OverviewUnit, WEEK_OPTIONS } from '../../../util/overview-date-utils';
8+
import { OverviewChartPeriod, OverviewChartRequestData, TotalChatsOverviewResponse } from '../../../types/overview-api';
99

1010
export type OverviewChartBucket = {
1111
readonly bucket: number;
1212
readonly burokratt: number;
1313
readonly csa: number;
1414
};
1515

16-
const bucketKey = (date: string | Date, period: 'hour' | 'day'): string | null => {
16+
const bucketKey = (date: string | Date, period: OverviewChartPeriod): string | null => {
1717
const parsed = new Date(date);
1818
if (Number.isNaN(parsed.getTime())) return null;
19-
return format(parsed, period === 'hour' ? "yyyy-MM-dd'T'HH" : 'yyyy-MM-dd');
19+
if (period === 'hour') return format(parsed, "yyyy-MM-dd'T'HH");
20+
if (period === 'week') return format(startOfWeek(parsed, WEEK_OPTIONS), 'yyyy-MM-dd');
21+
return format(parsed, 'yyyy-MM-dd');
2022
};
2123

2224
const toBucketEntries = (
2325
rows: Record<string, unknown>[],
2426
dateField: string,
25-
period: 'hour' | 'day'
27+
period: OverviewChartPeriod
2628
): [string, number][] =>
2729
rows.reduce<[string, number][]>((entries, row) => {
2830
const key = bucketKey(row[dateField] as string | Date, period);
2931
if (key !== null) entries.push([key, Number(row.count)]);
3032
return entries;
3133
}, []);
3234

35+
const getChartPeriod = (range: DateRange, unit: OverviewUnit): OverviewChartPeriod => {
36+
if (unit === 'day') return 'hour';
37+
return isWeeklyBucketPeriod(range) ? 'week' : 'day';
38+
};
39+
40+
const getIntervals = (range: DateRange, period: OverviewChartPeriod): Date[] => {
41+
if (period === 'hour') return eachHourOfInterval({ start: range.start, end: range.end });
42+
if (period === 'week') return eachWeekOfInterval({ start: range.start, end: range.end }, WEEK_OPTIONS);
43+
return eachDayOfInterval({ start: range.start, end: range.end });
44+
};
45+
3346
export const useOverviewChartData = (range: DateRange, unit: OverviewUnit) => {
3447
const [buckets, setBuckets] = useState<OverviewChartBucket[]>([]);
48+
const period = getChartPeriod(range, unit);
3549

3650
useEffect(() => {
3751
let cancelled = false;
38-
const period = unit === 'day' ? 'hour' : 'day';
3952
const urls = getDomainsArray();
4053
const showTest = getShowTestData();
4154
const start_date = formatISO(range.start);
@@ -56,10 +69,7 @@ export const useOverviewChartData = (range: DateRange, unit: OverviewUnit) => {
5669
const bykMap = new Map(toBucketEntries([...bykRows], 'time', period));
5770
const csaMap = new Map(toBucketEntries([...csaRows], 'time', period));
5871

59-
const intervals =
60-
period === 'hour'
61-
? eachHourOfInterval({ start: range.start, end: range.end })
62-
: eachDayOfInterval({ start: range.start, end: range.end });
72+
const intervals = getIntervals(range, period);
6373

6474
const newBuckets = intervals.map((date) => {
6575
const key = bucketKey(date, period) ?? '';
@@ -76,7 +86,7 @@ export const useOverviewChartData = (range: DateRange, unit: OverviewUnit) => {
7686
return () => {
7787
cancelled = true;
7888
};
79-
}, [range.start.getTime(), range.end.getTime(), unit]);
89+
}, [range.start.getTime(), range.end.getTime(), unit, period]);
8090

81-
return buckets;
91+
return { buckets, period };
8292
};

0 commit comments

Comments
 (0)