Poll: Support date in vote (#6760)

This commit is contained in:
Alexander Zinchuk 2026-03-31 11:28:46 +02:00
parent aa44302ed4
commit ac06cb6e80
5 changed files with 50 additions and 28 deletions

View File

@ -12,7 +12,10 @@
display: flex; display: flex;
align-items: center; align-items: center;
padding: 1rem 0.75rem 0.5rem 1rem; padding-top: 1rem;
padding-bottom: 0.5rem;
padding-inline-start: 1rem;
padding-inline-end: 0.75rem;
font-size: 0.9375rem; font-size: 0.9375rem;
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
@ -38,7 +41,6 @@
.poll-voters { .poll-voters {
position: relative; position: relative;
min-height: 3rem; min-height: 3rem;
padding: 0 0.75rem;
.Spinner { .Spinner {
--spinner-size: 1.25rem; --spinner-size: 1.25rem;
@ -50,6 +52,11 @@
} }
.chat-item-clickable { .chat-item-clickable {
.ListItem-button {
padding-inline-start: 1rem;
padding-inline-end: 0.75rem;
}
.ChatInfo .Avatar.size-tiny { .ChatInfo .Avatar.size-tiny {
margin-right: 1.75rem; margin-right: 1.75rem;
} }
@ -60,6 +67,11 @@
margin-left: 1.75rem; margin-left: 1.75rem;
} }
} }
.vote-date {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
} }
.ShowMoreButton { .ShowMoreButton {

View File

@ -11,8 +11,10 @@ import type {
ApiPollAnswer, ApiPollAnswer,
ApiPollResult, ApiPollResult,
} from '../../api/types'; } from '../../api/types';
import type { PollVote } from '../../global/types/tabState';
import { selectTabState } from '../../global/selectors'; import { selectTabState } from '../../global/selectors';
import { formatMediaDateTime } from '../../util/dates/dateFormat';
import { isUserId } from '../../util/entities/ids'; import { isUserId } from '../../util/entities/ids';
import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities'; import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities';
@ -36,7 +38,7 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
voters?: string[]; votes?: PollVote[];
offset: string; offset: string;
}; };
@ -49,7 +51,7 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
answer, answer,
answerVote, answerVote,
totalVoters, totalVoters,
voters, votes,
offset, offset,
}) => { }) => {
const { const {
@ -60,7 +62,7 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
const prevVotersCount = usePreviousDeprecated<number>(answerVote.votersCount); const prevVotersCount = usePreviousDeprecated<number>(answerVote.votersCount);
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const areVotersLoaded = Boolean(voters); const areVotersLoaded = Boolean(votes);
const { option, text } = answer; const { option, text } = answer;
const lang = useOldLang(); const lang = useOldLang();
@ -83,7 +85,7 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
setIsLoading(false); setIsLoading(false);
}, [voters]); }, [votes]);
const handleMemberClick = useCallback((id: string) => { const handleMemberClick = useCallback((id: string) => {
openChat({ id }); openChat({ id });
@ -91,7 +93,7 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
}, [closePollResults, openChat]); }, [closePollResults, openChat]);
function renderViewMoreButton() { function renderViewMoreButton() {
const leftVotersCount = answerVote.votersCount - voters!.length; const leftVotersCount = answerVote.votersCount - votes!.length;
return answerVote.votersCount > INITIAL_LIMIT && leftVotersCount > 0 && ( return answerVote.votersCount > INITIAL_LIMIT && leftVotersCount > 0 && (
<ShowMoreButton <ShowMoreButton
@ -106,32 +108,34 @@ const PollAnswerResults: FC<OwnProps & StateProps> = ({
return ( return (
<div className="PollAnswerResults"> <div className="PollAnswerResults">
<div className="poll-voters"> <div className="poll-voters">
{voters {votes
? voters.map((id) => ( ? votes.map(({ peerId, date }) => (
<ListItem <ListItem
key={id} key={peerId}
className="chat-item-clickable" className="chat-item-clickable"
onClick={() => handleMemberClick(peerId)}
onClick={() => handleMemberClick(id)}
> >
{isUserId(id) ? ( {isUserId(peerId) ? (
<PrivateChatInfo <PrivateChatInfo
avatarSize="tiny" avatarSize="tiny"
userId={id} userId={peerId}
forceShowSelf forceShowSelf
noStatusOrTyping noStatusOrTyping
/> />
) : ( ) : (
<GroupChatInfo <GroupChatInfo
avatarSize="tiny" avatarSize="tiny"
chatId={id} chatId={peerId}
noStatusOrTyping noStatusOrTyping
/> />
)} )}
<span className="vote-date">
{formatMediaDateTime(lang, date * 1000, true)}
</span>
</ListItem> </ListItem>
)) ))
: <Loading />} : <Loading />}
{voters && renderViewMoreButton()} {votes && renderViewMoreButton()}
</div> </div>
<div className="answer-head" dir={lang.isRtl ? 'rtl' : undefined}> <div className="answer-head" dir={lang.isRtl ? 'rtl' : undefined}>
<span className="answer-title" dir="auto"> <span className="answer-title" dir="auto">
@ -155,10 +159,10 @@ function getPercentage(value: number, total: number) {
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { answer }: OwnProps): Complete<StateProps> => { (global, { answer }: OwnProps): Complete<StateProps> => {
const { voters, offsets } = selectTabState(global).pollResults; const { votesByOption, offsets } = selectTabState(global).pollResults;
return { return {
voters: voters?.[answer.option], votes: votesByOption?.[answer.option],
offset: (offsets?.[answer.option]) || '', offset: (offsets?.[answer.option]) || '',
}; };
}, },

View File

@ -52,6 +52,7 @@ import {
partition, partition,
split, split,
unique, unique,
uniqueByField,
} from '../../../util/iteratees'; } from '../../../util/iteratees';
import { getMessageKey, isLocalMessageId } from '../../../util/keys/messageKey'; import { getMessageKey, isLocalMessageId } from '../../../util/keys/messageKey';
import { getTranslationFn, type RegularLangFnParameters } from '../../../util/localization'; import { getTranslationFn, type RegularLangFnParameters } from '../../../util/localization';
@ -1477,17 +1478,17 @@ addActionHandler('loadPollOptionResults', async (global, actions, payload): Prom
const tabState = selectTabState(global, tabId); const tabState = selectTabState(global, tabId);
const { pollResults } = tabState; const { pollResults } = tabState;
const { voters } = tabState.pollResults; const { votesByOption } = pollResults;
const existingVotes = !shouldResetVoters && votesByOption?.[option] ? votesByOption[option] : [];
const newVotes = uniqueByField([...existingVotes, ...result.votes], 'peerId');
global = updateTabState(global, { global = updateTabState(global, {
pollResults: { pollResults: {
...pollResults, ...pollResults,
voters: { votesByOption: {
...voters, ...votesByOption,
[option]: unique([ [option]: newVotes,
...(!shouldResetVoters && voters?.[option] ? voters[option] : []),
...result.votes.map((vote) => vote.peerId),
]),
}, },
offsets: { offsets: {
...(pollResults.offsets ? pollResults.offsets : {}), ...(pollResults.offsets ? pollResults.offsets : {}),

View File

@ -297,7 +297,7 @@ addActionHandler('openPollResults', (global, actions, payload): ActionReturnType
pollResults: { pollResults: {
chatId, chatId,
messageId, messageId,
voters: {}, votesByOption: {},
}, },
}, tabId); }, tabId);
setGlobal(global); setGlobal(global);
@ -307,7 +307,7 @@ addActionHandler('openPollResults', (global, actions, payload): ActionReturnType
pollResults: { pollResults: {
chatId, chatId,
messageId, messageId,
voters: {}, votesByOption: {},
}, },
}, tabId); }, tabId);
} }

View File

@ -102,6 +102,11 @@ import type { RegularLangFnParameters } from '../../util/localization';
import type { ProfileCollectionKey } from '../selectors/payments'; import type { ProfileCollectionKey } from '../selectors/payments';
import type { CallbackAction } from './actions'; import type { CallbackAction } from './actions';
export type PollVote = {
peerId: string;
date: number;
};
export type TabState = { export type TabState = {
id: number; id: number;
isBlurred?: boolean; isBlurred?: boolean;
@ -418,7 +423,7 @@ export type TabState = {
pollResults: { pollResults: {
chatId?: string; chatId?: string;
messageId?: number; messageId?: number;
voters?: Record<string, string[]>; // TODO Rename to `voterIds` votesByOption?: Record<string, PollVote[]>;
offsets?: Record<string, string>; offsets?: Record<string, string>;
}; };