diff --git a/src/components/onboarding/index.jsx b/src/components/onboarding/index.jsx index cb15f2d522c7d43520774a2516b68782e76f4e22..3ad27e5d728d3db6078973d46e23d3c4a23797cf 100644 --- a/src/components/onboarding/index.jsx +++ b/src/components/onboarding/index.jsx @@ -78,6 +78,38 @@ export const steps = [ </Chip> . </p> + <p> + U příspěvků se též zobrazuje celková míra podpory. Legenda barevného + odlišení je následující: + </p> + + <ul className="unordered-list unordered-list--dense"> + <li> + <div className="px-1 text-sm font-bold inline-block bg-green-400 text-white"> + Zelenou + </div>{" "} + je označen příspěvek, na kterém je konsensus, nebo takový, který + získal podporu skupiny členů. + </li> + <li> + <div className="px-1 text-sm font-bold inline-block bg-yellow-400 text-grey-300"> + Žlutou + </div>{" "} + je označen příspěvek, který podporu teprve sbírá. + </li> + <li> + <div className="px-1 text-sm font-bold inline-block bg-red-600 text-white"> + Červeně + </div>{" "} + je označen příspěvek, který má spíše negativní odezvu. + </li> + <li> + <div className="px-1 text-sm font-bold inline-block bg-grey-125 text-grey-200"> + Šedivě + </div>{" "} + je označen příspěvek, který zatím není ohodnocen. + </li> + </ul> </div> </> ), diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx index 3e3a5e698fcf584d030857b3ba6986c178e382ed..3f178c7a59858df03b8a5af9bf7821209029db78 100644 --- a/src/components/posts/Post.jsx +++ b/src/components/posts/Post.jsx @@ -5,6 +5,7 @@ import { format, isToday } from "date-fns"; import Chip from "components/Chip"; import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu"; +import PostScore from "components/posts/PostScore"; import Thumbs from "components/Thumbs"; const Post = ({ @@ -20,6 +21,7 @@ const Post = ({ state, dimIfArchived = true, currentUser, + supportThreshold, canThumb, onLike, onDislike, @@ -82,28 +84,53 @@ const Post = ({ labels.push( { pending: ( - <Chip key="state__pending" condensed color="grey-500"> + <Chip + key="state__pending" + condensed + color="grey-500" + title="Návrh čekající na zpracování" + > Čeká na zpracování </Chip> ), announced: ( - <Chip key="state__announced" condensed color="blue-300"> + <Chip + key="state__announced" + condensed + color="blue-300" + title="Návrh k hlasování" + > K hlasování </Chip> ), accepted: ( - <Chip key="state__accepted" condensed color="green-400"> + <Chip + key="state__accepted" + condensed + color="green-400" + title="Schválený návrh" + > Schválený </Chip> ), rejected: ( - <Chip key="state__rejected" condensed color="red-600"> + <Chip + key="state__rejected" + condensed + color="red-600" + title="Zamítnutý návrh" + > Zamítnutý </Chip> ), "rejected-by-chairman": ( - <Chip key="state__rejected-by-chairmen" condensed color="red-600"> - Zamítnutý předsedajícím + <Chip + key="state__rejected-by-chairmen" + condensed + color="red-600" + title="Návrh zamítnutý předsedajícím" + > + Zamítnutý předs. </Chip> ), }[state] @@ -159,8 +186,7 @@ const Post = ({ __html: content, }; - const thumbsEnabled = - canThumb && !archived && (type === "post" || state === "announced"); + const thumbsVisible = !archived && (type === "post" || state === "announced"); return ( <div className={wrapperClassName} ref={ref}> @@ -177,14 +203,14 @@ const Post = ({ <span className="font-bold">{author.name}</span> <div className="mt-1 xl:mt-0 xl:ml-2 leading-tight"> <span className="text-grey-200 text-sm">{author.group}</span> - <span className="text-grey-200 ml-1 text-sm"> + <span className="text-grey-200 ml-1 text-xs"> @{" "} {format( datetime, isToday(datetime) ? "H:mm" : "dd. MM. H:mm" )} {modified && ( - <span className="text-grey-200 text-sm block md:inline md:ml-2"> + <span className="text-grey-200 text-xs block md:inline md:ml-2"> (upraveno) </span> )} @@ -196,13 +222,22 @@ const Post = ({ </div> </div> <div className="flex items-center"> - <Thumbs - likes={ranking.likes} - dislikes={ranking.dislikes} - readOnly={!thumbsEnabled} - onLike={onLike} - onDislike={onDislike} - myVote={ranking.myVote} + {thumbsVisible && ( + <Thumbs + likes={ranking.likes} + dislikes={ranking.dislikes} + readOnly={!canThumb} + onLike={onLike} + onDislike={onDislike} + myVote={ranking.myVote} + /> + )} + <PostScore + className="ml-2" + score={ranking.score} + hasDislikes={ranking.dislikes > 0} + rankingReadonly={!thumbsVisible} + supportThreshold={supportThreshold} /> {showActions && ( <DropdownMenu right className="pl-4"> diff --git a/src/components/posts/PostList.jsx b/src/components/posts/PostList.jsx index 48caa5b08f0b6979e63414e3c789b4063c1465f4..1a8869d429b02f1c1de6122bed7007402741d4e8 100644 --- a/src/components/posts/PostList.jsx +++ b/src/components/posts/PostList.jsx @@ -8,6 +8,7 @@ const PostList = ({ items, showAddPostCta, currentUser, + supportThreshold, canThumb, dimArchived, onLike, @@ -67,6 +68,7 @@ const PostList = ({ archived={item.archived} dimIfArchived={dimArchived} currentUser={currentUser} + supportThreshold={supportThreshold} canThumb={canThumb} onLike={onPostLike(item)} onDislike={onPostDislike(item)} diff --git a/src/components/posts/PostScore.jsx b/src/components/posts/PostScore.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9085a43c77bb163ee061a1abaef040efa5c78a25 --- /dev/null +++ b/src/components/posts/PostScore.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import classNames from "classnames"; + +const PostScore = ({ + score, + hasDislikes, + supportThreshold, + rankingReadonly, + className, +}) => { + const coloring = rankingReadonly + ? "bg-grey-125 text-grey-200" + : { + "bg-red-600 text-white": score < 0, + "bg-grey-125 text-grey-200": score === 0, + "bg-yellow-400 text-grey-300": + score > 0 && hasDislikes && score < supportThreshold, + "bg-green-400 text-white": + score >= supportThreshold || (score > 0 && !hasDislikes), + }; + + return ( + <span + className={classNames( + "p-1 text-sm flex items-center space-x-1", + coloring, + className + )} + title={`Míra podpory je ${score}.`} + > + <i className="ico--power" /> + <span className="font-bold">{score}</span> + </span> + ); +}; + +export default React.memo(PostScore); diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx index 6995acd773864195fb1b463aa2485fd5177ae480..998f721ab302fad2204b2a47eb07db8accfd7f9c 100644 --- a/src/containers/PostsContainer.jsx +++ b/src/containers/PostsContainer.jsx @@ -22,7 +22,7 @@ import ModalWithActions from "components/modals/ModalWithActions"; import PostEditModal from "components/posts/PostEditModal"; import PostList from "components/posts/PostList"; import { useActionState, useItemActionConfirm } from "hooks"; -import { AuthStore, PostStore } from "stores"; +import { AuthStore, GlobalInfoStore, PostStore } from "stores"; const PostsContainer = ({ className, showAddPostCta }) => { const [ @@ -98,13 +98,18 @@ const PostsContainer = ({ className, showAddPostCta }) => { archive, })); - const { isAuthenticated, user } = AuthStore.useState(); + const { isAuthenticated, user } = AuthStore.useState((state) => + pick(state, ["isAuthenticated", "user"]) + ); const { window, items } = PostStore.useState((state) => pick(state, ["window", "items"]) ); const showingArchivedOnly = PostStore.useState( (state) => state.filters.flags === "archived" ); + const groupSizeHalf = GlobalInfoStore.useState( + (state) => state.groupSizeHalf + ); const [acceptingProposal, acceptingProposalError] = useActionState( acceptProposal, @@ -184,6 +189,7 @@ const PostsContainer = ({ className, showAddPostCta }) => { className={className} dimArchived={!showingArchivedOnly} currentUser={user} + supportThreshold={groupSizeHalf} onHide={setPostToHide} onBanUser={onBanUser} onUnbanUser={onUnbanUser} diff --git a/src/onboarding.jsx b/src/onboarding.jsx deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/stores.js b/src/stores.js index ef240f8d7823ea62bcaff2b4ca1712c803602818..fe8ab11f2adbc615ff78afae6719c2d72d2977e0 100644 --- a/src/stores.js +++ b/src/stores.js @@ -6,6 +6,7 @@ const globalInfoStoreInitial = { connectionState: "connecting", onlineMembers: 0, onlineUsers: 0, + groupSizeHalf: null, websocketUrl: null, streamUrl: null, protocolUrl: null, diff --git a/src/ws/handlers/global.js b/src/ws/handlers/global.js index 07b00f6fe8edc0db4f24bdcb504fc6129535cb6e..ddf8d4ed9c86075e08262c136326c9c49bde6abc 100644 --- a/src/ws/handlers/global.js +++ b/src/ws/handlers/global.js @@ -6,5 +6,8 @@ export const handleOnlineUsersUpdated = (payload) => { GlobalInfoStore.update((state) => { state.onlineUsers = isNumber(payload.all) ? payload.all : 0; state.onlineMembers = isNumber(payload.members) ? payload.members : 0; + state.groupSizeHalf = isNumber(payload.group_size_half) + ? payload.group_size_half + : null; }); }; diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts index a2f97abef673408e75893b4bb36ca82294fc5ccc..8d14ea8040adc4133b53a5d2843ddc311b3b3d14 100644 --- a/typings/cf2021.d.ts +++ b/typings/cf2021.d.ts @@ -1,8 +1,10 @@ declare namespace CF2021 { export interface GlobalInfoStorePayload { connectionState: "connected" | "offline" | "connecting"; + onlineMembers: number; onlineUsers: number; websocketUrl: string; + groupSizeHalf?: number; streamUrl?: string; protocolUrl?: string; protocol?: string;