refactor: question components
This commit is contained in:
parent
ab6f17ffe0
commit
73a47d94c3
|
@ -3,57 +3,28 @@ import { QuestionFragment } from "../../search/queries.generated";
|
||||||
type QualityIndicator = QuestionFragment["qualityIndicators"];
|
type QualityIndicator = QuestionFragment["qualityIndicators"];
|
||||||
type IndicatorName = keyof QualityIndicator;
|
type IndicatorName = keyof QualityIndicator;
|
||||||
|
|
||||||
const formatQualityIndicator = (indicator: IndicatorName) => {
|
// this duplication can probably be simplified with typescript magic, but this is good enough for now
|
||||||
let result: string | null = null;
|
type UsedIndicatorName =
|
||||||
switch (indicator) {
|
| "volume"
|
||||||
case "numForecasts":
|
| "numForecasters"
|
||||||
result = null;
|
| "spread"
|
||||||
break;
|
| "sharesVolume"
|
||||||
|
| "liquidity"
|
||||||
|
| "tradeVolume"
|
||||||
|
| "openInterest";
|
||||||
|
|
||||||
case "stars":
|
const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
|
||||||
result = null;
|
// numForecasts: null,
|
||||||
break;
|
// stars: null,
|
||||||
|
// yesBid: "Yes bid",
|
||||||
case "volume":
|
// yesAsk: "Yes ask",
|
||||||
result = "Volume";
|
volume: "Volume",
|
||||||
break;
|
numForecasters: "Forecasters",
|
||||||
|
spread: "Spread",
|
||||||
case "numForecasters":
|
sharesVolume: "Shares vol.",
|
||||||
result = "Forecasters";
|
liquidity: "Liquidity",
|
||||||
break;
|
tradeVolume: "Volume",
|
||||||
|
openInterest: "Interest",
|
||||||
// case "yesBid":
|
|
||||||
// result = null; // "Yes bid"
|
|
||||||
// break;
|
|
||||||
|
|
||||||
// case "yesAsk":
|
|
||||||
// result = null; // "Yes ask"
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case "spread":
|
|
||||||
result = "Spread";
|
|
||||||
break;
|
|
||||||
case "sharesVolume":
|
|
||||||
result = "Shares vol.";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "openInterest":
|
|
||||||
result = "Interest";
|
|
||||||
break;
|
|
||||||
|
|
||||||
// case "resolution_data":
|
|
||||||
// result = null;
|
|
||||||
// break;
|
|
||||||
|
|
||||||
case "liquidity":
|
|
||||||
result = "Liquidity";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tradeVolume":
|
|
||||||
result = "Volume";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatNumber = (num) => {
|
const formatNumber = (num) => {
|
||||||
|
@ -66,27 +37,16 @@ const formatNumber = (num) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatQualityIndicators = (qualityIndicators: QualityIndicator) => {
|
|
||||||
let newQualityIndicators: { [k: string]: string | number } = {};
|
|
||||||
for (const key of Object.keys(qualityIndicators)) {
|
|
||||||
const newKey = formatQualityIndicator(key as IndicatorName);
|
|
||||||
if (newKey && qualityIndicators[key] !== null) {
|
|
||||||
newQualityIndicators[newKey] = qualityIndicators[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newQualityIndicators;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Display functions*/
|
/* Display functions*/
|
||||||
|
|
||||||
const getPercentageSymbolIfNeeded = ({
|
const getPercentageSymbolIfNeeded = ({
|
||||||
indicator,
|
indicator,
|
||||||
platform,
|
platform,
|
||||||
}: {
|
}: {
|
||||||
indicator: string;
|
indicator: UsedIndicatorName;
|
||||||
platform: string;
|
platform: string;
|
||||||
}) => {
|
}) => {
|
||||||
let indicatorsWhichNeedPercentageSymbol = ["Spread"];
|
let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
|
||||||
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
|
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
|
||||||
return "%";
|
return "%";
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,10 +58,15 @@ const getCurrencySymbolIfNeeded = ({
|
||||||
indicator,
|
indicator,
|
||||||
platform,
|
platform,
|
||||||
}: {
|
}: {
|
||||||
indicator: string;
|
indicator: UsedIndicatorName;
|
||||||
platform: string;
|
platform: string;
|
||||||
}) => {
|
}) => {
|
||||||
let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"];
|
const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
|
||||||
|
"volume",
|
||||||
|
"tradeVolume",
|
||||||
|
"openInterest",
|
||||||
|
"liquidity",
|
||||||
|
];
|
||||||
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
|
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
|
||||||
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
|
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
|
||||||
if (dollarPlatforms.includes(platform)) {
|
if (dollarPlatforms.includes(platform)) {
|
||||||
|
@ -114,66 +79,50 @@ const getCurrencySymbolIfNeeded = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showFirstQualityIndicator: React.FC<{
|
const FirstQualityIndicator: React.FC<{
|
||||||
question: QuestionFragment;
|
question: QuestionFragment;
|
||||||
showTimeStamp: boolean;
|
}> = ({ question }) => {
|
||||||
}> = ({ question, showTimeStamp }) => {
|
if (question.qualityIndicators.numForecasts) {
|
||||||
const lastUpdated = new Date(question.timestamp * 1000);
|
|
||||||
if (!!question.qualityIndicators.numForecasts) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex col-span-1 row-span-1">
|
<div className="flex">
|
||||||
{/*<span>{` ${numforecasts == 1 ? "Forecast" : "Forecasts:"}`}</span> */}
|
|
||||||
<span>Forecasts:</span>
|
<span>Forecasts:</span>
|
||||||
<span className="font-bold">
|
<span className="font-bold">
|
||||||
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
|
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (showTimeStamp) {
|
|
||||||
return (
|
|
||||||
<span className="hidden sm:flex items-center justify-center text-gray-600 mt-2">
|
|
||||||
<svg className="ml-4 mr-1 mt-1" height="10" width="16">
|
|
||||||
<circle cx="4" cy="4" r="4" fill="rgb(29, 78, 216)" />
|
|
||||||
</svg>
|
|
||||||
{`Last updated: ${
|
|
||||||
lastUpdated ? lastUpdated.toISOString().slice(0, 10) : "unknown"
|
|
||||||
}`}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayQualityIndicators: React.FC<{
|
const QualityIndicatorsList: React.FC<{
|
||||||
question: QuestionFragment;
|
question: QuestionFragment;
|
||||||
showTimeStamp: boolean;
|
}> = ({ question }) => {
|
||||||
}> = ({ question, showTimeStamp }) => {
|
|
||||||
const { qualityIndicators } = question;
|
|
||||||
return (
|
return (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
{showFirstQualityIndicator({
|
<FirstQualityIndicator question={question} />
|
||||||
question,
|
{Object.entries(question.qualityIndicators).map((entry, i) => {
|
||||||
showTimeStamp,
|
const indicatorLabel = qualityIndicatorLabels[entry[0]];
|
||||||
|
if (!indicatorLabel || entry[1] === null) return;
|
||||||
|
const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
|
||||||
|
const value = entry[1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={indicator}>
|
||||||
|
<span>{indicatorLabel}:</span>
|
||||||
|
<span className="font-bold">
|
||||||
|
{`${getCurrencySymbolIfNeeded({
|
||||||
|
indicator,
|
||||||
|
platform: question.platform.id,
|
||||||
|
})}${formatNumber(value)}${getPercentageSymbolIfNeeded({
|
||||||
|
indicator,
|
||||||
|
platform: question.platform.id,
|
||||||
|
})}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
{Object.entries(formatQualityIndicators(question.qualityIndicators)).map(
|
|
||||||
(entry, i) => {
|
|
||||||
return (
|
|
||||||
<div className="col-span-1 row-span-1">
|
|
||||||
<span>${entry[0]}:</span>
|
|
||||||
<span className="font-bold">
|
|
||||||
{`${getCurrencySymbolIfNeeded({
|
|
||||||
indicator: entry[0],
|
|
||||||
platform: question.platform.id,
|
|
||||||
})}${formatNumber(entry[1])}${getPercentageSymbolIfNeeded({
|
|
||||||
indicator: entry[0],
|
|
||||||
platform: question.platform.id,
|
|
||||||
})}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -235,16 +184,13 @@ function getStarsColor(numstars: number) {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
question: QuestionFragment;
|
question: QuestionFragment;
|
||||||
showTimeStamp: boolean;
|
|
||||||
expandFooterToFullWidth: boolean;
|
expandFooterToFullWidth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const QuestionFooter: React.FC<Props> = ({
|
export const QuestionFooter: React.FC<Props> = ({
|
||||||
question,
|
question,
|
||||||
showTimeStamp,
|
|
||||||
expandFooterToFullWidth,
|
expandFooterToFullWidth,
|
||||||
}) => {
|
}) => {
|
||||||
let debuggingWithBackground = false;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`grid grid-cols-3 ${
|
className={`grid grid-cols-3 ${
|
||||||
|
@ -254,14 +200,14 @@ export const QuestionFooter: React.FC<Props> = ({
|
||||||
<div
|
<div
|
||||||
className={`self-center col-span-1 ${getStarsColor(
|
className={`self-center col-span-1 ${getStarsColor(
|
||||||
question.qualityIndicators.stars
|
question.qualityIndicators.stars
|
||||||
)} ${debuggingWithBackground ? "bg-red-200" : ""}`}
|
)}`}
|
||||||
>
|
>
|
||||||
{getstars(question.qualityIndicators.stars)}
|
{getstars(question.qualityIndicators.stars)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
expandFooterToFullWidth ? "place-self-center" : "self-center"
|
expandFooterToFullWidth ? "place-self-center" : "self-center"
|
||||||
} col-span-1 font-bold ${debuggingWithBackground ? "bg-red-100" : ""}`}
|
} col-span-1 font-bold`}
|
||||||
>
|
>
|
||||||
{question.platform.label
|
{question.platform.label
|
||||||
.replace("Good Judgment Open", "GJOpen")
|
.replace("Good Judgment Open", "GJOpen")
|
||||||
|
@ -272,12 +218,9 @@ export const QuestionFooter: React.FC<Props> = ({
|
||||||
expandFooterToFullWidth
|
expandFooterToFullWidth
|
||||||
? "justify-self-end mr-4"
|
? "justify-self-end mr-4"
|
||||||
: "justify-self-center"
|
: "justify-self-center"
|
||||||
} col-span-1 ${debuggingWithBackground ? "bg-red-100" : ""}`}
|
} col-span-1`}
|
||||||
>
|
>
|
||||||
{displayQualityIndicators({
|
<QualityIndicatorsList question={question} />
|
||||||
question,
|
|
||||||
showTimeStamp,
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
import { CopyText } from "../../common/CopyText";
|
import { CopyText } from "../../common/CopyText";
|
||||||
import { QuestionOptions } from "../../questions/components/QuestionOptions";
|
import { QuestionOptions } from "../../questions/components/QuestionOptions";
|
||||||
import { formatProbability } from "../../questions/utils";
|
|
||||||
import { QuestionFragment } from "../../search/queries.generated";
|
import { QuestionFragment } from "../../search/queries.generated";
|
||||||
import { Card } from "../Card";
|
import { Card } from "../Card";
|
||||||
import { QuestionFooter } from "./QuestionFooter";
|
import { QuestionFooter } from "./QuestionFooter";
|
||||||
|
@ -87,99 +86,12 @@ const cleanText = (text: string): string => {
|
||||||
return textString;
|
return textString;
|
||||||
};
|
};
|
||||||
|
|
||||||
const primaryForecastColor = (probability: number) => {
|
|
||||||
if (probability < 0.03) {
|
|
||||||
return "bg-red-600";
|
|
||||||
} else if (probability < 0.1) {
|
|
||||||
return "bg-red-600 opacity-80";
|
|
||||||
} else if (probability < 0.2) {
|
|
||||||
return "bg-red-600 opacity-70";
|
|
||||||
} else if (probability < 0.3) {
|
|
||||||
return "bg-red-600 opacity-60";
|
|
||||||
} else if (probability < 0.4) {
|
|
||||||
return "bg-red-600 opacity-50";
|
|
||||||
} else if (probability < 0.5) {
|
|
||||||
return "bg-gray-500";
|
|
||||||
} else if (probability < 0.6) {
|
|
||||||
return "bg-gray-500";
|
|
||||||
} else if (probability < 0.7) {
|
|
||||||
return "bg-green-600 opacity-50";
|
|
||||||
} else if (probability < 0.8) {
|
|
||||||
return "bg-green-600 opacity-60";
|
|
||||||
} else if (probability < 0.9) {
|
|
||||||
return "bg-green-600 opacity-70";
|
|
||||||
} else if (probability < 0.97) {
|
|
||||||
return "bg-green-600 opacity-80";
|
|
||||||
} else {
|
|
||||||
return "bg-green-600";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const textColor = (probability: number) => {
|
|
||||||
if (probability < 0.03) {
|
|
||||||
return "text-red-600";
|
|
||||||
} else if (probability < 0.1) {
|
|
||||||
return "text-red-600 opacity-80";
|
|
||||||
} else if (probability < 0.2) {
|
|
||||||
return "text-red-600 opacity-80";
|
|
||||||
} else if (probability < 0.3) {
|
|
||||||
return "text-red-600 opacity-70";
|
|
||||||
} else if (probability < 0.4) {
|
|
||||||
return "text-red-600 opacity-70";
|
|
||||||
} else if (probability < 0.5) {
|
|
||||||
return "text-gray-500";
|
|
||||||
} else if (probability < 0.6) {
|
|
||||||
return "text-gray-500";
|
|
||||||
} else if (probability < 0.7) {
|
|
||||||
return "text-green-600 opacity-70";
|
|
||||||
} else if (probability < 0.8) {
|
|
||||||
return "text-green-600 opacity-70";
|
|
||||||
} else if (probability < 0.9) {
|
|
||||||
return "text-green-600 opacity-80";
|
|
||||||
} else if (probability < 0.97) {
|
|
||||||
return "text-green-600 opacity-80";
|
|
||||||
} else {
|
|
||||||
return "text-green-600";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const primaryEstimateAsText = (probability: number) => {
|
|
||||||
if (probability < 0.03) {
|
|
||||||
return "Exceptionally unlikely";
|
|
||||||
} else if (probability < 0.1) {
|
|
||||||
return "Very unlikely";
|
|
||||||
} else if (probability < 0.4) {
|
|
||||||
return "Unlikely";
|
|
||||||
} else if (probability < 0.6) {
|
|
||||||
return "About Even";
|
|
||||||
} else if (probability < 0.9) {
|
|
||||||
return "Likely";
|
|
||||||
} else if (probability < 0.97) {
|
|
||||||
return "Very likely";
|
|
||||||
} else {
|
|
||||||
return "Virtually certain";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Logical checks
|
|
||||||
|
|
||||||
const checkIfDisplayTimeStampAtBottom = (qualityIndicators: {
|
|
||||||
[k: string]: any;
|
|
||||||
}) => {
|
|
||||||
let indicators = Object.keys(qualityIndicators);
|
|
||||||
if (indicators.length == 1 && indicators[0] == "stars") {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auxiliary components
|
// Auxiliary components
|
||||||
|
|
||||||
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
||||||
description,
|
description,
|
||||||
}) => {
|
}) => {
|
||||||
let formatted = truncateText(250, cleanText(description));
|
const formatted = truncateText(250, cleanText(description));
|
||||||
// overflow-hidden overflow-ellipsis h-24
|
// overflow-hidden overflow-ellipsis h-24
|
||||||
return formatted === "" ? null : (
|
return formatted === "" ? null : (
|
||||||
<div className="overflow-clip">
|
<div className="overflow-clip">
|
||||||
|
@ -217,19 +129,10 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
expandFooterToFullWidth,
|
expandFooterToFullWidth,
|
||||||
showIdToggle,
|
showIdToggle,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { options } = question;
|
||||||
platform,
|
const lastUpdated = new Date(question.timestamp * 1000);
|
||||||
description,
|
|
||||||
options,
|
|
||||||
qualityIndicators,
|
|
||||||
timestamp,
|
|
||||||
visualization,
|
|
||||||
} = question;
|
|
||||||
const lastUpdated = new Date(timestamp * 1000);
|
|
||||||
const displayTimestampAtBottom =
|
|
||||||
checkIfDisplayTimeStampAtBottom(qualityIndicators);
|
|
||||||
|
|
||||||
const yesNoOptions =
|
const isBinary =
|
||||||
options.length === 2 &&
|
options.length === 2 &&
|
||||||
(options[0].name === "Yes" || options[0].name === "No");
|
(options[0].name === "Yes" || options[0].name === "No");
|
||||||
|
|
||||||
|
@ -243,7 +146,7 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div>
|
||||||
<Link href={`/questions/${question.id}`}>
|
<Link href={`/questions/${question.id}`} passHref>
|
||||||
<a className="float-right block ml-2 mt-1.5">
|
<a className="float-right block ml-2 mt-1.5">
|
||||||
<FaExpand
|
<FaExpand
|
||||||
size="18"
|
size="18"
|
||||||
|
@ -261,41 +164,17 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
</a>
|
</a>
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
</div>
|
</div>
|
||||||
{yesNoOptions && (
|
{isBinary ? (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="space-x-2">
|
<QuestionOptions options={options} />
|
||||||
<span
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
|
||||||
className={`${primaryForecastColor(
|
|
||||||
options[0].probability
|
|
||||||
)} text-white w-16 rounded-md px-1.5 py-0.5 font-bold`}
|
|
||||||
>
|
|
||||||
{formatProbability(options[0].probability)}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`${textColor(
|
|
||||||
options[0].probability
|
|
||||||
)} text-gray-500 inline-block`}
|
|
||||||
>
|
|
||||||
{primaryEstimateAsText(options[0].probability)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`hidden ${
|
|
||||||
showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
{!yesNoOptions && (
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<QuestionOptions options={options} />
|
<QuestionOptions options={options} />
|
||||||
<div
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
|
||||||
className={`hidden ${
|
|
||||||
showTimeStamp && !displayTimestampAtBottom ? "sm:block" : ""
|
|
||||||
} ml-6`}
|
|
||||||
>
|
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -303,14 +182,14 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
|
|
||||||
{question.platform.id !== "guesstimate" && options.length < 3 && (
|
{question.platform.id !== "guesstimate" && options.length < 3 && (
|
||||||
<div className="text-gray-500">
|
<div className="text-gray-500">
|
||||||
<DisplayMarkdown description={description} />
|
<DisplayMarkdown description={question.description} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{question.platform.id === "guesstimate" && (
|
{question.platform.id === "guesstimate" && (
|
||||||
<img
|
<img
|
||||||
className="rounded-sm"
|
className="rounded-sm"
|
||||||
src={visualization}
|
src={question.visualization}
|
||||||
alt="Guesstimate Screenshot"
|
alt="Guesstimate Screenshot"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -324,7 +203,6 @@ export const DisplayQuestion: React.FC<Props> = ({
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<QuestionFooter
|
<QuestionFooter
|
||||||
question={question}
|
question={question}
|
||||||
showTimeStamp={showTimeStamp && displayTimestampAtBottom}
|
|
||||||
expandFooterToFullWidth={expandFooterToFullWidth}
|
expandFooterToFullWidth={expandFooterToFullWidth}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,18 +1,95 @@
|
||||||
|
import { QuestionFragment } from "../../search/queries.generated";
|
||||||
import { formatProbability } from "../utils";
|
import { formatProbability } from "../utils";
|
||||||
|
|
||||||
const OptionRow: React.FC<{ option: any }> = ({ option }) => {
|
type Option = QuestionFragment["options"][0];
|
||||||
const chooseColor = (probability: number) => {
|
|
||||||
if (probability < 0.1) {
|
|
||||||
return "bg-blue-50 text-blue-500";
|
|
||||||
} else if (probability < 0.3) {
|
|
||||||
return "bg-blue-100 text-blue-600";
|
|
||||||
} else if (probability < 0.7) {
|
|
||||||
return "bg-blue-200 text-blue-700";
|
|
||||||
} else {
|
|
||||||
return "bg-blue-300 text-blue-800";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const textColor = (probability: number) => {
|
||||||
|
if (probability < 0.03) {
|
||||||
|
return "text-red-600";
|
||||||
|
} else if (probability < 0.1) {
|
||||||
|
return "text-red-600 opacity-80";
|
||||||
|
} else if (probability < 0.2) {
|
||||||
|
return "text-red-600 opacity-80";
|
||||||
|
} else if (probability < 0.3) {
|
||||||
|
return "text-red-600 opacity-70";
|
||||||
|
} else if (probability < 0.4) {
|
||||||
|
return "text-red-600 opacity-70";
|
||||||
|
} else if (probability < 0.5) {
|
||||||
|
return "text-gray-500";
|
||||||
|
} else if (probability < 0.6) {
|
||||||
|
return "text-gray-500";
|
||||||
|
} else if (probability < 0.7) {
|
||||||
|
return "text-green-600 opacity-70";
|
||||||
|
} else if (probability < 0.8) {
|
||||||
|
return "text-green-600 opacity-70";
|
||||||
|
} else if (probability < 0.9) {
|
||||||
|
return "text-green-600 opacity-80";
|
||||||
|
} else if (probability < 0.97) {
|
||||||
|
return "text-green-600 opacity-80";
|
||||||
|
} else {
|
||||||
|
return "text-green-600";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const primaryForecastColor = (probability: number) => {
|
||||||
|
if (probability < 0.03) {
|
||||||
|
return "bg-red-600";
|
||||||
|
} else if (probability < 0.1) {
|
||||||
|
return "bg-red-600 opacity-80";
|
||||||
|
} else if (probability < 0.2) {
|
||||||
|
return "bg-red-600 opacity-70";
|
||||||
|
} else if (probability < 0.3) {
|
||||||
|
return "bg-red-600 opacity-60";
|
||||||
|
} else if (probability < 0.4) {
|
||||||
|
return "bg-red-600 opacity-50";
|
||||||
|
} else if (probability < 0.5) {
|
||||||
|
return "bg-gray-500";
|
||||||
|
} else if (probability < 0.6) {
|
||||||
|
return "bg-gray-500";
|
||||||
|
} else if (probability < 0.7) {
|
||||||
|
return "bg-green-600 opacity-50";
|
||||||
|
} else if (probability < 0.8) {
|
||||||
|
return "bg-green-600 opacity-60";
|
||||||
|
} else if (probability < 0.9) {
|
||||||
|
return "bg-green-600 opacity-70";
|
||||||
|
} else if (probability < 0.97) {
|
||||||
|
return "bg-green-600 opacity-80";
|
||||||
|
} else {
|
||||||
|
return "bg-green-600";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const primaryEstimateAsText = (probability: number) => {
|
||||||
|
if (probability < 0.03) {
|
||||||
|
return "Exceptionally unlikely";
|
||||||
|
} else if (probability < 0.1) {
|
||||||
|
return "Very unlikely";
|
||||||
|
} else if (probability < 0.4) {
|
||||||
|
return "Unlikely";
|
||||||
|
} else if (probability < 0.6) {
|
||||||
|
return "About Even";
|
||||||
|
} else if (probability < 0.9) {
|
||||||
|
return "Likely";
|
||||||
|
} else if (probability < 0.97) {
|
||||||
|
return "Very likely";
|
||||||
|
} else {
|
||||||
|
return "Virtually certain";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const chooseColor = (probability: number) => {
|
||||||
|
if (probability < 0.1) {
|
||||||
|
return "bg-blue-50 text-blue-500";
|
||||||
|
} else if (probability < 0.3) {
|
||||||
|
return "bg-blue-100 text-blue-600";
|
||||||
|
} else if (probability < 0.7) {
|
||||||
|
return "bg-blue-200 text-blue-700";
|
||||||
|
} else {
|
||||||
|
return "bg-blue-300 text-blue-800";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
|
@ -29,14 +106,42 @@ const OptionRow: React.FC<{ option: any }> = ({ option }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuestionOptions: React.FC<{ options: any[] }> = ({ options }) => {
|
export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
||||||
|
options,
|
||||||
|
}) => {
|
||||||
|
const isBinary =
|
||||||
|
options.length === 2 &&
|
||||||
|
(options[0].name === "Yes" || options[0].name === "No");
|
||||||
|
|
||||||
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
|
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
|
||||||
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
|
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
if (isBinary) {
|
||||||
{optionsMax5.map((option, i) => (
|
return (
|
||||||
<OptionRow option={option} key={i} />
|
<div className="space-x-2">
|
||||||
))}
|
<span
|
||||||
</div>
|
className={`${primaryForecastColor(
|
||||||
);
|
options[0].probability
|
||||||
|
)} text-white w-16 rounded-md px-1.5 py-0.5 font-bold`}
|
||||||
|
>
|
||||||
|
{formatProbability(options[0].probability)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={`${textColor(
|
||||||
|
options[0].probability
|
||||||
|
)} text-gray-500 inline-block`}
|
||||||
|
>
|
||||||
|
{primaryEstimateAsText(options[0].probability)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{optionsMax5.map((option, i) => (
|
||||||
|
<OptionRow option={option} key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
import { Query } from "../../common/Query";
|
import { Query } from "../../common/Query";
|
||||||
import { Card } from "../../display/Card";
|
import { Card } from "../../display/Card";
|
||||||
|
import { QuestionFooter } from "../../display/DisplayQuestion/QuestionFooter";
|
||||||
import { Layout } from "../../display/Layout";
|
import { Layout } from "../../display/Layout";
|
||||||
import { QuestionFragment } from "../../search/queries.generated";
|
import { QuestionFragment } from "../../search/queries.generated";
|
||||||
import { ssrUrql } from "../../urql";
|
import { ssrUrql } from "../../urql";
|
||||||
|
@ -39,8 +40,18 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
|
||||||
question,
|
question,
|
||||||
}) => (
|
}) => (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h1>{question.title}</h1>
|
<h1>
|
||||||
|
<a
|
||||||
|
className="text-black no-underline"
|
||||||
|
href={question.url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{question.title}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<QuestionFooter question={question} expandFooterToFullWidth={true} />
|
||||||
<QuestionOptions options={question.options} />
|
<QuestionOptions options={question.options} />
|
||||||
|
|
||||||
<ReactMarkdown linkTarget="_blank" className="font-normal">
|
<ReactMarkdown linkTarget="_blank" className="font-normal">
|
||||||
{question.description}
|
{question.description}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user