export default function RoomOutcome()

in projects/deliberation_at_scale/packages/frontend/components/RoomOutcome.tsx [32:159]


export default function RoomOutcome(props: Props) {
    const { _ } = useLingui();
    const { outcome, participantId, participants, variant } = props;
    const { id: outcomeId, content = '', type } = outcome ?? {};
    const { setOpinion, getExistingOpinion, getGroupOpinions } = useGiveOpinion({ subjects: [outcome], participantId });
    const [selectedOpinionOptionType, setSelectedOpinionOptionType] = useState<OpinionOptionType | undefined | null>();
    const existingOpinion = getExistingOpinion(outcomeId);
    const groupOpinions = getGroupOpinions(outcomeId);
    const [timeoutCompleted, setTimeoutCompleted] = useState(false);
    const title = useMemo(() => {
        switch (type) {
            case OutcomeType.Consensus: return _(msg`Consensus Proposal`);
            case OutcomeType.Milestone: return _(msg`Milestone`);
            case OutcomeType.OffTopic: return _(msg`Off Topic`);
            case OutcomeType.CrossPollination: return _(msg`Statement`);
            case OutcomeType.SeedStatement: return _(msg`Statement`);
            default: return _(msg`Statement`);
        }
    }, [type, _]);
    const timeoutMs = useMemo(() => {
        if (!type || !!existingOpinion) {
            return 0;
        }

        return OUTCOME_OPINION_TIMEOUT_MS_LOOKUP[type];
    }, [type, existingOpinion]);
    const hasTimeout = timeoutMs > 0;
    const opinionOptions = useMemo(() => {
        switch (type) {
            case OutcomeType.Consensus:
            case OutcomeType.CrossPollination:
            case OutcomeType.SeedStatement:
            default:
                return [
                    {
                        content: _(msg`Agree`),
                        icon: faCheck,
                        optionType: OpinionOptionType.AgreeConsensus,
                    },
                    {
                        content: _(msg`Disagree`),
                        icon: faTimes,
                        optionType: OpinionOptionType.DisagreeConsensus,
                    },
                    {
                        content: _(msg`Pass`),
                        icon: faTimes,
                        optionType: OpinionOptionType.Wrong,
                    },
                ];
        }

    }, [type, _]);
    const hasOpinionOptions = (opinionOptions.length > 0);
    const formattedContent = content?.trim();

    // handle updates from the database which should reset the optimistic selected option
    useEffect(() => {
        if (existingOpinion && existingOpinion.option_type === selectedOpinionOptionType) {
            setSelectedOpinionOptionType(null);
        }
    }, [existingOpinion, selectedOpinionOptionType]);

    if (!outcome) {
        return null;
    }

    return (
        <motion.div
            layoutId={outcomeId}
            className="gap-2 md:gap-4 flex flex-col items-start"
            initial={{ opacity: 0, y: 40 }}
            animate={{ opacity: 1, y: 0 }}
        >
            <Pill icon={statementSolid} className="border-black hidden md:inline-flex">{title}</Pill>
            <span className="md:hidden font-bold">{title}:</span>
            <ReactMarkdown>{formattedContent}</ReactMarkdown>
            {hasOpinionOptions && (
                <div className={classNames(
                    "flex gap-1 md:gap-2 w-full flex-wrap",
                    variant === "compact" && "flex-row",
                    variant === "spacious" && "flex-col",
                    variant === "spacious" && "flex-col"
                )}>
                    {opinionOptions.map((option) => {
                        const { content, icon, optionType } = option;
                        const isSelected = (existingOpinion?.option_type === optionType && !selectedOpinionOptionType) || selectedOpinionOptionType === optionType;
                        const isDisabled = (timeoutCompleted && DISABLE_OPINION_INPUT_WHEN_TIMED_OUT);
                        const participantAmount = participants?.length ?? 0;
                        const otherOptionOpinions = groupOpinions.filter((opinion) => opinion.option_type === optionType && opinion.participant_id != participantId);
                        const progress = (participantAmount > 0) ? ((otherOptionOpinions.length + (isSelected ? 1 : 0)) / participantAmount) : 0;
                        const onOptionClick = () => {
                            setSelectedOpinionOptionType(optionType);
                            setOpinion({
                                subjectId: outcomeId,
                                type: OpinionType.Option,
                                optionType,
                            });
                        };

                        return (
                            <Button
                                key={optionType}
                                disabled={isDisabled}
                                selected={isSelected}
                                icon={icon}
                                onClick={onOptionClick}
                                className="flex-1"
                                progress={progress}
                            >
                                {content}
                            </Button>
                        );
                    })}
                </div>
            )}
            {hasTimeout && (
                <TimeProgressBar
                    durationMs={timeoutMs}
                    startReferenceTime={outcome.created_at}
                    onIsCompleted={(isCompleted) => {
                        setTimeoutCompleted(isCompleted);
                    }}
                />
            )}
        </motion.div>
    );
}