Skip to content
Snippets Groups Projects
Commit c2fe36f7 authored by xaralis's avatar xaralis
Browse files

feat: add schedule page, load schedule and use it

parent f8dc5b6a
No related branches found
No related tags found
No related merge requests found
Pipeline #1806 passed
REACT_APP_STYLEGUIDE_URL=http://localhost:3001 REACT_APP_STYLEGUIDE_URL=http://localhost:3001
REACT_APP_API_BASE_URL=https://cf2021.pirati.cz/api
...@@ -13783,6 +13783,11 @@ ...@@ -13783,6 +13783,11 @@
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==" "integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
}, },
"unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA=="
},
"unicode-canonical-property-names-ecmascript": { "unicode-canonical-property-names-ecmascript": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-modal": "^3.12.1", "react-modal": "^3.12.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.3" "react-scripts": "3.4.3",
"unfetch": "^4.2.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
......
...@@ -4,6 +4,7 @@ import { KeycloakProvider } from "@react-keycloak/web"; ...@@ -4,6 +4,7 @@ import { KeycloakProvider } from "@react-keycloak/web";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { loadGroupMappings } from "actions/misc"; import { loadGroupMappings } from "actions/misc";
import { loadProgram } from "actions/program";
import Footer from "components/Footer"; import Footer from "components/Footer";
import Navbar from "components/Navbar"; import Navbar from "components/Navbar";
import Home from "pages/Home"; import Home from "pages/Home";
...@@ -42,7 +43,7 @@ const LoadingComponent = ( ...@@ -42,7 +43,7 @@ const LoadingComponent = (
<div className="h-screen w-screen flex justify-center items-center"> <div className="h-screen w-screen flex justify-center items-center">
<div className="text-center"> <div className="text-center">
<img <img
src={`${process.env.REACT_APP_STYLEGUIDE_URL}images/logo-round-black.svg`} src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-black.svg`}
className="w-20 mb-2 inline-block" className="w-20 mb-2 inline-block"
alt="Pirátská strana" alt="Pirátská strana"
/> />
...@@ -54,6 +55,7 @@ const LoadingComponent = ( ...@@ -54,6 +55,7 @@ const LoadingComponent = (
const BaseApp = () => { const BaseApp = () => {
loadGroupMappings.read(); loadGroupMappings.read();
loadProgram.read();
return ( return (
<Router> <Router>
......
import { createAsyncAction, errorResult, successResult } from "pullstate"; import { createAsyncAction, errorResult, successResult } from "pullstate";
import fetch from "unfetch";
import { AuthStore } from "stores"; import { AuthStore } from "stores";
......
import pick from "lodash/pick";
import { createAsyncAction, errorResult, successResult } from "pullstate";
import fetch from "unfetch";
import { ProgramStore } from "stores";
export const loadProgram = createAsyncAction(
async () => {
try {
const resp = await fetch(`${process.env.REACT_APP_API_BASE_URL}/program`);
const mappings = await resp.json();
return successResult(mappings);
} catch (err) {
return errorResult([], err.toString());
}
},
{
postActionHook: ({ result }) => {
if (!result.error) {
const entries = result.payload
.map(
/**
*
* @param {any} entry
* @returns {CF2021.ProgramScheduleEntry}
*/
(entry) => {
return {
...pick(entry, [
"id",
"number",
"title",
"description",
"proposer",
]),
expectedStartAt: new Date(entry.expectedStartAt),
expectedFinishAt: entry.expectedFinishAt
? new Date(entry.expectedFinishAt)
: undefined,
};
}
)
.sort((a, b) => a.expectedStartAt - b.expectedStartAt);
const currentEntry = result.payload.find((entry) => entry.is_live);
ProgramStore.update((state) => {
state.schedule = entries;
if (currentEntry) {
state.current = state.schedule.find(
(scheduleEntry) => scheduleEntry.id === currentEntry.id
);
} else {
// TODO: for testing only
state.current = state.schedule[1];
}
});
}
},
}
);
import React from "react"; import React from "react";
import { NavLink } from "react-router-dom";
import classNames from "classnames"; import classNames from "classnames";
const Button = ({ const Button = ({
...@@ -10,6 +11,7 @@ const Button = ({ ...@@ -10,6 +11,7 @@ const Button = ({
hoverActive = true, hoverActive = true,
fullwidth = false, fullwidth = false,
children, children,
routerTo,
...props ...props
}) => { }) => {
const btnClass = classNames( const btnClass = classNames(
...@@ -25,8 +27,7 @@ const Button = ({ ...@@ -25,8 +27,7 @@ const Button = ({
const iconWrapperClass = classNames("btn__icon", iconWrapperClassName); const iconWrapperClass = classNames("btn__icon", iconWrapperClassName);
return ( const inner = (
<button className={btnClass} {...props}>
<div className="btn__body-wrap"> <div className="btn__body-wrap">
<div className="btn__body">{children}</div> <div className="btn__body">{children}</div>
{!!icon && ( {!!icon && (
...@@ -36,6 +37,19 @@ const Button = ({ ...@@ -36,6 +37,19 @@ const Button = ({
</div> </div>
)} )}
</div> </div>
);
if (routerTo) {
return (
<NavLink to={routerTo} className={btnClass} {...props}>
{inner}
</NavLink>
);
}
return (
<button className={btnClass} {...props}>
{inner}
</button> </button>
); );
}; };
......
import React, { useState } from "react"; import React, { useState } from "react";
import Button from "components/Button";
import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu"; import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
import ModalConfirm from "components/modals/ModalConfirm"; import ModalConfirm from "components/modals/ModalConfirm";
import AddAnnouncementForm from "containers/AddAnnouncementForm"; import AddAnnouncementForm from "containers/AddAnnouncementForm";
...@@ -7,12 +8,33 @@ import AddPostForm from "containers/AddPostForm"; ...@@ -7,12 +8,33 @@ import AddPostForm from "containers/AddPostForm";
import AnnouncementsContainer from "containers/AnnoucementsContainer"; import AnnouncementsContainer from "containers/AnnoucementsContainer";
import PostFilters from "containers/PostFilters"; import PostFilters from "containers/PostFilters";
import PostsContainer from "containers/PostsContainer"; import PostsContainer from "containers/PostsContainer";
import { ProgramStore } from "stores";
const noCurrentDiscussion = (
<article className="container container--wide pt-8 py-8 lg:py-32">
<div className="hidden md:inline-block flag bg-violet-400 text-white head-alt-base mb-4 py-4 px-5">
Jejda ...
</div>
<h1 className="head-alt-base md:head-alt-md lg:head-alt-xl mb-2">
Jednání ještě nebylo zahájeno :(
</h1>
<p className="text-xl leading-snug mb-8">
Jednání celostátního fóra v tuto chvíli neprobíhá. Můžete si ale zobrazit
program.
</p>
<Button routerTo="/program" className="md:text-lg lg:text-xl" hoverActive>
Zobrazit program
</Button>
</article>
);
const Home = () => { const Home = () => {
const [showEndDiscussionConfirm, setShowEndDiscussionConfirm] = useState( const [showEndDiscussionConfirm, setShowEndDiscussionConfirm] = useState(
false false
); );
const { current } = ProgramStore.useState();
const onRenameProgramPoint = () => { const onRenameProgramPoint = () => {
console.log("renameProgramPoint"); console.log("renameProgramPoint");
}; };
...@@ -24,14 +46,17 @@ const Home = () => { ...@@ -24,14 +46,17 @@ const Home = () => {
console.log("endProgramPoint"); console.log("endProgramPoint");
}; };
if (!current) {
return noCurrentDiscussion;
}
return ( return (
<> <>
<article className="container container--wide pt-8 lg:py-24 cf2021"> <article className="container container--wide pt-8 lg:py-24 cf2021">
<section className="cf2021__video space-y-8"> <section className="cf2021__video space-y-8">
<div className="flex items-center justify-between mb-4 lg:mb-8"> <div className="flex items-center justify-between mb-4 lg:mb-8">
<h1 className="head-alt-md lg:head-alt-lg mb-0"> <h1 className="head-alt-md lg:head-alt-lg mb-0">
Bod č. 1: Programové priority Pirátské strany pro sněmovní volby Bod č. {current.number}: {current.title}
2021
</h1> </h1>
<DropdownMenu right triggerSize="lg"> <DropdownMenu right triggerSize="lg">
<DropdownMenuItem <DropdownMenuItem
......
import React from "react"; import React from "react";
import { Link } from "react-router-dom";
import classNames from "classnames";
import { format } from "date-fns";
import Chip from "components/Chip";
import { ProgramStore } from "stores";
const Schedule = () => { const Schedule = () => {
return <>Schedule</>; const { current, schedule } = ProgramStore.useState();
return (
<article className="container container--wide py-8 lg:py-24">
<h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedání</h1>
<div className="flex flex-col">
{schedule.map((entry) => {
const isCurrent = entry === current;
return (
<div
className={classNames(
"flex flex-col md:flex-row my-4 hover:opacity-100 transition duration-300",
{
"text-black": isCurrent,
"text-black opacity-50": !isCurrent,
}
)}
key={entry.id}
>
<div className="w-28 md:text-right">
{isCurrent && (
<Chip condensed className="mt-2 mr-2" color="red-600">
Právě probíhá
</Chip>
)}
</div>
<div className="w-32 flex items-start head-heavy-xs md:head-heavy-base text-center">
<span>{format(entry.expectedStartAt, "H:mm")}</span>
</div>
<div className="flex-grow w-full">
<h2 className="head-heavy-xs md:head-heavy-base mb-1">
<Link to="/">{entry.title}</Link>
</h2>
<div className="flex space-x-2">
<strong>Navrhovatel:</strong>
<span>{entry.proposer}</span>
</div>
{entry.description && <p>{entry.description}</p>}
</div>
</div>
);
})}
</div>
</article>
);
}; };
export default Schedule; export default Schedule;
...@@ -13,6 +13,14 @@ const authStoreInitial = { ...@@ -13,6 +13,14 @@ const authStoreInitial = {
export const AuthStore = new Store(authStoreInitial); export const AuthStore = new Store(authStoreInitial);
/** @type {CF2021.ProgramStorePayload} */
const programStoreInitial = {
current: undefined,
schedule: [],
};
export const ProgramStore = new Store(programStoreInitial);
/** @type {CF2021.AnnouncementStorePayload} */ /** @type {CF2021.AnnouncementStorePayload} */
const announcementStoreInitial = { const announcementStoreInitial = {
items: [ items: [
......
declare namespace CF2021 { declare namespace CF2021 {
interface ProgramScheduleEntry { interface ProgramScheduleEntry {
id: string; id: number;
number: string;
title: string; title: string;
proposer: string;
description?: string;
expectedStartAt: Date; expectedStartAt: Date;
expectedFinishAt: Date; expectedFinishAt?: Date;
} }
export interface ProgramStorePayload { export interface ProgramStorePayload {
current: ProgramScheduleEntry & { current?: ProgramScheduleEntry & {
discussionOpened: boolean; discussionOpened: boolean;
} }
schedule: ProgramScheduleEntry[]; schedule: ProgramScheduleEntry[];
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment