diff --git a/src/components/chuni/top-rating-sidebar.tsx b/src/components/chuni/top-rating-sidebar.tsx index 9ce6726..41d67b5 100644 --- a/src/components/chuni/top-rating-sidebar.tsx +++ b/src/components/chuni/top-rating-sidebar.tsx @@ -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> }) => { 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 (
-
Top
+
+ Top  + { topAvg.toFixed(2) } +
-
Recent
- +
+ Recent  + { recentAvg.toFixed(2) } +
+
- - - - -
- Ratings - +
+ + + + + + {(shownRating === 'top' ? topAvg : recentAvg).toFixed(2)} + +
+
+ {shownRating &&
+ Average:  + + {(shownRating === 'top' ? topAvg : recentAvg).toFixed(2)} + +
} + Ratings + diff --git a/src/helpers/big-decimal.ts b/src/helpers/big-decimal.ts new file mode 100644 index 0000000..6ba880f --- /dev/null +++ b/src/helpers/big-decimal.ts @@ -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); + } +} diff --git a/tsconfig.json b/tsconfig.json index 7b28589..864f3b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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"