mirror of
https://github.com/XFox111/bonch-calendar.git
synced 2026-06-30 10:52:41 +03:00
174 lines
6.0 KiB
TypeScript
174 lines
6.0 KiB
TypeScript
import { LargeTitle, Subtitle1, Label, Dropdown, Button, Subtitle2, Body1, Option } from "@fluentui/react-components";
|
|
import { mergeClasses, useArrowNavigationGroup } from "@fluentui/react-components";
|
|
import type { SelectionEvents, OptionOnSelectData } from "@fluentui/react-components";
|
|
import { Copy24Regular, ArrowDownload24Regular, Checkmark24Regular } from "@fluentui/react-icons";
|
|
import { Slide, Stagger } from "@fluentui/react-motion-components-preview";
|
|
import { use, useCallback, useMemo, useState, type ReactElement } from "react";
|
|
import useTimeout from "../hooks/useTimeout";
|
|
import useStyles_MainView from "./MainView.styles";
|
|
import { fetchFaculties, fetchGroups } from "../utils/api";
|
|
import strings from "../utils/strings";
|
|
import { v7 as uuid7 } from "uuid";
|
|
|
|
const facultiesPromise = fetchFaculties().then(Object.entries);
|
|
|
|
const getEntryOrEmpty = (entries: [string, string][], key: string): string =>
|
|
entries.find(i => i[0] === key)?.[1] ?? "";
|
|
|
|
export default function MainView(): ReactElement
|
|
{
|
|
const faculties: [string, string][] = use(facultiesPromise);
|
|
const [facultyId, setFacultyId] = useState<string>("");
|
|
|
|
const courses: number[] = useMemo(() => facultyId == "56682" ? [1, 2] : [1, 2, 3, 4, 5], [facultyId]);
|
|
const [course, setCourse] = useState<number>(0);
|
|
|
|
const [groups, setGroups] = useState<[string, string][] | null>(null);
|
|
const [groupId, setGroupId] = useState<string>("");
|
|
|
|
const id = uuid7();
|
|
const icalUrl = useMemo(() => `${import.meta.env.VITE_BACKEND_HOST}/timetable/${facultyId}/${groupId}`, [groupId, facultyId]);
|
|
|
|
const [showCta, setShowCta] = useState<boolean>(false);
|
|
const [copyActive, triggerCopy] = useTimeout(3000);
|
|
|
|
const navAttributes = useArrowNavigationGroup({ axis: "horizontal" });
|
|
const cls = useStyles_MainView();
|
|
|
|
const copyLink = useCallback((): void =>
|
|
{
|
|
navigator.clipboard.writeText(icalUrl + "?id=" + id);
|
|
triggerCopy();
|
|
setShowCta(true);
|
|
}, [icalUrl, triggerCopy, id]);
|
|
|
|
const onFacultySelect = useCallback((_: SelectionEvents, data: OptionOnSelectData): void =>
|
|
{
|
|
if (data.optionValue === facultyId)
|
|
return;
|
|
|
|
setFacultyId(data.optionValue!);
|
|
setCourse(0);
|
|
setGroupId("");
|
|
setGroups(null);
|
|
}, [facultyId]);
|
|
|
|
const onCourseSelect = useCallback((courseNumber: number): void =>
|
|
{
|
|
if (courseNumber === course)
|
|
return;
|
|
|
|
setCourse(courseNumber);
|
|
setGroupId("");
|
|
setGroups(null);
|
|
fetchGroups(facultyId, courseNumber).then(Object.entries).then(setGroups);
|
|
}, [course, facultyId]);
|
|
|
|
return (
|
|
<section className={ cls.root }>
|
|
<header className={ cls.stack }>
|
|
<LargeTitle as="h1">
|
|
{ strings.formatString(strings.title_p1, <span className={ cls.highlight }>{ strings.title_p2 }</span>) }
|
|
</LargeTitle>
|
|
<Subtitle1 as="p">
|
|
{ strings.formatString(strings.subtitle_p1, <span className={ cls.highlight }>{ strings.subtitle_p2 }</span>) }
|
|
</Subtitle1>
|
|
</header>
|
|
<div className={ mergeClasses(cls.stack, cls.form) }>
|
|
<Slide visible appear>
|
|
<div className={ cls.stack }>
|
|
<Label htmlFor="faculty">{ strings.pickFaculty }</Label>
|
|
<Dropdown id="faculty"
|
|
value={ getEntryOrEmpty(faculties, facultyId) }
|
|
onOptionSelect={ onFacultySelect }
|
|
className={ cls.field }
|
|
positioning={ { pinned: true, position: "below" } }
|
|
button={
|
|
<span className={ cls.truncatedText }>{ getEntryOrEmpty(faculties, facultyId) }</span>
|
|
}>
|
|
|
|
{ faculties.map(([id, name]) =>
|
|
<Option key={ id } value={ id }>{ name }</Option>
|
|
) }
|
|
</Dropdown>
|
|
</div>
|
|
</Slide>
|
|
<Slide visible={ facultyId !== "" }>
|
|
<div className={ mergeClasses(cls.stack, facultyId === "" && cls.hidden) }>
|
|
<Label>{ strings.pickCourse }</Label>
|
|
<div { ...navAttributes }>
|
|
{ courses.map(i =>
|
|
<Button key={ i }
|
|
className={ cls.courseButton }
|
|
appearance={ course === i ? "primary" : "secondary" }
|
|
disabled={ facultyId === "" }
|
|
onClick={ () => onCourseSelect(i) }>
|
|
|
|
{ i }
|
|
</Button>
|
|
) }
|
|
</div>
|
|
</div>
|
|
</Slide>
|
|
<Slide visible={ course !== 0 && groups !== null }>
|
|
<div className={ mergeClasses(cls.stack, course === 0 && cls.hidden) }>
|
|
<Label as="label" htmlFor="group">{ strings.pickGroup }</Label>
|
|
<Dropdown id="group"
|
|
className={ cls.field }
|
|
positioning={ { pinned: true, position: "below" } }
|
|
value={ getEntryOrEmpty(groups ?? [], groupId) }
|
|
disabled={ course === 0 || groups === null }
|
|
onOptionSelect={ (_, e) => setGroupId(e.optionValue!) }>
|
|
|
|
{ groups?.map(([id, name]) =>
|
|
<Option key={ id } value={ id }>{ name }</Option>
|
|
) }
|
|
{ (groups?.length ?? 0) < 1 &&
|
|
<Option disabled>{ strings.pickGroup_empty }</Option>
|
|
}
|
|
</Dropdown>
|
|
</div>
|
|
</Slide>
|
|
</div>
|
|
<div className={ cls.stack }>
|
|
<Stagger visible={ groupId !== "" }>
|
|
<Slide>
|
|
<div className={ mergeClasses(cls.stack, groupId === "" && cls.hidden) }>
|
|
<Subtitle2>{ strings.subscribe }</Subtitle2>
|
|
<Button
|
|
onClick={ copyLink }
|
|
className={ mergeClasses(cls.field, copyActive && cls.copiedStyle) }
|
|
iconPosition="after"
|
|
title={ strings.copy }
|
|
disabled={ groupId === "" }
|
|
icon={ copyActive
|
|
? <Checkmark24Regular className={ cls.copyIcon } />
|
|
: <Copy24Regular className={ cls.copyIcon } />
|
|
}>
|
|
|
|
<span className={ cls.truncatedText }>{ icalUrl + "?id=" + id }</span>
|
|
</Button>
|
|
</div>
|
|
</Slide>
|
|
<Slide>
|
|
<div className={ mergeClasses(cls.stack, groupId === "" && cls.hidden) }>
|
|
<Body1>{ strings.or }</Body1>
|
|
<Button as="a"
|
|
appearance="subtle" icon={ <ArrowDownload24Regular /> }
|
|
onClick={ () => setShowCta(true) }
|
|
disabled={ groupId === "" }
|
|
href={ icalUrl + "?id=download" }>
|
|
|
|
{ strings.download }
|
|
</Button>
|
|
</div>
|
|
</Slide>
|
|
</Stagger>
|
|
</div>
|
|
<Slide visible={ showCta }>
|
|
<Subtitle2 as="p">{ strings.cta }</Subtitle2>
|
|
</Slide>
|
|
</section>
|
|
);
|
|
}
|