formatting

This commit is contained in:
Виталий Лавшонок
2025-11-04 15:04:59 +03:00
parent 3cd8e14288
commit 4972836164
60 changed files with 3604 additions and 2916 deletions

View File

@@ -1,65 +1,74 @@
import { cn } from "../../../lib/cn";
import { Account } from "../../../assets/icons/auth";
import { PrimaryButton } from "../../../components/button/PrimaryButton";
import { ReverseButton } from "../../../components/button/ReverseButton";
import { cn } from '../../../lib/cn';
import { Account } from '../../../assets/icons/auth';
import { PrimaryButton } from '../../../components/button/PrimaryButton';
import { ReverseButton } from '../../../components/button/ReverseButton';
export interface ContestItemProps {
name: string;
startAt: string;
duration: number;
members: number;
statusRegister: "reg" | "nonreg";
type: "first" | "second";
statusRegister: 'reg' | 'nonreg';
type: 'first' | 'second';
}
function formatDate(dateString: string): string {
const date = new Date(dateString);
const date = new Date(dateString);
const day = date.getDate().toString().padStart(2, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const year = date.getFullYear();
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear();
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${day}/${month}/${year}\n${hours}:${minutes}`;
return `${day}/${month}/${year}\n${hours}:${minutes}`;
}
function formatWaitTime(ms: number): string {
const minutes = Math.floor(ms / 60000);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const minutes = Math.floor(ms / 60000);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
const remainder = days % 10;
let suffix = "дней";
if (remainder === 1 && days !== 11) suffix = "день";
else if (remainder >= 2 && remainder <= 4 && (days < 10 || days > 20)) suffix = "дня";
return `${days} ${suffix}`;
} else if (hours > 0) {
const mins = minutes % 60;
return mins > 0 ? `${hours} ч ${mins} мин` : `${hours} ч`;
} else {
return `${minutes} мин`;
}
if (days > 0) {
const remainder = days % 10;
let suffix = 'дней';
if (remainder === 1 && days !== 11) suffix = 'день';
else if (remainder >= 2 && remainder <= 4 && (days < 10 || days > 20))
suffix = 'дня';
return `${days} ${suffix}`;
} else if (hours > 0) {
const mins = minutes % 60;
return mins > 0 ? `${hours} ч ${mins} мин` : `${hours} ч`;
} else {
return `${minutes} мин`;
}
}
const ContestItem: React.FC<ContestItemProps> = ({
name, startAt, duration, members, statusRegister, type
name,
startAt,
duration,
members,
statusRegister,
type,
}) => {
const now = new Date();
const waitTime = new Date(startAt).getTime() - now.getTime();
return (
<div className={cn("w-full box-border relative rounded-[10px] px-[20px] py-[10px] text-liquid-white text-[16px] leading-[20px]",
waitTime <= 0 ? "grid grid-cols-6" : "grid grid-cols-7",
"items-center font-bold text-liquid-white",
type == "first" ? " bg-liquid-lighter" : " bg-liquid-background"
)}>
<div className="text-left font-bold text-[18px]">
{name}
</div>
<div
className={cn(
'w-full box-border relative rounded-[10px] px-[20px] py-[10px] text-liquid-white text-[16px] leading-[20px]',
waitTime <= 0 ? 'grid grid-cols-6' : 'grid grid-cols-7',
'items-center font-bold text-liquid-white',
type == 'first'
? ' bg-liquid-lighter'
: ' bg-liquid-background',
)}
>
<div className="text-left font-bold text-[18px]">{name}</div>
<div className="text-center text-liquid-brightmain font-normal ">
{/* {authors.map((v, i) => <p key={i}>{v}</p>)} */}
valavshonok
@@ -67,29 +76,29 @@ const ContestItem: React.FC<ContestItemProps> = ({
<div className="text-center text-nowrap whitespace-pre-line">
{formatDate(startAt)}
</div>
<div className="text-center">
{formatWaitTime(duration)}
</div>
{
waitTime > 0 &&
<div className="text-center">{formatWaitTime(duration)}</div>
{waitTime > 0 && (
<div className="text-center whitespace-pre-line ">
{"До начала\n" + formatWaitTime(waitTime)}
{'До начала\n' + formatWaitTime(waitTime)}
</div>
}
)}
<div className="items-center justify-center flex gap-[10px] flex-row w-full">
<div>{members}</div>
<img src={Account} className="h-[24px] w-[24px]"/>
<img src={Account} className="h-[24px] w-[24px]" />
</div>
<div className="flex items-center justify-end">
{
statusRegister == "reg" ?
<> <PrimaryButton onClick={() => {}} text="Регистрация"/></>
:
<> <ReverseButton onClick={() => {}} text="Вы записаны"/></>
}
{statusRegister == 'reg' ? (
<>
{' '}
<PrimaryButton onClick={() => {}} text="Регистрация" />
</>
) : (
<>
{' '}
<ReverseButton onClick={() => {}} text="Вы записаны" />
</>
)}
</div>
</div>
);
};

View File

@@ -1,10 +1,10 @@
import { useEffect } from "react";
import { SecondaryButton } from "../../../components/button/SecondaryButton";
import { cn } from "../../../lib/cn";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
import ContestsBlock from "./ContestsBlock";
import { setMenuActivePage } from "../../../redux/slices/store";
import { fetchContests } from "../../../redux/slices/contests";
import { useEffect } from 'react';
import { SecondaryButton } from '../../../components/button/SecondaryButton';
import { cn } from '../../../lib/cn';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import ContestsBlock from './ContestsBlock';
import { setMenuActivePage } from '../../../redux/slices/store';
import { fetchContests } from '../../../redux/slices/contests';
const Contests = () => {
const dispatch = useAppDispatch();
@@ -17,12 +17,14 @@ const Contests = () => {
// При загрузке страницы — выставляем активную вкладку и подгружаем контесты
useEffect(() => {
dispatch(setMenuActivePage("contests"));
dispatch(setMenuActivePage('contests'));
dispatch(fetchContests({}));
}, []);
if (loading == "loading") {
return <div className="text-liquid-white p-4">Загрузка контестов...</div>;
if (loading == 'loading') {
return (
<div className="text-liquid-white p-4">Загрузка контестов...</div>
);
}
if (error) {
@@ -33,7 +35,11 @@ const Contests = () => {
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20p]">
<div className="h-full box-border">
<div className="relative flex items-center mb-[20px]">
<div className={cn("h-[50px] text-[40px] font-bold text-liquid-white flex items-center")}>
<div
className={cn(
'h-[50px] text-[40px] font-bold text-liquid-white flex items-center',
)}
>
Контесты
</div>
<SecondaryButton
@@ -49,8 +55,7 @@ const Contests = () => {
className="mb-[20px]"
title="Текущие"
contests={contests.filter((contest) => {
const endTime =
new Date(contest.endsAt).getTime()
const endTime = new Date(contest.endsAt).getTime();
return endTime >= now.getTime();
})}
/>
@@ -59,8 +64,7 @@ const Contests = () => {
className="mb-[20px]"
title="Прошедшие"
contests={contests.filter((contest) => {
const endTime =
new Date(contest.endsAt).getTime()
const endTime = new Date(contest.endsAt).getTime();
return endTime < now.getTime();
})}
/>

View File

@@ -1,11 +1,8 @@
import { useState, FC } from "react";
import { cn } from "../../../lib/cn";
import { ChevroneDown } from "../../../assets/icons/groups";
import ContestItem from "./ContestItem";
import { Contest } from "../../../redux/slices/contests";
import { useState, FC } from 'react';
import { cn } from '../../../lib/cn';
import { ChevroneDown } from '../../../assets/icons/groups';
import ContestItem from './ContestItem';
import { Contest } from '../../../redux/slices/contests';
interface ContestsBlockProps {
contests: Contest[];
@@ -13,46 +10,61 @@ interface ContestsBlockProps {
className?: string;
}
const ContestsBlock: FC<ContestsBlockProps> = ({ contests, title, className }) => {
const [active, setActive] = useState<boolean>(title != "Скрытые");
const ContestsBlock: FC<ContestsBlockProps> = ({
contests,
title,
className,
}) => {
const [active, setActive] = useState<boolean>(title != 'Скрытые');
return (
<div className={cn(" border-b-[1px] border-b-liquid-lighter rounded-[10px]",
className
)}>
<div className={cn(" h-[40px] text-[24px] font-bold flex gap-[10px] items-center cursor-pointer border-b-[1px] border-b-transparent transition-all duration-300",
active && "border-b-liquid-lighter"
<div
className={cn(
' border-b-[1px] border-b-liquid-lighter rounded-[10px]',
className,
)}
>
<div
className={cn(
' h-[40px] text-[24px] font-bold flex gap-[10px] items-center cursor-pointer border-b-[1px] border-b-transparent transition-all duration-300',
active && 'border-b-liquid-lighter',
)}
onClick={() => {
setActive(!active)
}}>
setActive(!active);
}}
>
<span>{title}</span>
<img src={ChevroneDown} className={cn("transition-all duration-300",
active && "rotate-180"
)} />
<img
src={ChevroneDown}
className={cn(
'transition-all duration-300',
active && 'rotate-180',
)}
/>
</div>
<div className={cn(" grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-300",
active && "grid-rows-[1fr] opacity-100"
)}>
<div
className={cn(
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-300',
active && 'grid-rows-[1fr] opacity-100',
)}
>
<div className="overflow-hidden">
<div className="pb-[10px] pt-[20px]">
{
contests.map((v, i) => <ContestItem
key={i}
name={v.name}
startAt={v.startsAt}
statusRegister={"reg"}
duration={new Date(v.endsAt).getTime() - new Date(v.startsAt).getTime()}
members={v.members.length}
type={i % 2 ? "second" : "first"} />)
}
{contests.map((v, i) => (
<ContestItem
key={i}
name={v.name}
startAt={v.startsAt}
statusRegister={'reg'}
duration={
new Date(v.endsAt).getTime() -
new Date(v.startsAt).getTime()
}
members={v.members.length}
type={i % 2 ? 'second' : 'first'}
/>
))}
</div>
</div>
</div>
</div>