index.tsx 18.6 KB
Newer Older
1
2
import { saveAs } from 'file-saver';
import Papa from 'papaparse';
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { child, off, onValue, ref, set } from 'firebase/database';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import pokerTable from '../../assets/images/poker_table.svg';
import Button from '../../components/Button';
import Carousel from '../../components/Carousel';
import Header from '../../components/Header';
import Qrcode from '../../components/Qrcode';
import ToolTip from '../../components/Tooltip';
import VoteCard from '../../components/VoteCard';
import VotingTriider from '../../components/VotingTriider';
import { useDelete } from '../../hooks/useDelete';
import { getRealtimeDatabase } from '../../services/firebase';
import { Session } from '../../services/firebaseSessions';
17
import Modal from '../../components/Modal';
Gabriel Bohn's avatar
Gabriel Bohn committed
18
import LogoOrange from '../../assets/triiderLogos/logoOrange.svg';
19
20
21
22
23
24

export default function Game() {
	const router = useNavigate();

	const [voteState, setVoteState] = useState('individual');
	const [cardSelected, setCardSelected] = useState('');
Gabriel Bohn's avatar
Gabriel Bohn committed
25
	const [sessionEnded, setSessionEnded] = useState(false);
26
27
28

	const { session_id } = useParams();
	const [session, setSession] = useState<Session | null>(null);
29

30
31
32
33
34
35
	const database = getRealtimeDatabase();

	const { request } = useDelete<void>();

	const refSession = ref(database, 'sessions/' + session_id);

36
	const [isOpenCancelModal, setIsOpenCancelModal] = useState(false);
37
	const [isOpenNoPlayersModal, setIsOpenNoPlayersModal] = useState(false);
38

39
40
41
	function sessionListener() {
		onValue(refSession, (snapshot) => {
			const session = snapshot.val();
Gabriel Bohn's avatar
Gabriel Bohn committed
42
			if (!session || session?.game.finished) {
43
44
45
				router('/');
				return;
			}
46
47
48
49
50
			setSession(session);
		});
	}

	async function closeListener(save: boolean) {
Gabriel Bohn's avatar
Gabriel Bohn committed
51
		off(refSession);
Gabriel Bohn's avatar
Gabriel Bohn committed
52
53
54
		if (save) {
			setSessionEnded(true);
		} else {
Gabriel Bohn's avatar
Gabriel Bohn committed
55
			handleDeleteSession(false);
Gabriel Bohn's avatar
Gabriel Bohn committed
56
57
			router('/');
		}
58
59
60
61
	}

	let usersList = Object.entries(session?.users || {});

62
63
64
	const votes = session?.votes || {};

	const taskVotes = votes[session?.game.current_task || '']?.votes || {};
65
66

	let min = Number.MAX_SAFE_INTEGER;
67
	const max = Math.max(...Object.values(taskVotes));
68
69
70
71

	let average = 0;
	let count = 0;

72
73
74
	for (const vote in taskVotes) {
		if (taskVotes[vote] === -1) continue;
		if (taskVotes[vote] < min) min = taskVotes[vote];
75

76
		average += taskVotes[vote];
77
78
79
80
81
82
		count++;
	}

	average /= count;

	let storieDificulty = 0;
83
	const storie = session?.game.stories[session.game.current_story];
84
85

	for (const task in storie?.tasks) {
86
		storieDificulty += votes[task]?.final_vote || 0;
87
88
89
90
91
92
93
94
95
96
97
98
99
	}

	useEffect(() => {
		sessionListener();
		if (session) {
			usersList = Object.entries(session?.users || {});
			ShowPlayers();
		}
	}, []);

	useEffect(() => {
		if (!session) return;

100
		const currentTask = votes[session.game.current_task];
101

102
		if (currentTask?.final_vote) {
103
104
			let nextTask = getNextTask();

105
			let nextStory: string | undefined = session.game.current_story;
106
107
108
109
110
111
112
113
114

			if (nextTask == null) {
				nextStory = getNextStory();

				if (nextStory == null) {
					closeListener(true);
					return;
				}

115
				nextTask = Object.keys(session.game.stories[nextStory].tasks)[0];
116
117
118
119
			}

			set(refSession, {
				...session,
120
121
122
123
124
				game: {
					...session.game,
					current_story: nextStory,
					current_task: nextTask,
				},
125
126
127
128
129
130
131
132
133
134
			}).then(() => {
				setVoteState('individual');
				setCardSelected('');
			});
		}
	}, [session]);

	function getNextTask() {
		if (!session) return;

135
		const tasks = session.game.stories[session.game.current_story].tasks;
136
137

		for (const task in tasks) {
138
			if (votes[task]?.final_vote) continue;
139
140
141
142
143
144
145
146
147
148
149
150
151

			return task;
		}

		return null;
	}

	async function voteLater() {
		if (!session) return;

		const nextTask = getNextTask();

		if (nextTask) {
152
			await set(child(refSession, 'game/current_task'), nextTask);
153
154
155
156
157
		}
	}

	function getNextStory() {
		if (!session) return;
158
		const stories = session.game.stories;
159
160
161
162
163

		for (const story in stories) {
			let allVoted = true;

			for (const task in stories[story].tasks) {
164
				if (!votes[task]?.final_vote) {
165
166
167
168
169
170
171
172
173
174
175
176
177
178
					allVoted = false;
					break;
				}
			}

			if (allVoted) continue;

			return story;
		}
	}

	async function handleVote() {
		if (cardSelected === '' || !session) return;

179
		await set(child(refSession, `votes/${session.game.current_task}/final_vote`), +cardSelected);
180
181
	}

182
	const isToShow = voteState === 'group';
183
184
185
186
187

	function ShowPlayers() {
		return (
			<>
				<div>
188
					<div className="absolute top-[345px] left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-10">
189
190
191
						<VotingTriider
							isToShow={isToShow}
							type="card"
192
							number={usersList.length > 0 ? taskVotes[usersList[0][0] ?? ''] || 0 : 0}
193
194
195
196
197
198
199
							name={
								usersList.length > 0
									? usersList[0][1].firebase_auth_id
										? usersList[0][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
200
							position="bottom"
201
202
203
204
205
206
						/>
					</div>
					<div className="absolute -bottom-40 left-32 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
207
							number={usersList.length > 1 ? taskVotes[usersList[1][0] ?? ''] || 0 : 0}
208
209
210
211
212
213
214
							name={
								usersList.length > 1
									? usersList[1][1].firebase_auth_id
										? usersList[1][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
215
							position="bottom"
216
217
218
219
220
221
						/>
					</div>
					<div className="absolute -bottom-28 -left-10 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
222
							number={usersList.length > 2 ? taskVotes[usersList[2][0] ?? ''] || 0 : 0}
223
224
225
226
227
228
229
							name={
								usersList.length > 2
									? usersList[2][1].firebase_auth_id
										? usersList[2][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
230
							position="bottom"
231
232
233
234
235
236
						/>
					</div>
					<div className="absolute top-1/2 pb-10 -left-36 transform -translate-y-1/2 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
237
							number={usersList.length > 3 ? taskVotes[usersList[3][0] ?? ''] || 0 : 0}
238
239
240
241
242
243
244
							name={
								usersList.length > 3
									? usersList[3][1].firebase_auth_id
										? usersList[3][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
245
							position="top"
246
247
248
249
250
251
						/>
					</div>
					<div className="absolute -top-36 -left-10 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
252
							number={usersList.length > 4 ? taskVotes[usersList[4][0] ?? ''] || 0 : 0}
253
254
255
256
257
258
259
							name={
								usersList.length > 4
									? usersList[4][1].firebase_auth_id
										? usersList[4][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
260
							position="top"
261
262
263
264
265
266
						/>
					</div>
					<div className="absolute -top-48 left-32 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
267
							number={usersList.length > 5 ? taskVotes[usersList[5][0] ?? ''] || 0 : 0}
268
269
270
271
272
273
274
							name={
								usersList.length > 5
									? usersList[5][1].firebase_auth_id
										? usersList[5][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
275
							position="top"
276
277
278
279
280
281
						/>
					</div>
					<div className="absolute -top-48 left-1/2 transform -translate-x-1/2 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
282
							number={usersList.length > 6 ? taskVotes[usersList[6][0] ?? ''] || 0 : 0}
283
284
285
286
287
288
289
							name={
								usersList.length > 6
									? usersList[6][1].firebase_auth_id
										? usersList[6][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
290
							position="top"
291
292
293
294
295
296
						/>
					</div>
					<div className="absolute -top-48 right-32 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
297
							number={usersList.length > 7 ? taskVotes[usersList[7][0] ?? ''] || 0 : 0}
298
299
300
301
302
303
304
							name={
								usersList.length > 7
									? usersList[7][1].firebase_auth_id
										? usersList[7][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
305
							position="top"
306
307
308
309
310
311
						/>
					</div>
					<div className="absolute -top-36 -right-10 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
312
							number={usersList.length > 8 ? taskVotes[usersList[8][0] ?? ''] || 0 : 0}
313
314
315
316
317
318
319
							name={
								usersList.length > 8
									? usersList[8][1].firebase_auth_id
										? usersList[8][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
320
							position="top"
321
322
323
324
325
326
						/>
					</div>
					<div className="absolute top-1/2 pb-10 -right-36 transform -translate-y-1/2 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
327
							number={usersList.length > 9 ? taskVotes[usersList[9][0] ?? ''] || 0 : 0}
328
329
330
331
332
333
334
							name={
								usersList.length > 9
									? usersList[9][1].firebase_auth_id
										? usersList[9][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
335
							position="top"
336
337
338
339
340
341
						/>
					</div>
					<div className="absolute -bottom-28 -right-10 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
342
							number={usersList.length > 10 ? taskVotes[usersList[10][0] ?? ''] || 0 : 0}
343
344
345
346
347
348
349
							name={
								usersList.length > 10
									? usersList[10][1].firebase_auth_id
										? usersList[10][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
350
							position="bottom"
351
352
353
354
355
356
						/>
					</div>
					<div className="absolute -bottom-40 right-32 z-10">
						<VotingTriider
							isToShow={isToShow}
							type="card"
357
							number={usersList.length > 11 ? taskVotes[usersList[11][0] ?? ''] || 0 : 0}
358
359
360
361
362
363
364
							name={
								usersList.length > 11
									? usersList[11][1].firebase_auth_id
										? usersList[11][1]?.name
										: ''
									: ''
							}
Leticia Flores's avatar
Leticia Flores committed
365
							position="bottom"
366
367
368
369
370
371
372
						/>
					</div>
				</div>
			</>
		);
	}

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
	const handleParseCSV = () => {
		const storiesList = Object.entries(session?.game.stories || {});
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		const stories = storiesList.map((story: any) => {
			const tasksList = Object.entries(story[1].tasks || {});
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const descriptions = tasksList.map((task: any) => task);
			return {
				storyName: story[1].name,
				tasks: descriptions,
			};
		});

		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		const csvData: any[] | Papa.UnparseObject<any> = [];

		const votesList = Object.entries(session?.votes || {});
		stories.forEach((item) => {
			const row = {
				Story: [item.storyName],
				SumOfTasksVotes: item.tasks.reduce((sum, task) => {
					const taskVotes = votesList.find((item) => item[0] === task[0]);
					return sum + (taskVotes ? Number(taskVotes[1].final_vote) : 0);
				}, 0),
				Tasks: item.tasks.map((task) => task[1].description).join(','),
				TasksVotes: item.tasks
					.map((task) => {
						const taskVotes = votesList.find((item) => item[0] === task[0]);
						return taskVotes ? taskVotes[1].final_vote : '0';
					})
					.join(','),
			};

			csvData.push(row);
		});

		const csv = Papa.unparse(csvData, { delimiter: ';' });

Gabriel Bohn's avatar
Gabriel Bohn committed
411
		handleDeleteSession(true);
412
		router('/');
413
414
415
		return csv;
	};

Gabriel Bohn's avatar
Gabriel Bohn committed
416
	const handleDeleteSession = async (save: boolean) => {
417
		await request(`/sessions/finalize/${session_id}?save=${save}`);
Gabriel Bohn's avatar
Gabriel Bohn committed
418
419
	};

Gabriel Bohn's avatar
Gabriel Bohn committed
420
421
422
423
424
425
426
427
428
	const FinishedVotingModal = () => {
		return (
			<div className="fixed flex items-center justify-center flex-col w-screen h-screen top-0 left-0 gap-10 bg-slate-50 bg-opacity-80 z-50">
				<img className="w-96" src={LogoOrange} alt="Triider logo" />
				<h1 className="text-7xl">FIM DA VOTAÇÃO</h1>
				<Button
					size="large"
					variant="primary"
					onClickAction={() => {
429
430
431
						const csv = handleParseCSV();
						const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
						saveAs(blob, `Squad_-_${session?.game.squad_name}.csv`);
Gabriel Bohn's avatar
Gabriel Bohn committed
432
433
434
435
436
437
438
					}}
				>
					Exportar agora
				</Button>
			</div>
		);
	};
439

440
	return (
441
442
		<>
			<div className="w-screen h-screen bg-triider-grayscale-game">
Gabriel Bohn's avatar
Gabriel Bohn committed
443
				{sessionEnded && <FinishedVotingModal />}
444
445
446
447
				<Header
					onCancelPlanning={() => {
						setIsOpenCancelModal(true);
					}}
448
449
					sprintName={session?.game?.sprint_name}
					squadName={session?.game?.squad_name}
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
				/>
				<div className="ml-16 mt-24">
					<Qrcode link={session_id || ''} code={session_id || ''} />
				</div>
				<div className="flex-col absolute top-28 right-20">
					{voteState === 'group' && (
						<div className="w-72 h-52 border-2 p-6 rounded-2xl bg-white font-medium border-transparent shadow-xl mb-20">
							<div className="font-medium text-xl">Resumo</div>
							<div className="flex flex-row justify-between mt-4">
								<div className="font-medium">Mínima</div>
								<div className="font-medium">
									{isToShow && min < Number.MAX_SAFE_INTEGER && min}
								</div>
							</div>
							<div className="flex flex-row justify-between mt-4">
								<div className="font-medium">Média</div>
								<div className="font-medium">{isToShow && !isNaN(average) && average}</div>
							</div>
							<div className="flex flex-row justify-between mt-4">
								<div className="font-medium">Máxima</div>
								<div className="font-medium">{isToShow && max > -Infinity && max}</div>
							</div>
472
						</div>
473
474
475
					)}
					<div className="flex flex-col justify-between w-72 h-52 border-2 p-6 rounded-2xl bg-triider-helper-green1 font-medium border-transparent shadow-xl mt-2">
						{session?.game.stories[session.game.current_story]?.name &&
Kayky Belleboni Casagrande's avatar
Kayky Belleboni Casagrande committed
476
						session?.game.stories[session.game.current_story]?.name.length > 67 ? (
477
478
479
480
481
482
							<ToolTip tooltip={session?.game.stories[session.game.current_story]?.name}>
								<div className="text-white text-xl line-clamp-3">
									{session?.game.stories[session.game.current_story]?.name}
								</div>
							</ToolTip>
						) : (
483
							<div className="text-white text-xl line-clamp-3">
484
								{session?.game.stories[session.game.current_story]?.name}
485
							</div>
486
487
488
489
490
491
						)}
						<div className="flex flex-row justify-end items-center gap-2">
							<div className="text-white">Pontuação atual</div>
							<div className="item-center justify-center w-fit p-4 border-2 rounded-2xl bg-triider-helper-green2 font-medium border-transparent shadow-xl">
								<div className="font-bold">{storieDificulty}</div>
							</div>
492
493
494
						</div>
					</div>
				</div>
495
				{voteState === 'individual' ? (
496
497
					<div>
						<div className="flex h-72 items-center justify-center w-fit mx-auto -mt-20 relative">
498
499
500
501
502
503
504
505
							{session && <ShowPlayers />}
							<img src={pokerTable} alt="Poker table" className=" relative px-4" />

							<div className="flex flex-col absolute items-center gap-2">
								<h2 className="text-center text-2xl font-medium text-white">Votando</h2>
								<p className="text-lg text-white pb-2">Aguarde enquanto seus colegas votam</p>
								<Button
									onClickAction={() => {
506
507
508
509
510
511
512
513
514
										const someoneIsOn: boolean = usersList?.some(
											(user) => user[1]?.firebase_auth_id
										);

										if (someoneIsOn) {
											setVoteState('group');
										} else {
											setIsOpenNoPlayersModal(true);
										}
515
516
517
518
519
520
									}}
									size="small"
								>
									Mostrar Votos
								</Button>
							</div>
521
522
						</div>
					</div>
523
524
				) : (
					<div>
525
						<div className="flex h-72 items-center justify-center w-fit mx-auto -mt-20 relative">
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
							{session && <ShowPlayers />}
							<img src={pokerTable} alt="Poker table" className=" relative px-4" />

							<div className="flex flex-col absolute items-center gap-2">
								<h2 className="text-center text-2xl font-medium text-white">Votação conjunta</h2>
								<p className="text-lg text-white pb-2">Escolha a pontuação final da tarefa</p>
								<div className="flex justify-around items-center gap-3">
									<VoteCard
										label="1"
										selected={cardSelected === '1'}
										onClick={(e) => setCardSelected(e)}
									/>
									<VoteCard
										label="2"
										selected={cardSelected === '2'}
										onClick={(e) => {
											setCardSelected(e);
										}}
									/>
									<VoteCard
										label="3"
										selected={cardSelected === '3'}
										onClick={(e) => setCardSelected(e)}
									/>
									<VoteCard
										label="5"
										selected={cardSelected === '5'}
										onClick={(e) => setCardSelected(e)}
									/>
									<VoteCard
										label="8"
										selected={cardSelected === '8'}
										onClick={(e) => setCardSelected(e)}
									/>
									<VoteCard
										label="13"
										selected={cardSelected === '13'}
										onClick={(e) => setCardSelected(e)}
									/>
								</div>
								<Button
									size="small"
									onClickAction={() => {
										handleVote();
570
									}}
571
572
573
								>
									Atribuir pontuação
								</Button>
574
							</div>
575
576
577
578
579
580
581
582
583
584
585
586
587
588
						</div>
					</div>
				)}
				<div className="w-screen fixed left-1/2 transform -translate-x-1/2 bottom-0">
					<Carousel
						finalVotes={votes}
						taskList={session?.game.stories[session.game.current_story].tasks || {}}
						currentTaskId={session?.game.current_task ?? null}
						voteLater={voteLater}
					/>
				</div>
			</div>
			<Modal isOpen={isOpenCancelModal} onClose={() => setIsOpenCancelModal(false)} title="">
				<div className="fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 z-30">
Gabriel Bohn's avatar
Gabriel Bohn committed
589
					<div className="flex flex-col items-center min-w-fit w-fit border-2 p-16 bg-white rounded-lg relative">
590
591
592
593
594
595
596
						<h1 className="text-2xl font-bold decoration-triider-primary-orange2 my-8 underline-offset-8">
							Cancelar a votação?
						</h1>
						<div className="flex gap-5 justify-center mt-4">
							<Button variant="secondary" onClickAction={() => setIsOpenCancelModal(false)}>
								Não
							</Button>
597
							<Button
598
								variant="primary"
599
								onClickAction={() => {
600
									closeListener(false);
601
602
								}}
							>
603
								Sim
604
605
606
607
							</Button>
						</div>
					</div>
				</div>
608
			</Modal>
609
610
			<Modal isOpen={isOpenNoPlayersModal} onClose={() => setIsOpenCancelModal(false)} title="">
				<div className="fixed left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 z-30">
Gabriel Bohn's avatar
Gabriel Bohn committed
611
					<div className="flex flex-col items-center min-w-fit w-fit border-2 p-16 bg-white rounded-lg relative">
612
613
614
615
616
617
618
619
620
621
622
						<h1 className="text-2xl font-bold decoration-triider-primary-orange2 my-8 underline-offset-8">
							Não há jogadores na sessão
						</h1>
						<div className="flex gap-5 justify-center mt-4">
							<Button variant="primary" onClickAction={() => setIsOpenNoPlayersModal(false)}>
								Ok
							</Button>
						</div>
					</div>
				</div>
			</Modal>
623
		</>
624
625
	);
}