v3 release #75

Merged
alekswilc merged 63 commits from v3 into main 2024-12-13 20:29:17 +01:00
5 changed files with 79 additions and 28 deletions
Showing only changes of commit 7d564ebbfc - Show all commits

View File

@ -29,6 +29,12 @@ const generateSearch = (regex: RegExp) => [
},
];
const sortyByMap: Record<string, any> = {
time: { trainTime: -1 },
points: { trainPoints: -1 },
distance: { trainDistance: -1 },
}
export class LeaderboardRoute
{
static load()
@ -48,9 +54,10 @@ export class LeaderboardRoute
],
},
});
const sortBy = sortyByMap[req.query.s?.toString() ?? 'distance'] ?? sortyByMap.distance;
const records = await MProfile.aggregate(filter)
.sort({ trainPoints: -1 })
.sort(sortBy)
.limit(10);
res.json(

View File

@ -31,4 +31,24 @@ export const ArrowIcon = ({ rotated }: { rotated?: boolean }) =>
d="M4.41107 6.9107C4.73651 6.58527 5.26414 6.58527 5.58958 6.9107L10.0003 11.3214L14.4111 6.91071C14.7365 6.58527 15.2641 6.58527 15.5896 6.91071C15.915 7.23614 15.915 7.76378 15.5896 8.08922L10.5896 13.0892C10.2641 13.4147 9.73651 13.4147 9.41107 13.0892L4.41107 8.08922C4.08563 7.76378 4.08563 7.23614 4.41107 6.9107Z"
fill=""
/>
</svg>;
export const FlexArrowIcon = ({ rotated }: { rotated?: boolean }) =>
<svg
className={ `fill-current ${
rotated && "rotate-180"
}` }
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.41107 6.9107C4.73651 6.58527 5.26414 6.58527 5.58958 6.9107L10.0003 11.3214L14.4111 6.91071C14.7365 6.58527 15.2641 6.58527 15.5896 6.91071C15.915 7.23614 15.915 7.76378 15.5896 8.08922L10.5896 13.0892C10.2641 13.4147 9.73651 13.4147 9.41107 13.0892L4.41107 8.08922C4.08563 7.76378 4.08563 7.23614 4.41107 6.9107Z"
fill=""
/>
</svg>;

View File

@ -20,9 +20,16 @@ import { TLeaderboardRecord } from "../../../types/leaderboard.ts";
import { ContentLoader } from "../../mini/loaders/ContentLoader.tsx";
import { WarningAlert } from "../../mini/alerts/Warning.tsx";
import { formatTime } from "../../../util/time.ts";
import { FaCheck } from 'react-icons/fa6';
import { FaCheck } from "react-icons/fa6";
import { Dispatch, SetStateAction } from "react";
import { FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
export const TrainTable = ({ trains, error }: { trains: TLeaderboardRecord[], error: number }) =>
export const TrainTable = ({ trains, error, setSortBy, sortBy }: {
trains: TLeaderboardRecord[],
error: number,
setSortBy: Dispatch<SetStateAction<string>>
sortBy: string
}) =>
{
const { t } = useTranslation();
@ -35,26 +42,32 @@ export const TrainTable = ({ trains, error }: { trains: TLeaderboardRecord[], er
{ error === 1 && <div
className="rounded-sm border border-stroke bg-white px-5 pt-6 pb-2.5 shadow-default dark:border-strokedark dark:bg-boxdark sm:px-7.5 xl:pb-1">
<div className="flex flex-col">
<div className="grid grid-cols-4 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-5">
<div className="grid grid-cols-3 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-5">
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("leaderboard.user") }
</h5>
</div>
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("leaderboard.points") }
</h5>
</div>
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5">
<h5 className="cursor-pointer text-sm font-medium uppercase xsm:text-base"
onClick={ () => setSortBy("distance") }>
{ t("leaderboard.distance") }
</h5>
<FlexArrowIcon rotated={ !(sortBy === "distance") }/>
</div>
<div className="p-2.5 text-center sm:block xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5">
<h5 className="cursor-pointer text-sm font-medium uppercase xsm:text-base"
onClick={ () => setSortBy("points") }>
{ t("leaderboard.points") }
</h5>
<FlexArrowIcon rotated={ !(sortBy === "points") }/>
</div>
<div className="hidden sm:flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5">
<h5 className="cursor-pointer text-sm font-medium uppercase xsm:text-base"
onClick={ () => setSortBy("time") }>
{ t("leaderboard.time") }
</h5>
<FlexArrowIcon rotated={ !(sortBy === "time") }/>
</div>
<div className="hidden p-2.5 text-center sm:block xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
@ -65,7 +78,7 @@ export const TrainTable = ({ trains, error }: { trains: TLeaderboardRecord[], er
{ trains.map((train, key) => (
<div
className={ `grid grid-cols-4 sm:grid-cols-5 ${ trains.length === (key + 1)
className={ `grid grid-cols-3 sm:grid-cols-5 ${ trains.length === (key + 1)
? ""
: "border-b border-stroke dark:border-strokedark"
}` }
@ -74,19 +87,20 @@ export const TrainTable = ({ trains, error }: { trains: TLeaderboardRecord[], er
<div className="flex items-center justify-center gap-3 p-5 lg:p-5">
<p className="text-black dark:text-white sm:block break-all">
<Link to={ "/profile/" + train.steam }
className="color-orchid">{ train.steamName }</Link> { train.verified && <FaCheck className={ "inline text-meta-3 ml-1" }/> }
className="color-orchid">{ train.steamName }</Link> { train.verified &&
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-6">{ train.trainPoints }</p>
<p className="text-meta-6">{ (train.trainDistance / 1000).toFixed(2) }km</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-5">{ (train.trainDistance / 1000).toFixed(2) }km</p>
<p className="text-meta-5">{ train.trainPoints }</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<div className="hidden sm:flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-3">{ formatTime(train.trainTime) }</p>
</div>

View File

@ -17,7 +17,7 @@
import { useState } from "react";
import { TProfileData } from "../../../types/profile.ts";
import { useTranslation } from "react-i18next";
import { ArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
import { formatTime } from "../../../util/time.ts";
import { FaCheck } from "react-icons/fa6";
@ -26,7 +26,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
const [ showTrains, setShowTrains ] = useState(false);
const [ showStations, setShowStations ] = useState(false);
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("score");
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
const { t } = useTranslation();
return <div
@ -78,23 +78,26 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
{ t("profile.trains.train") }
</h5>
</div>
<div className="p-2.5 text-center xl:p-5 cursor-pointer"
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("distance") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.distance") }
</h5>
<FlexArrowIcon rotated={ !(sortTrainsBy === "distance") }/>
</div>
<div className="hidden sm:block p-2.5 text-center xl:p-5 cursor-pointer"
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("score") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.points") }
</h5>
<FlexArrowIcon rotated={ !(sortTrainsBy === "score") }/>
</div>
<div className="p-2.5 text-center xl:p-5 cursor-pointer"
<div className="hidden sm:flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("time") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.time") }
</h5>
<FlexArrowIcon rotated={ !(sortTrainsBy === "time") }/>
</div>
</div>
@ -116,11 +119,11 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
<p className="text-meta-6 sm:block break-all">{ Math.floor(train.distance / 1000) }km</p>
</div>
<div className="hidden sm:flex items-center justify-center p-2.5 lg:p-5">
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-3">{ train.score }</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<div className="hidden sm:flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-3">{ formatTime(train.time) }</p>
</div>
</div>;

View File

@ -26,6 +26,8 @@ export const TrainLeaderboard = () =>
const [ data, setData ] = useState<TLeaderboardRecord[]>([]);
const [ searchParams, setSearchParams ] = useSearchParams();
const [ searchItem, setSearchItem ] = useState(searchParams.get("q") ?? "");
const [ sortBy, setSortBy ] = useState(searchParams.get("distance") ?? "");
useEffect(() =>
{
@ -43,14 +45,19 @@ export const TrainLeaderboard = () =>
searchValue === "" ? searchParams.delete("q") : searchParams.set("q", searchValue);
setSearchParams(searchParams);
const params = new URLSearchParams();
searchValue && params.set('q', searchValue);
sortBy && params.set('s', sortBy);
setData([]);
setError(0);
fetch(`${ import.meta.env.VITE_API_URL }/leaderboard/train/?q=${ searchValue }`).then(x => x.json()).then(x =>
fetch(`${ import.meta.env.VITE_API_URL }/leaderboard/train/?${params.toString()}`).then(x => x.json()).then(x =>
{
setData(x.data.records);
setError(x.data.records.length > 0 ? 1 : 2);
});
}, [ searchValue ]);
}, [ searchValue, sortBy ]);
useEffect(() =>
{
@ -66,7 +73,7 @@ export const TrainLeaderboard = () =>
<>
<div className="flex flex-col gap-10">
<Search handleInputChange={ handleInputChange } searchItem={ searchItem }/>
<TrainTable trains={ data } error={ error }/>
<TrainTable trains={ data } error={ error } setSortBy={setSortBy} sortBy={sortBy}/>
</div>
</>