forked from sk1982/actaeon
chuni: add recent/top ratings to board
This commit is contained in:
parent
6f3cc058fd
commit
5c5459f5f0
@ -5,33 +5,58 @@ import { getUserRating } from '@/actions/chuni/profile';
|
||||
import { useState } from 'react';
|
||||
import { Button, ButtonGroup } from '@nextui-org/react';
|
||||
import { useBreakpoint } from '@/helpers/use-breakpoint';
|
||||
import { BigDecimal } from '@/helpers/big-decimal';
|
||||
import { ChuniRating } from '@/components/chuni/rating';
|
||||
|
||||
export const ChuniTopRatingSidebar = ({ rating }: { rating: Awaited<ReturnType<typeof getUserRating>> }) => {
|
||||
const [shownRating, setShownRating] = useState<'top' | 'recent' | null>('recent');
|
||||
const breakpoint = useBreakpoint();
|
||||
|
||||
const recent = rating.recent.slice(0, 10);
|
||||
const topAvg = rating.top.reduce((t, x) => t.add(x.rating), new BigDecimal(0))
|
||||
.div(30, 2n);
|
||||
const recentAvg = recent.reduce((t, x) => t.add(x.rating), new BigDecimal(0))
|
||||
.div(10, 2n);
|
||||
|
||||
if (![undefined, 'sm'].includes(breakpoint) && shownRating === null)
|
||||
setShownRating('recent');
|
||||
|
||||
return (<div className="w-full mt-4 md:mt-0 px-2 sm:px-0 md:fixed md:overflow-y-auto h-fixed flex md:w-[16rem] 2xl:w-[32rem]">
|
||||
<div className="hidden 2xl:flex">
|
||||
<div className="w-1/2 pr-1">
|
||||
<div>Top</div>
|
||||
<div className="flex items-baseline">
|
||||
<span>Top </span>
|
||||
<ChuniRating className="text-xl" rating={+topAvg.mul(100)}>{ topAvg.toFixed(2) }</ChuniRating>
|
||||
</div>
|
||||
<ChuniTopRating rating={rating.top} />
|
||||
</div>
|
||||
<div className="pl-1 w-1/2 mr-2">
|
||||
<div>Recent</div>
|
||||
<ChuniTopRating rating={rating.recent.slice(0, 10)} />
|
||||
<div className="flex items-baseline">
|
||||
<span>Recent </span>
|
||||
<ChuniRating className="text-xl" rating={+recentAvg.mul(100)}>{ recentAvg.toFixed(2) }</ChuniRating>
|
||||
</div>
|
||||
<ChuniTopRating rating={recent} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex flex-col 2xl:hidden pr-2">
|
||||
<ButtonGroup size="sm" className="mb-2 hidden md:block">
|
||||
<Button color={shownRating === 'top' ? 'primary' : 'default'} onClick={() => setShownRating('top')}>Top</Button>
|
||||
<Button color={shownRating === 'recent' ? 'primary' : 'default'} onClick={() => setShownRating('recent')}>Recent</Button>
|
||||
</ButtonGroup>
|
||||
<div className="flex items-center justify-center overflow-hidden">
|
||||
<span className="text-lg mr-6 md:hidden font-semibold pb-2">Ratings</span>
|
||||
<ButtonGroup size="md" className="mb-2 md:hidden">
|
||||
<div className="mb-2 hidden md:flex">
|
||||
<ButtonGroup size="sm" className="">
|
||||
<Button color={shownRating === 'top' ? 'primary' : 'default'} onClick={() => setShownRating('top')}>Top</Button>
|
||||
<Button color={shownRating === 'recent' ? 'primary' : 'default'} onClick={() => setShownRating('recent')}>Recent</Button>
|
||||
</ButtonGroup>
|
||||
<ChuniRating className="ml-auto text-xl" rating={+(shownRating === 'top' ? topAvg : recentAvg).mul(100)}>
|
||||
{(shownRating === 'top' ? topAvg : recentAvg).toFixed(2)}
|
||||
</ChuniRating>
|
||||
</div>
|
||||
<div className="flex items-center justify-center overflow-hidden h-32 md:hidden">
|
||||
{shownRating && <div className="flex items-baseline mb-2">
|
||||
Average:
|
||||
<ChuniRating className="text-xl" rating={+(shownRating === 'top' ? topAvg : recentAvg).mul(100)}>
|
||||
{(shownRating === 'top' ? topAvg : recentAvg).toFixed(2)}
|
||||
</ChuniRating>
|
||||
</div>}
|
||||
<span className="text-lg mr-6 font-semibold pb-2 ml-auto">Ratings</span>
|
||||
<ButtonGroup size="md" className="mb-2 h-full">
|
||||
<Button color={shownRating === 'top' ? 'primary' : 'default'} onClick={() => setShownRating('top')}>Top</Button>
|
||||
<Button color={shownRating === 'recent' ? 'primary' : 'default'} onClick={() => setShownRating('recent')}>Recent</Button>
|
||||
<Button color={shownRating === null ? 'primary' : 'default'} onClick={() => setShownRating(null)}>Hide</Button>
|
||||
|
109
src/helpers/big-decimal.ts
Normal file
109
src/helpers/big-decimal.ts
Normal file
@ -0,0 +1,109 @@
|
||||
type DecimalInput = BigDecimal | number | string;
|
||||
|
||||
export class BigDecimal {
|
||||
private val: bigint;
|
||||
private decimals: bigint;
|
||||
|
||||
constructor(val: DecimalInput) {
|
||||
if (val instanceof BigDecimal) {
|
||||
this.val = val.val;
|
||||
this.decimals = val.decimals;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof val === 'number')
|
||||
val = val.toString();
|
||||
|
||||
const decimalIndex = val.indexOf('.');
|
||||
val = val.replace('.', '');
|
||||
this.val = BigInt(val);
|
||||
|
||||
if (decimalIndex === -1) {
|
||||
this.decimals = 0n;
|
||||
} else {
|
||||
this.decimals = BigInt(val.length - decimalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private coerceDecimals(other: DecimalInput) {
|
||||
const a = new BigDecimal(other);
|
||||
const b = new BigDecimal(this);
|
||||
|
||||
if (a.decimals > b.decimals) {
|
||||
b.val *= 10n ** (a.decimals - b.decimals);
|
||||
b.decimals = a.decimals;
|
||||
} else if (a.decimals < b.decimals) {
|
||||
a.val *= 10n ** (b.decimals - a.decimals);
|
||||
a.decimals = b.decimals;
|
||||
}
|
||||
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
add(other: DecimalInput) {
|
||||
const [a, b] = this.coerceDecimals(other);
|
||||
a.val += b.val;
|
||||
return a;
|
||||
}
|
||||
|
||||
sub(other: DecimalInput) {
|
||||
const [a, b] = this.coerceDecimals(other);
|
||||
b.val -= a.val;
|
||||
return b;
|
||||
}
|
||||
|
||||
mul(other: DecimalInput) {
|
||||
const a = new BigDecimal(other);
|
||||
const b = new BigDecimal(this);
|
||||
a.val *= b.val;
|
||||
a.decimals += b.decimals;
|
||||
return a;
|
||||
}
|
||||
|
||||
div(other: DecimalInput, minDecimals=0n) {
|
||||
const a = new BigDecimal(other);
|
||||
const b = new BigDecimal(this);
|
||||
|
||||
if ((b.decimals - a.decimals) < minDecimals) {
|
||||
const exp = minDecimals - (b.decimals - a.decimals);
|
||||
b.val *= 10n ** exp;
|
||||
b.decimals += exp;
|
||||
}
|
||||
|
||||
b.val /= a.val;
|
||||
b.decimals -= a.decimals;
|
||||
if (b.decimals < 0) b.decimals = 0n;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static stringVal(val: bigint, decimals: bigint) {
|
||||
const str = val.toString();
|
||||
const pos = -Number(decimals);
|
||||
return str.slice(0, pos) + '.' + str.slice(pos);
|
||||
}
|
||||
|
||||
|
||||
toFixed(places: number | bigint) {
|
||||
places = BigInt(places);
|
||||
|
||||
if (places >= this.decimals)
|
||||
return BigDecimal.stringVal(this.val, this.decimals) + '0'.repeat(Number(places - this.decimals));
|
||||
|
||||
return BigDecimal.stringVal(this.val / (10n ** (this.decimals - places)), places);
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return +this.toString();
|
||||
}
|
||||
|
||||
toString() {
|
||||
let val = this.val;
|
||||
let decimals = this.decimals;
|
||||
while (val && !(val % 10n)) {
|
||||
val /= 10n;
|
||||
--decimals;
|
||||
}
|
||||
return BigDecimal.stringVal(val, decimals);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": ["dom", "dom.iterable", "esnext", "es2020"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -12,6 +12,7 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"target": "es2020",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
|
Loading…
Reference in New Issue
Block a user