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