Skip to content

Commit b36928c

Browse files
committed
Refactor statistics sections to use generic data
The StatisticsGeneralSection and StatisticsPlayerSection components were refactored to use a generic data object instead of specific types. This allows for more flexibility in the data that can be displayed in the statistics sections. The formatMs function was renamed to formatMsToHoursMins and a new formatMs function was created to format milliseconds into hours and minutes.
1 parent 5d50049 commit b36928c

5 files changed

Lines changed: 130 additions & 137 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "igropolius",
33
"private": true,
4-
"version": "1.0.194",
4+
"version": "1.0.195",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src/components/core/endGameStatistics/components/StatisticsGeneralSection.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ type Props = {
99
};
1010

1111
function StatisticsGeneralSection({ data }: Props) {
12-
const keyToProps = (key: keyof FinalStatsResponse): Omit<StatisticsCardProps, 'value'> | null => {
13-
switch (key) {
12+
const keyToProps = (key: string): Omit<StatisticsCardProps, 'value'> | null => {
13+
const dataKey = key as keyof FinalStatsResponse;
14+
switch (dataKey) {
1415
case 'total_score':
1516
return {
1617
text: `Всего очков\nзаработано`,
1718
icon: <Share className="size-[26px]" />,
18-
modifiedValue: Math.round(data[key]),
19+
modifiedValue: Math.round(data[dataKey]),
1920
order: 1,
2021
};
2122
case 'hours_spent_on_games':
2223
return {
2324
text: `Наиграли в игры\nстримеры`,
24-
modifiedValue: `${Math.round(data[key])}ч`,
25+
modifiedValue: `${Math.round(data[dataKey])}ч`,
2526
order: 4,
2627
};
2728
case 'average_rating_of_completed_games':
2829
return {
2930
text: `Средняя оценка\nпройденных игр`,
30-
modifiedValue: `${data[key]} / 10`,
31+
modifiedValue: `${data[dataKey]} / 10`,
3132
order: 11,
3233
};
3334
case 'completed_games':
Lines changed: 106 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,118 @@
1-
import { PlayerFinalStats } from "@/lib/api-types-generated";
2-
import { FALLBACK_AVATAR_URL } from "@/lib/constants";
3-
import { playersData } from "@/lib/mockData";
4-
import { StatisticsCardProps } from "./StatisticsCard";
5-
import { formatMs } from "@/lib/utils";
6-
import { Badge } from "@/components/ui/badge";
7-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
8-
import StatisticsHeading from "./StatisticsHeading";
9-
import StatisticsRows from "./StatisticsRows";
10-
import { Skeleton } from "@/components/ui/skeleton";
11-
import StatisticsGameReview from "./StatisticsGameReview";
12-
import { Share } from "@/components/icons";
1+
import { PlayerFinalStats } from '@/lib/api-types-generated';
2+
import { FALLBACK_AVATAR_URL } from '@/lib/constants';
3+
import { playersData } from '@/lib/mockData';
4+
import { StatisticsCardProps } from './StatisticsCard';
5+
import { formatMs, formatMsToHoursMins } from '@/lib/utils';
6+
import { Badge } from '@/components/ui/badge';
7+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
8+
import StatisticsHeading from './StatisticsHeading';
9+
import StatisticsRows from './StatisticsRows';
10+
import { Skeleton } from '@/components/ui/skeleton';
11+
import StatisticsGameReview from './StatisticsGameReview';
12+
import { Share } from '@/components/icons';
1313

1414
type PlayerSectionProps = {
15-
data: PlayerFinalStats & {
16-
placement: number;
17-
monopolies_amount: number;
18-
buildings_amount: number;
19-
};
20-
}
15+
data: PlayerFinalStats & {
16+
placement: number;
17+
monopolies_amount: number;
18+
buildings_amount: number;
19+
};
20+
};
2121

2222
function StatisticsPlayerSection({ data }: PlayerSectionProps) {
23-
const color = playersData.find((p) => p.id === data.player_id)?.color || 'white';
24-
const avatar = playersData.find((p) => p.id === data.player_id)?.avatar_link || FALLBACK_AVATAR_URL;
25-
const filteredData = {
26-
total_score: data.total_score,
27-
games_completed: data.games_completed,
28-
games_dropped: data.games_dropped,
29-
longest_game_hours: data.longest_game_hours,
30-
shortest_game_hours: data.shortest_game_hours,
31-
cards_amount: data.cards_amount,
32-
hours_played: data.hours_played,
33-
buildings_amount: data.buildings_amount,
34-
monopolies_amount: data.monopolies_amount,
35-
};
23+
const color = playersData.find(p => p.id === data.player_id)?.color || 'white';
24+
const avatar = playersData.find(p => p.id === data.player_id)?.avatar_link || FALLBACK_AVATAR_URL;
3625

37-
const keyToProps = (key: string): Omit<StatisticsCardProps, 'value'> => {
38-
switch (key) {
39-
case 'total_score': {
40-
const totalScoreText =
41-
data.placement === 1
42-
? 'Очков — самый\nбогатый'
43-
: data.placement === playersData.length
44-
? 'Очков — \nБанкрот!'
45-
: 'Очков\nполучено';
46-
return {
47-
text: totalScoreText,
48-
icon: <Share className="size-[26px]" />,
49-
order: 1,
50-
};
51-
}
52-
case 'longest_game_hours':
53-
return {
54-
text: `Самая\nдлинная игра`,
55-
modifiedValue: formatMs(data[key] * 1000),
56-
order: 4,
57-
};
58-
case 'shortest_game_hours':
59-
return {
60-
text: `Самая\nкороткая игра`,
61-
modifiedValue: formatMs(data[key] * 1000),
62-
order: 9,
63-
};
64-
case 'hours_played':
65-
return {
66-
text: `Наиграно в игры\nна ивенте`,
67-
modifiedValue: formatMs(data[key] * 1000),
68-
order: 6,
69-
};
70-
case 'games_completed':
71-
return { text: `Игр\nпройдено`, order: 3, };
72-
case 'games_dropped':
73-
return { text: `Игр\nдропнуто`, order: 7, };
74-
case 'cards_amount':
75-
return { text: `Карточек\nзаролено`, order: 5, };
76-
case 'buildings_amount':
77-
return { text: `Зданий\nна карте`, order: 2, };
78-
case 'monopolies_amount':
79-
return { text: `Монополий\nпостроено`, order: 8, };
80-
default:
81-
return { text: '', order: 0, };
82-
}
83-
};
26+
const keyToProps = (key: string): Omit<StatisticsCardProps, 'value'> | null => {
27+
const dataKey = key as keyof PlayerSectionProps['data'];
28+
switch (dataKey) {
29+
case 'total_score': {
30+
const totalScoreText =
31+
data.placement === 1
32+
? 'Очков — самый\nбогатый'
33+
: data.placement === playersData.length
34+
? 'Очков — \nБанкрот!'
35+
: 'Очков\nполучено';
36+
return {
37+
text: totalScoreText,
38+
icon: <Share className="size-[26px]" />,
39+
order: 1,
40+
};
41+
}
42+
case 'longest_game_hours':
43+
return {
44+
text: `Самая\nдлинная игра`,
45+
modifiedValue: formatMsToHoursMins(data[dataKey] * 1000),
46+
order: 4,
47+
};
48+
case 'shortest_game_hours':
49+
return {
50+
text: `Самая\nкороткая игра`,
51+
modifiedValue: formatMsToHoursMins(data[dataKey] * 1000),
52+
order: 9,
53+
};
54+
case 'hours_played':
55+
return {
56+
text: `Наиграно в игры\nна ивенте`,
57+
modifiedValue: formatMs(data[dataKey] * 1000),
58+
order: 6,
59+
};
60+
case 'games_completed':
61+
return { text: `Игр\nпройдено`, order: 3 };
62+
case 'games_dropped':
63+
return { text: `Игр\nдропнуто`, order: 7 };
64+
case 'cards_amount':
65+
return { text: `Карточек\nзаролено`, order: 5 };
66+
case 'buildings_amount':
67+
return { text: `Зданий\nна карте`, order: 2 };
68+
case 'monopolies_amount':
69+
return { text: `Монополий\nпостроено`, order: 8 };
70+
default:
71+
return null;
72+
}
73+
};
8474

85-
return (
86-
<div className="space-y-[30px]">
87-
<div className="space-y-[15px]">
88-
<Badge
89-
className="flex py-1 uppercase font-roboto-wide-semibold-italic leading-[19px] border-0 mx-auto"
90-
style={{ backgroundColor: color }}
91-
>
92-
{data.placement} место
93-
</Badge>
94-
<div className="flex gap-2 justify-center items-center">
95-
<div className="relative">
96-
<Avatar className="size-[56px] overflow-auto">
97-
<AvatarImage src={avatar} />
98-
<AvatarFallback className="uppercase">{data.username.slice(0, 2)}</AvatarFallback>
99-
</Avatar>
100-
</div>
101-
<StatisticsHeading>
102-
{data.username}
103-
</StatisticsHeading>
104-
</div>
105-
</div>
75+
return (
76+
<div className="space-y-[30px]">
77+
<div className="space-y-[15px]">
78+
<Badge
79+
className="flex py-1 uppercase font-roboto-wide-semibold-italic leading-[19px] border-0 mx-auto"
80+
style={{ backgroundColor: color }}
81+
>
82+
{data.placement} место
83+
</Badge>
84+
<div className="flex gap-2 justify-center items-center">
85+
<div className="relative">
86+
<Avatar className="size-[56px] overflow-auto">
87+
<AvatarImage src={avatar} />
88+
<AvatarFallback className="uppercase">{data.username.slice(0, 2)}</AvatarFallback>
89+
</Avatar>
90+
</div>
91+
<StatisticsHeading>{data.username}</StatisticsHeading>
92+
</div>
93+
</div>
10694

107-
<StatisticsRows data={filteredData} keyToProps={keyToProps} />
95+
<StatisticsRows data={data} keyToProps={keyToProps} />
10896

109-
<div className="space-y-[15px]">
110-
<div className="text-center font-roboto-wide-black-alt text-2xl leading-7">Лучший клип</div>
111-
<div className="w-[500px] h-[281px] shrink-0 mx-auto">
112-
<Skeleton className="w-full h-full" />
113-
</div>
114-
</div>
97+
<div className="space-y-[15px]">
98+
<div className="text-center font-roboto-wide-black-alt text-2xl leading-7">Лучший клип</div>
99+
<div className="w-[500px] h-[281px] shrink-0 mx-auto">
100+
<Skeleton className="w-full h-full" />
101+
</div>
102+
</div>
115103

116-
{(data.best_rated_game && data.worst_rated_game) && (
117-
<div className="flex gap-[15px] justify-center">
118-
{data.best_rated_game && (
119-
<StatisticsGameReview data={data.best_rated_game} title="Лучшая игра" />
120-
)}
121-
{data.worst_rated_game && (
122-
<StatisticsGameReview data={data.worst_rated_game} title="Худшая игра" />
123-
)}
124-
</div>
125-
)}
126-
</div>
127-
)
104+
{data.best_rated_game && data.worst_rated_game && (
105+
<div className="flex gap-[15px] justify-center">
106+
{data.best_rated_game && (
107+
<StatisticsGameReview data={data.best_rated_game} title="Лучшая игра" />
108+
)}
109+
{data.worst_rated_game && (
110+
<StatisticsGameReview data={data.worst_rated_game} title="Худшая игра" />
111+
)}
112+
</div>
113+
)}
114+
</div>
115+
);
128116
}
129117

130118
export default StatisticsPlayerSection;

src/components/core/endGameStatistics/components/StatisticsRows.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
1-
import { FinalStatsResponse } from '@/lib/api-types-generated';
21
import StatisticsCard, { StatisticsCardProps } from './StatisticsCard';
32

4-
type KeyToPropsType = (key: keyof FinalStatsResponse) => Omit<StatisticsCardProps, 'value'> | null;
3+
type KeyToPropsType = (key: string) => Omit<StatisticsCardProps, 'value'> | null;
54

6-
function StatisticsRows({
7-
data,
8-
keyToProps,
9-
}: {
10-
data: FinalStatsResponse;
11-
keyToProps: KeyToPropsType;
12-
}) {
5+
function StatisticsRows({ data, keyToProps }: { data: object; keyToProps: KeyToPropsType }) {
136
const dataEntries = Object.entries(data)
147
.map(([key, value]) => {
15-
const params = keyToProps(key as keyof FinalStatsResponse);
8+
const params = keyToProps(key);
169
if (!params) {
1710
return null;
1811
}

src/lib/utils.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,18 @@ export function formatMs(diffMs: number) {
501501
return `${hours}ч ${minutes}м`;
502502
}
503503

504+
export function formatMsToHoursMins(diffMs: number) {
505+
const diffS = Math.floor(diffMs / 1000);
506+
const hours = Math.floor(diffS / (60 * 60));
507+
const minutes = Math.floor((diffS % (60 * 60)) / 60);
508+
509+
if (hours === 0) {
510+
return `${minutes}м`;
511+
}
512+
513+
return `${hours}ч ${minutes}м`;
514+
}
515+
504516
export function formatHltbLength(seconds: number) {
505517
const hours = Math.floor(seconds / 3600);
506518
const minutes = Math.floor((seconds % 3600) / 60);
@@ -744,8 +756,7 @@ export function getNoun(num: number, words: string[]) {
744756
}
745757

746758
export function createPortionsRounded(amount: number, min: number, max: number) {
747-
if (amount <= 1)
748-
throw new Error('Amount must be greater than 1');
759+
if (amount <= 1) throw new Error('Amount must be greater than 1');
749760

750761
const result: number[] = [];
751762
const step = (max - min) / (amount - 1);
@@ -754,6 +765,6 @@ export function createPortionsRounded(amount: number, min: number, max: number)
754765
result.push(Math.floor(min + step * i));
755766
}
756767

757-
console.log('function')
768+
console.log('function');
758769
return result;
759770
}

0 commit comments

Comments
 (0)