/calendar new UI (#589)

* Initial

* Show popover for calendar items

* Remove event search

* Only show next button if events in the future

* Horizontal scroll

* Fix badges alignment

* Show badges for tournaments with them

* Showcase time until calendar date header in days

* Use Chakra UI for CSS

* Proper size calendar

* Restore inline styles for BadgeContainer

* Overflow auto works

* Show time badges
This commit is contained in:
Kalle 2021-07-18 16:48:46 +03:00 committed by GitHub
parent a7553ffd11
commit d462c6d0f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 303 additions and 35 deletions

View File

@ -3,6 +3,8 @@ import Markdown from "components/common/Markdown";
import MyLink from "components/common/MyLink";
import OutlinedBox from "components/common/OutlinedBox";
import UserAvatar from "components/common/UserAvatar";
import BadgeContainer from "components/u/BadgeContainer";
import { regularTournamentBadges } from "components/u/Badges";
import { useMyTheme, useUser } from "hooks/common";
import Image from "next/image";
import React from "react";
@ -59,10 +61,19 @@ const EventInfo = ({ event, edit }: EventInfoProps) => {
const imgSrc = eventImage(event.name);
const badges = regularTournamentBadges.filter(
(badgeObj) =>
event.name.toUpperCase().includes(badgeObj.name.toUpperCase()) ||
badgeObj.altNames?.some((altName) =>
event.name.toUpperCase().includes(altName.toUpperCase())
)
);
return (
<OutlinedBox
my={4}
py={4}
id={`event-${event.id}`}
data-cy={`event-info-section-${event.name
.toLowerCase()
.replace(/ /g, "-")}`}
@ -73,22 +84,36 @@ const EventInfo = ({ event, edit }: EventInfoProps) => {
{imgSrc && <Image src={imgSrc} width={36} height={36} alt="" />}
<Heading size="lg">{event.name}</Heading>
{event.tags.length > 0 && (
<Flex flexWrap="wrap" justifyContent="center" my={2}>
{event.tags.map((tag) => {
const tagInfo = TAGS.find((tagObj) => tagObj.code === tag)!;
return (
<Badge
key={tag}
mx={1}
my={1}
bg={tagInfo.color}
color="black"
>
{tagInfo.name}
</Badge>
);
})}
</Flex>
<>
<Flex flexWrap="wrap" justifyContent="center" my={2}>
{event.tags.map((tag) => {
const tagInfo = TAGS.find((tagObj) => tagObj.code === tag)!;
return (
<Badge
key={tag}
mx={1}
my={1}
bg={tagInfo.color}
color="black"
>
{tagInfo.name}
</Badge>
);
})}
</Flex>
{event.tags.some((tag) => tag === "BADGE") &&
badges.length > 0 ? (
<BadgeContainer
showInfo={false}
showBadges={true}
badges={badges.map((badge) => ({
src: `${badge.badgeName}.gif`,
description: "",
count: 1,
}))}
/>
) : null}
</>
)}
<Grid
templateColumns={["1fr", "2fr 4fr 2fr"]}

View File

@ -0,0 +1,141 @@
import { Box, Flex, Grid, IconButton, useMediaQuery } from "@chakra-ui/react";
import { useMyTheme } from "hooks/common";
import { ReactNode } from "react";
import { FiChevronLeft, FiChevronRight } from "react-icons/fi";
const daysInMonth = (month: number, year: number): number[] => {
const monthZeroIndex = month - 1;
const date = new Date(year, monthZeroIndex, 1);
const result = [];
while (date.getMonth() === monthZeroIndex) {
result.push(date.getDate());
date.setDate(date.getDate() + 1);
}
return result;
};
const emptyDaysCount = (currentDate: Date): number => {
return [6, 0, 1, 2, 3, 4, 5, 6][currentDate.getDay()];
};
const isToday = (date: Date) => {
const now = new Date();
return (
date.getDate() === now.getDate() &&
date.getMonth() === now.getMonth() &&
date.getFullYear() === now.getFullYear()
);
};
type MonthYear = { month: number; year: number };
const Square = ({ children }: { children?: ReactNode }) => {
const { gray } = useMyTheme();
return (
<Box color={gray} fontSize="small" fontWeight="bold">
{children}
</Box>
);
};
const Calendar = ({
current,
min,
handleBackClick,
handleNextClick,
showNextButton,
dateContents,
}: {
current: MonthYear;
min: MonthYear;
handleBackClick: () => void;
handleNextClick: () => void;
showNextButton: boolean;
dateContents: Record<string, ReactNode>;
}) => {
const { themeColorHex } = useMyTheme();
const [noSideNav] = useMediaQuery("(max-width: 991px)");
const currentDate = new Date(current.year, current.month - 1, 1);
return (
<Box
display="inline-block"
textAlign="center"
overflowX="auto"
width={noSideNav ? "100vw" : "calc(100vw - 250px)"}
>
<Flex justify="space-evenly">
<IconButton
aria-label="Go back a month"
variant="ghost"
color="current"
icon={<FiChevronLeft />}
borderRadius="50%"
float="left"
size="lg"
onClick={handleBackClick}
visibility={
current.month === min.month && current.year === min.year
? "hidden"
: "visible"
}
/>
<Box>
{currentDate.toLocaleDateString("en", { month: "long" })}
<br />
<b>{current.year}</b>
</Box>
<IconButton
aria-label="Go forward a month"
variant="ghost"
color="current"
icon={<FiChevronRight />}
borderRadius="50%"
float="right"
size="lg"
onClick={handleNextClick}
visibility={showNextButton ? "visible" : "hidden"}
/>
</Flex>
<Grid
templateColumns="repeat(7, max(14%, 125px))"
gridAutoRows="1fr"
gridRowGap="0.5rem"
gridColumnGap="0.25rem"
mt="1rem"
>
<Square>Mo</Square>
<Square>Tu</Square>
<Square>We</Square>
<Square>Th</Square>
<Square>Fr</Square>
<Square>Sa</Square>
<Square>Su</Square>
{new Array(emptyDaysCount(currentDate)).fill(null).map((_, i) => (
<Square key={i}></Square>
))}
{daysInMonth(current.month, current.year).map((day) => (
<Square key={day}>
<Box
as="span"
display="inline-block"
boxShadow={
isToday(new Date(current.year, current.month - 1, day))
? `inset 0px -2px 0px 0px ${themeColorHex}`
: undefined
}
>
{day}
</Box>
{dateContents[`${day}-${current.month}-${current.year}`]}
</Square>
))}
</Grid>
</Box>
);
};
export default Calendar;

View File

@ -1,5 +1,6 @@
import { Image as ChakraImage } from "@chakra-ui/image";
import { Flex, Text } from "@chakra-ui/react";
import { useMyTheme } from "hooks/common";
import { Fragment } from "react";
const BadgeContainer = ({
@ -15,6 +16,7 @@ const BadgeContainer = ({
count: number;
}[];
}) => {
const { themeColorHex } = useMyTheme();
return (
<Flex
flexDir={showInfo ? "column" : "row"}
@ -29,7 +31,8 @@ const BadgeContainer = ({
my={3}
>
{showBadges &&
badges.flatMap((badge) => {
badges.flatMap((badge, i) => {
const isLast = i === badges.length - 1;
if (showInfo)
return (
<Fragment key={badge.src}>
@ -54,14 +57,20 @@ const BadgeContainer = ({
src={`/badges/${badge.src}`}
/>
<Text
fontSize="sm"
style={{marginLeft: -5, marginTop: -25, paddingRight: 5, fontSize: '0.7rem', fontWeight: 'bold' }}
style={{
marginLeft: -3,
marginTop: -25,
paddingRight: isLast ? 0 : 5,
fontSize: "0.7rem",
fontWeight: "bold",
color: themeColorHex,
}}
visibility={badge.count === 1 ? "hidden" : "visible"}
>
{`x${badge.count}`}
</Text>
</Fragment>
)
);
})}
</Flex>
);

View File

@ -3,14 +3,16 @@ import { useEffect, useMemo, useState } from "react";
import { wonITZCount } from "utils/constants";
import BadgeContainer from "./BadgeContainer";
const regularTournamentWinners: {
export const regularTournamentBadges: {
badgeName: string;
name: string;
altNames?: string[];
winnerDiscordIds: string;
}[] = [
{
badgeName: "zones",
name: "Dapple SZ Speedladder",
altNames: ["Dapple SZ Ladder"],
winnerDiscordIds:
"393411373289177098,300073469503340546,554124226915860507,417489824589676548,403345464138661888,343375632819814400,726389792237027370,570288931321413653,716227192060641281",
},
@ -23,12 +25,14 @@ const regularTournamentWinners: {
{
badgeName: "pair",
name: "League Rush (Pair)",
altNames: ["League Rush!!"],
winnerDiscordIds:
"453753483427053568,398818695608270849,776911543216111648,393908122525368331",
},
{
badgeName: "quad",
name: "League Rush (Quad)",
altNames: ["League Rush!!"],
winnerDiscordIds:
"105390854063034368,151192098962407424,147036636608331779,260602342309756940,115572122482507782,109804061900992512,169184589200359424",
},
@ -197,7 +201,7 @@ const usersBadges = ({
// Other tournaments
for (const tournament of regularTournamentWinners) {
for (const tournament of regularTournamentBadges) {
const count = tournament.winnerDiscordIds
.split(",")
.reduce(

View File

@ -1,28 +1,91 @@
import { Button } from "@chakra-ui/button";
import { Input, InputGroup, InputLeftElement } from "@chakra-ui/input";
import { Box } from "@chakra-ui/layout";
import {
Badge,
Popover,
PopoverContent,
PopoverTrigger,
} from "@chakra-ui/react";
import { t, Trans } from "@lingui/macro";
import EventInfo from "components/calendar/EventInfo";
import { EventModal, FormData } from "components/calendar/EventModal";
import Calendar from "components/common/Calendar";
import MyHead from "components/common/MyHead";
import SubText from "components/common/SubText";
import { useMyTheme, useUser } from "hooks/common";
import { ssr } from "pages/api/trpc/[trpc]";
import { Fragment, useState } from "react";
import { Fragment, ReactNode, useMemo, useState } from "react";
import { FiSearch } from "react-icons/fi";
import { trpc } from "utils/trpc";
const CalendarPage = () => {
const { gray } = useMyTheme();
const { gray, secondaryBgColor } = useMyTheme();
const events = trpc.useQuery(["calendar.events"], { enabled: false });
const [eventToEdit, setEventToEdit] = useState<
boolean | (FormData & { id: number })
>(false);
const [filter, setFilter] = useState("");
const [{ month, year }, setMonthYear] = useState({
month: new Date().getMonth() + 1,
year: new Date().getFullYear(),
});
const [user] = useUser();
let lastPrintedDate: [number, number, Date] | null = null;
const scrollToEvent = (id: number) => {
document
.getElementById(`event-${id}`)
?.scrollIntoView({ behavior: "smooth" });
};
const eventsInFuture = Boolean(
events.data?.some(
(event) =>
event.date.getMonth() + 1 > month || event.date.getFullYear() > year
)
);
const calendarDateContents = useMemo(() => {
return (events.data ?? []).reduce(
(result: Record<string, ReactNode[]>, event) => {
const key = `${event.date.getDate()}-${
event.date.getMonth() + 1
}-${event.date.getFullYear()}`;
const node = (
<>
<Button
display="block"
mx="auto"
size="xs"
variant="ghost"
textOverflow="ellipsis"
maxW="150px"
width="100%"
height="2rem"
mt="0.25rem"
mb="0.5rem"
overflow="hidden"
onClick={() => {
scrollToEvent(event.id);
}}
>
<Badge display="block" size="xs" colorScheme="gray" mb="0.25rem">
{event.date.toLocaleTimeString("en", { hour: "numeric" })}
</Badge>
{event.name}
</Button>
</>
);
if (result[key]) result[key].push(node);
else result[key] = [node];
return result;
},
{}
);
}, []);
return (
<>
<MyHead title={t`Calendar`} />
@ -34,7 +97,7 @@ const CalendarPage = () => {
/>
)}
{user && (
<div>
<Box mb={6}>
<Button
size="sm"
onClick={() => setEventToEdit(true)}
@ -42,17 +105,33 @@ const CalendarPage = () => {
>
<Trans>Add event</Trans>
</Button>
</div>
</Box>
)}
<InputGroup my={8} maxW="24rem" mx="auto">
<InputLeftElement pointerEvents="none">
<FiSearch color={gray} />
</InputLeftElement>
<Input value={filter} onChange={(e) => setFilter(e.target.value)} />
</InputGroup>
<Calendar
current={{ month, year }}
min={{ month: 7, year: 2021 }}
handleNextClick={() =>
setMonthYear(
month === 12
? { month: 1, year: year + 1 }
: { month: month + 1, year }
)
}
handleBackClick={() =>
setMonthYear(
month === 1
? { month: 12, year: year - 1 }
: { month: month - 1, year }
)
}
showNextButton={eventsInFuture}
dateContents={calendarDateContents}
/>
{(events.data ?? [])
.filter((event) =>
event.name.toLowerCase().includes(filter.toLowerCase().trim())
.filter(
(event) =>
event.date.getMonth() + 1 === month &&
event.date.getFullYear() === year
)
.map((event, i) => {
const printDateHeader =
@ -74,6 +153,13 @@ const CalendarPage = () => {
lastPrintedDate![2].getDate() === now.getDate() &&
lastPrintedDate![2].getMonth() === now.getMonth();
const timeUntilDateInDays = ((date: Date) => {
const now = new Date();
const diff = date.getTime() - now.getTime();
const day = Math.floor(diff / (1000 * 3600 * 24));
return day;
})(lastPrintedDate![2]);
return (
<Fragment key={event.id}>
{printDateHeader && (
@ -86,6 +172,9 @@ const CalendarPage = () => {
weekday: "long",
})}{" "}
{isToday && <Trans>(Today)</Trans>}
{timeUntilDateInDays > 1 && (
<>(In {timeUntilDateInDays} days)</>
)}
</SubText>
</Box>
)}