|
1 |
| -import React from "react"; |
2 |
| -import { Table } from "reactstrap"; |
| 1 | +import React, { useState } from "react"; |
| 2 | +import { Table, Button, ButtonGroup } from "reactstrap"; |
3 | 3 | import { ProblemId } from "../../interfaces/Status";
|
4 | 4 | import { isAccepted } from "../../utils";
|
5 | 5 | import { countBy, groupBy } from "../../utils/GroupBy";
|
@@ -47,52 +47,129 @@ export const getUserPointCounts = (
|
47 | 47 | };
|
48 | 48 |
|
49 | 49 | export const SmallTable: React.FC<Props> = ({ submissions, setFilterFunc }) => {
|
| 50 | + const [grouped, setGrouped] = useState(true); |
50 | 51 | const mergedProblemMap =
|
51 | 52 | useMergedProblemMap().data ?? new Map<ProblemId, MergedProblem>();
|
52 | 53 | const userPointCountMap = getUserPointCounts(mergedProblemMap, submissions);
|
53 | 54 | const totalCount = getTotalCount(mergedProblemMap);
|
| 55 | + const totalCountBy100 = totalCount.reduce( |
| 56 | + ( |
| 57 | + ret: { point: number; count: number }[], |
| 58 | + current: { point: number; count: number } |
| 59 | + ) => { |
| 60 | + const roundedPoint = Math.floor(current.point / 100) * 100; |
| 61 | + const prev = ret.find((entry) => entry.point === roundedPoint); |
| 62 | + if (prev) { |
| 63 | + prev.count += current.count; |
| 64 | + } else { |
| 65 | + ret.push({ point: roundedPoint, count: current.count }); |
| 66 | + } |
| 67 | + return ret; |
| 68 | + }, |
| 69 | + [] |
| 70 | + ); |
| 71 | + |
| 72 | + const binarySearch = ( |
| 73 | + arr: { point: number; count: number }[], |
| 74 | + target: number |
| 75 | + ) => { |
| 76 | + let left = 0; |
| 77 | + let right = arr.length; |
| 78 | + while (right - left > 1) { |
| 79 | + const mid = Math.floor((left + right) / 2); |
| 80 | + if (arr[mid].point <= target) { |
| 81 | + left = mid; |
| 82 | + } else { |
| 83 | + right = mid; |
| 84 | + } |
| 85 | + } |
| 86 | + return left; |
| 87 | + }; |
| 88 | + |
| 89 | + const getUserPointCountInArea = ( |
| 90 | + countByPoint: Map<number | null | undefined, number>, |
| 91 | + pointStart: number, |
| 92 | + pointEnd: number |
| 93 | + ) => { |
| 94 | + let ret = 0; |
| 95 | + for ( |
| 96 | + let i = binarySearch(totalCount, pointStart); |
| 97 | + i < totalCount.length; |
| 98 | + i++ |
| 99 | + ) { |
| 100 | + if (totalCount[i].point >= pointEnd) { |
| 101 | + break; |
| 102 | + } |
| 103 | + ret += countByPoint.get(totalCount[i].point) ?? 0; |
| 104 | + } |
| 105 | + return ret; |
| 106 | + }; |
| 107 | + |
54 | 108 | return (
|
55 |
| - <Table striped bordered hover responsive> |
56 |
| - <thead> |
57 |
| - <tr> |
58 |
| - <th>Point</th> |
59 |
| - {totalCount.map(({ point }) => ( |
60 |
| - <th key={point}> |
61 |
| - <a |
62 |
| - href={window.location.hash} |
63 |
| - onClick={(): void => setFilterFunc(point)} |
64 |
| - > |
65 |
| - {point} |
66 |
| - </a> |
67 |
| - </th> |
68 |
| - ))} |
69 |
| - </tr> |
70 |
| - <tr> |
71 |
| - <th>Total</th> |
72 |
| - {totalCount.map(({ point, count }) => ( |
73 |
| - <th key={point}>{count}</th> |
74 |
| - ))} |
75 |
| - </tr> |
76 |
| - </thead> |
77 |
| - <tbody> |
78 |
| - {userPointCountMap.map(({ userId, countByPoint }) => ( |
79 |
| - <tr key={userId}> |
80 |
| - <td>{userId}</td> |
81 |
| - {totalCount.map(({ point, count }) => ( |
82 |
| - <td |
83 |
| - key={point} |
84 |
| - className={ |
85 |
| - countByPoint.get(point) === count |
86 |
| - ? TableColor.Success |
87 |
| - : TableColor.None |
88 |
| - } |
89 |
| - > |
90 |
| - {countByPoint.get(point) ?? 0} |
91 |
| - </td> |
| 109 | + <> |
| 110 | + <ButtonGroup className="mb-2"> |
| 111 | + <Button onClick={(): void => setGrouped(!grouped)}> |
| 112 | + {grouped ? "Grouped" : "All"} |
| 113 | + </Button> |
| 114 | + </ButtonGroup> |
| 115 | + <Table striped bordered hover responsive> |
| 116 | + <thead> |
| 117 | + <tr> |
| 118 | + <th>Point</th> |
| 119 | + {(grouped ? totalCountBy100 : totalCount).map(({ point }) => ( |
| 120 | + <th key={point}> |
| 121 | + <a |
| 122 | + href={window.location.hash} |
| 123 | + onClick={(): void => setFilterFunc(point)} |
| 124 | + > |
| 125 | + {grouped ? `${point}-` : point} |
| 126 | + </a> |
| 127 | + </th> |
92 | 128 | ))}
|
93 | 129 | </tr>
|
94 |
| - ))} |
95 |
| - </tbody> |
96 |
| - </Table> |
| 130 | + <tr> |
| 131 | + <th>Total</th> |
| 132 | + {(grouped ? totalCountBy100 : totalCount).map( |
| 133 | + ({ point, count }) => ( |
| 134 | + <th key={point}>{count}</th> |
| 135 | + ) |
| 136 | + )} |
| 137 | + </tr> |
| 138 | + </thead> |
| 139 | + <tbody> |
| 140 | + {userPointCountMap.map(({ userId, countByPoint }) => ( |
| 141 | + <tr key={userId}> |
| 142 | + <td>{userId}</td> |
| 143 | + {(grouped ? totalCountBy100 : totalCount).map( |
| 144 | + ({ point, count }) => ( |
| 145 | + <td |
| 146 | + key={point} |
| 147 | + className={ |
| 148 | + (grouped |
| 149 | + ? getUserPointCountInArea( |
| 150 | + countByPoint, |
| 151 | + point, |
| 152 | + point + 100 |
| 153 | + ) |
| 154 | + : countByPoint.get(point)) === count |
| 155 | + ? TableColor.Success |
| 156 | + : TableColor.None |
| 157 | + } |
| 158 | + > |
| 159 | + {(grouped |
| 160 | + ? getUserPointCountInArea( |
| 161 | + countByPoint, |
| 162 | + point, |
| 163 | + point + 100 |
| 164 | + ) |
| 165 | + : countByPoint.get(point)) ?? 0} |
| 166 | + </td> |
| 167 | + ) |
| 168 | + )} |
| 169 | + </tr> |
| 170 | + ))} |
| 171 | + </tbody> |
| 172 | + </Table> |
| 173 | + </> |
97 | 174 | );
|
98 | 175 | };
|
0 commit comments