reactstrap#Card TypeScript Examples

The following examples show how to use reactstrap#Card. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: app.tsx    From website with Apache License 2.0 5 votes vote down vote up
function App({
  query,
  loading,
  onSearch,
  chartsData,
}: AppPropType): JSX.Element {
  return (
    <>
      <div id="plot-container">
        <Container fluid={true}>
          <Row>
            <Card className="place-options-card">
              <Container className="place-options" fluid={true}>
                <div className="place-options-section">
                  <TextSearchBar
                    onSearch={onSearch}
                    initialValue={query}
                    placeholder='For example "People with Doctorate Degrees in France"'
                  />
                </div>
              </Container>
            </Card>
          </Row>
          {chartsData && !loading && (
            <Row>
              <Card>
                <div id="main" className="col-md-9x col-lg-10">
                  <StatVarResults {...chartsData} />
                </div>
              </Card>
            </Row>
          )}
          <div id="screen" style={{ display: loading ? "block" : "none" }}>
            <div id="spinner"></div>
          </div>
        </Container>
      </div>
    </>
  );
}
Example #2
Source File: stat_vars.tsx    From website with Apache License 2.0 5 votes vote down vote up
/**
 * Component for rendering results associated with a rich search query.
 */
export function StatVarResults({
  placeName,
  statVarInfo,
  statVarOrder,
  debug,
}: StatVarResultsPropType): JSX.Element {
  const [debugOpen, setDebugOpen] = useState(false);
  const places = Object.keys(placeName);
  if (!places.length) {
    return (
      <section className="block col-12">No places found in the query.</section>
    );
  }
  if (!statVarOrder.length) {
    return <section className="block col-12">No results found.</section>;
  }

  return (
    <section className="block col-12">
      <div id="chart-region">
        <ChartRegion
          placeName={placeName}
          statVarInfo={statVarInfo}
          statVarOrder={statVarOrder}
        ></ChartRegion>
      </div>
      <ul>
        {statVarOrder.map((sv) => (
          <li key={sv}>
            <a href={getURL(places, sv)}>{statVarInfo[sv].title}</a>
          </li>
        ))}
      </ul>
      {!!debug.length && (
        <div className="debug-view">
          <Button
            className="btn-light"
            onClick={() => setDebugOpen(!debugOpen)}
            size="sm"
          >
            {debugOpen ? "Hide Debug" : "Show Debug"}
          </Button>
          <Collapse isOpen={debugOpen}>
            <Card>
              <CardBody>
                {debug.map((line, key) => (
                  <div key={key}>{line}</div>
                ))}
              </CardBody>
            </Card>
          </Collapse>
        </div>
      )}
    </section>
  );
}
Example #3
Source File: place_details.tsx    From website with Apache License 2.0 5 votes vote down vote up
export function PlaceDetails(props: PlaceDetailsPropType): JSX.Element {
  const selectedPlace = props.placeInfo.selectedPlace;
  const unitString = _.isEmpty(props.unit) ? "" : ` ${props.unit}`;
  const selectedPlaceValue =
    selectedPlace.dcid in props.breadcrumbDataValues
      ? formatNumber(props.breadcrumbDataValues[selectedPlace.dcid], "") +
        unitString
      : "N/A";
  const selectedPlaceDate =
    selectedPlace.dcid in props.metadata
      ? ` (${props.metadata[selectedPlace.dcid].placeStatDate})`
      : "";
  const rankedPlaces = props.geoJsonFeatures.filter(
    (feature) =>
      feature.properties.geoDcid in props.mapDataValues &&
      _.isNumber(props.mapDataValues[feature.properties.geoDcid])
  );
  rankedPlaces.sort(
    (a, b) =>
      props.mapDataValues[b.properties.geoDcid] -
      props.mapDataValues[a.properties.geoDcid]
  );
  return (
    <Card className="place-details-card">
      <div className="place-details">
        <div className="place-details-section">
          <div className="place-details-section-title">Top Places</div>
          {rankedPlaces
            .slice(0, Math.min(5, rankedPlaces.length))
            .map((place, index) =>
              getListItemElement(
                {
                  dcid: place.properties.geoDcid,
                  name: place.properties.name || place.properties.geoDcid,
                  types: [props.placeInfo.enclosedPlaceType],
                },
                props,
                unitString,
                index + 1
              )
            )}
        </div>
        <div className="place-details-section">
          <div className="place-details-section-title">Bottom Places</div>
          {rankedPlaces.slice(-5).map((place, index) =>
            getListItemElement(
              {
                dcid: place.properties.geoDcid,
                name: place.properties.name || place.properties.geoDcid,
                types: [props.placeInfo.enclosedPlaceType],
              },
              props,
              unitString,
              Math.max(0, rankedPlaces.length - 5) + index + 1
            )
          )}
        </div>
        <div className="place-details-section">
          <div className="place-details-section-title">Containing Places</div>
          <div>
            {selectedPlace.name}
            {selectedPlaceDate}: {selectedPlaceValue}
          </div>
          {props.placeInfo.parentPlaces.map((place) =>
            getListItemElement(place, props, unitString)
          )}
        </div>
      </div>
    </Card>
  );
}
Example #4
Source File: Home.tsx    From reference-merchant with Apache License 2.0 5 votes vote down vote up
function Home(props) {
  const { t } = useTranslation("layout");
  const [selectedProduct, setSelectedProduct] = useState<Product>();
  const [products, setProducts] = useState<Product[] | undefined>();
  const [demoMode, setDemoMode] = useState<boolean>(props.demoMode === undefined ? false : true);

  const getProducts = async () => {
    try {
      setProducts(await new BackendClient().getProductsList());
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    //noinspection JSIgnoredPromiseFromCall
    getProducts();
  }, []);

  return (
    <>
      <TestnetWarning />
      <Container>
        <h1 className="text-center font-weight-bold mt-5">{t("name")}</h1>

        <section className="mt-5">
          {products && (
            <Row>
              {products.map((product, i) => (
                <Col key={product.gtin} md={6} lg={4}>
                  <Card key={product.gtin} className="mb-4">
                    <CardImg top src={product.image_url} />
                    <CardBody>
                      <CardTitle className="font-weight-bold h5">{product.name}</CardTitle>
                      <CardText>{product.description}</CardText>
                    </CardBody>
                    <CardFooter>
                      <Row>
                        <Col>
                          <div>
                            <strong>Price:</strong> {product.price / 1000000} {product.currency}
                          </div>
                        </Col>
                        <Col lg={4} className="text-right">
                          <Button
                            color="secondary"
                            block
                            className="btn-sm"
                            onClick={() => setSelectedProduct(products[i])}
                          >
                            Buy Now
                          </Button>
                        </Col>
                      </Row>
                    </CardFooter>
                  </Card>
                </Col>
              ))}
            </Row>
          )}
          {!products && <ProductsLoader />}
        </section>
      </Container>
      <Payment
        demoMode={demoMode}
        product={selectedProduct}
        isOpen={!!selectedProduct}
        onClose={() => setSelectedProduct(undefined)}
      />
    </>
  );
}
Example #5
Source File: DataVisualization.tsx    From TutorBase with MIT License 4 votes vote down vote up
DataVisualization = () => {
  
  const [dropdownLabel2, setDropdownLabel2] = useState("All Time");
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [dropdownOpen2, setDropdownOpen2] = useState(false);
  const [dropdownOpen3, setDropdownOpen3] = useState(false);
  const [dateRange, setDateRange] = useState(new Date(2020,0,0));
  const [course, setCourse] = useState("All Courses");
  const [courses, setCourses] = useState(new Array<string>());
  const [appointments, setAppointments] = useState(0);
  const [hours, setHours] = useState(0);
  const [earnings, setEarnings] = useState(0);
  const [chart, setChart] = useState(0);
  const [meetingsMap, setMeetingsMap] = useState(new Map<number,number>());
  const [earningsMap, setEarningsMap] = useState(new Map<number,number>());
  const toggle = () => setDropdownOpen(prevState => !prevState);
  const toggle2 = () => setDropdownOpen2(prevState => !prevState);
  const toggle3 = () => setDropdownOpen3(prevState => !prevState);
  let tutor = useSelector(selectClientData);
  let tutorID = tutor.clientId;
  useEffect(() => {
    GetTutoringHours(course, tutorID).then( apiResult => {
    setMeetingsMap(apiResult[0]);
    setEarningsMap(apiResult[1]);
    setAppointments(apiResult[3]);
    setHours(apiResult[2]);
    setEarnings(apiResult[4]);
    setCourses(apiResult[5]);
    });
  },[]);



  let coursesDropdowns:Array<ReactElement> = [];
  coursesDropdowns.push(<DropdownItem onClick={() => {
    setCourse("All Courses");
    GetTutoringHours("All Courses", tutorID).then( apiResult => {
      setMeetingsMap(apiResult[0]);
      setEarningsMap(apiResult[1]);
      setAppointments(apiResult[3]);
      setHours(apiResult[2]);
      setEarnings(apiResult[4]);
      setCourses(apiResult[5]);
      });
  }}>
    All Courses
  </DropdownItem>);
  for (let i = 0; i < courses.length; i++) {
    coursesDropdowns.push(<DropdownItem onClick={() => {
      setCourse(courses[i]);
      GetTutoringHours(courses[i], tutorID).then( apiResult => {
        setMeetingsMap(apiResult[0]);
        setEarningsMap(apiResult[1]);
        setAppointments(apiResult[3]);
        setHours(apiResult[2]);
        setEarnings(apiResult[4]);
        setCourses(apiResult[5]);
        });
    }}>
      {courses[i]}
    </DropdownItem>);         
  }
    return (
        <Container fluid className="background" style={{marginBottom:'10em'}}>
        <hr></hr>
        <Row xs="2" className="parent">

        </Row>
        <div style={{display:'flex', flexDirection:'row', flexWrap:'wrap'}}>
                <div style={{display:'flex', flexDirection:'column', flex:'1 1 0px', flexWrap:'wrap'}}>
                    <Card body>
                    <CardTitle tag="h5">Appointments</CardTitle>
                    <CardText>
                        <h1>
                        <CountUp 
                            end={appointments} 
                            useEasing={true}
                            duration={3.5}
                            />
                            </h1>
                        </CardText>
                    
                    </Card>
                </div>
                <div style={{display:'flex', flexDirection:'column', flex:'1 1 0px', flexWrap:'wrap'}}>
                    <Card body>
                    <CardTitle tag="h5">Hours Tutored</CardTitle>
                    <CardText>
                        <h1>
                        <CountUp 
                            end={hours} 
                            useEasing={true}
                            duration={4}
                            />
                            </h1>
                        </CardText>
                    
                    </Card>
                </div>
                <div style={{display:'flex', flexDirection:'column', flex:'1 1 0px', flexWrap:'wrap'}}>
                <Card body>
                    <CardTitle tag="h5">Earnings</CardTitle>
                    <CardText>
                        <h1>
                        <CountUp 
                            decimals={2}
                            prefix="$"
                            end={earnings} 
                            useEasing={true}
                            

                            duration={4}/>
                            </h1>
                        </CardText>
                    
                    </Card>
                </div>
            </div>
        
            <div style={{display:'flex', flexDirection:'row'}}>
            <Card body>
                <CardTitle tag="h5">
                <div style={{display:'flex', flexDirection:'row', flexWrap:'wrap'}}>
                  <div style={{display:'flex', flexDirection:'column', marginRight:'1em', marginTop:'0.25em'}}>
                    <Dropdown isOpen={dropdownOpen} toggle={toggle}>
                      <DropdownToggle caret>
                        {(chart === 0) ? "Calendar" : (chart === 1 ? "Total Hours" : "Total Earnings")}
                        
                        <FontAwesomeIcon icon={faArrowDown} style={{marginLeft:'1em'}}/>
                      </DropdownToggle>
                      <DropdownMenu>
                        <DropdownItem header>Tutor Data</DropdownItem>
                        <DropdownItem onClick={() => setChart(0)}>Calendar</DropdownItem>
                        <DropdownItem onClick={() => setChart(1)}>Total Hours</DropdownItem>
                        <DropdownItem divider />
                        <DropdownItem onClick={() => setChart(2)}>Total Earnings</DropdownItem>
                      </DropdownMenu>
                    </Dropdown>
                  </div>
                    { chart != 0 ?
                    <div style={{display:'flex', flexDirection:'column', marginRight:'1em', marginTop:'0.25em'}}>
                    <Dropdown isOpen={dropdownOpen2} toggle={toggle2} style={{alignSelf:'right'}}>
                      <DropdownToggle caret>
                        {dropdownLabel2}
                        <FontAwesomeIcon icon={faArrowDown} style={{marginLeft:'1em'}}/>
                      </DropdownToggle>
                      <DropdownMenu>
                        <DropdownItem header>Date Range</DropdownItem>
                        <DropdownItem onClick={() => {
                          let date = new Date(getNow());
                          date.setFullYear(2020);
                          date.setMonth(0);
                          date.setDate(0);
                          setDateRange(date);
                          setDropdownLabel2("All Time");
                        }}>
                          All Time
                        </DropdownItem>
                        
                        <DropdownItem onClick={() => {
                              let date = new Date(getNow());
                              date.setFullYear(date.getFullYear() - 1);
                              setDateRange(date);
                              setDropdownLabel2("1Y");

                            }}>1Y
                        </DropdownItem>

                        <DropdownItem onClick={() => { 
                              let date = new Date(getNow()); 
                              date.setMonth(date.getMonth() - 6);
                              setDateRange(date);
                              setDropdownLabel2("6M");
                            }}>6M
                        </DropdownItem>
                        <DropdownItem onClick={() => { 
                              let date = new Date(getNow()); 
                              date.setMonth(date.getMonth() - 1);
                              setDateRange(date);
                              setDropdownLabel2("1M");
                            }}>1M
                        </DropdownItem>
                      </DropdownMenu>
                    </Dropdown>
                    </div>
                    : <div></div>}
                    <div style={{display:'flex', flexDirection:'column', marginTop:'0.25em'}}>
                      <Dropdown isOpen={dropdownOpen3} toggle={toggle3} style={{alignSelf:'right'}}>
                      <DropdownToggle caret>
                        {course}
                        <FontAwesomeIcon icon={faArrowDown} style={{marginLeft:'1em'}}/>
                      </DropdownToggle>
                      <DropdownMenu>
                        <DropdownItem header>Filter by Course</DropdownItem>
                        {coursesDropdowns}
                      </DropdownMenu>
                    </Dropdown>
                    </div>
                    </div>
                    </CardTitle>
                    
                    <CardText>
                      {chart == 0 ?
                        <TutorHeatmap dateMap={meetingsMap} />
                        : (chart == 1 ? <LineGraph dateMap={meetingsMap}
                          fromTime={dateRange}
                          isHours={true}/>
                          :<LineGraph dateMap={earningsMap}
                          fromTime={dateRange}
                          isHours={false}/>
                          )}
                      
                      
                    </CardText>

                    
              </Card>
            </div>
            </Container>
    );
}
Example #6
Source File: TutorPanelSignup.tsx    From TutorBase with MIT License 4 votes vote down vote up
Panel = (props: IProps) => {
    let dispatch = useDispatch();
    let id = useSelector(selectClientData).clientId;
    const [modalOpen, setModalOpen] = useState(false);
    let params : string = useLocation().pathname;
    const [selectedSubjects, setSelectedSubjects] = useState(new Set<string>());
    const [RIN, setRIN] = useState("");
    const [validRIN, setValidRIN] = useState(false);
    const [cohort, setCohort] = useState("");
    const [comments, setComments] = useState("");
    const [footerMessage, setFooterMessage] = useState("");
    const [rate, setRate] = useState(0);
    let subjects = [];
    let selectedSubjectsOutput = [];
    const [subjectsList, setSubjectsList] = useState(new Array<Subject>());
    function checkRIN(value: string) {
        if (value.length !== 9) {
            setValidRIN(false);
        }
        else {
            setValidRIN(true); 
        }
        setRIN(value);

    }
    function submit() {
        if (!validRIN
            || cohort === ""
            || cohort === "Select"
            || selectedSubjects.size === 0) {
                setFooterMessage("Please complete required fields.");
                return;
        }
        let subs: Array<String> = Array.from(selectedSubjects.keys());
        api.TutorSignup(id, RIN, subs, comments, rate).then(res =>{
            res ?
            setFooterMessage("Application submitted.")
            : setFooterMessage("Error submitting. Please try again.");
            }).catch(err => {
                setFooterMessage("Error submitting. Please try again.")
            });
    }
    
    useEffect(() => {
        // Get all avaliable subjects from API
        const getSubjects = async () => {
            return (await api.GetSubjects()).data;
        }

        getSubjects().then(value => {
                setSubjectsList(value);
            }
        )
    }, []);
    for (let i = 0; i < subjectsList.length; i++) {
        let name: string = subjectsList[i].id;
        let color = SubjectToColor(name);
        subjects.push(
            (<Button
                style={{background: color}}
                onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))}
            >
                {name}
            </Button>
            )
            );
    }
    let selectedSubs:Array<string> = Array.from(selectedSubjects.keys());
    for (let i = 0; i < selectedSubs.length; i++) {
        let name: string = selectedSubs[i];
        let color = SubjectToColor(name);
        selectedSubjectsOutput.push(
            (
               
                    <Badge
                    style={{
                        backgroundColor: color,
                        cursor:'default',
                        color: "black",
                        minWidth: '6em',
                        display: "flex",
                        flexDirection:'row',
                        alignItems: 'center',
                        marginRight: '0.5em'
                    }}
                    pill
                >
                    <div style={{
                        display: "flex",
                        flex: '50%',
                    }}>
                        {name + ' '}
                    </div> 
                    <Button 
                    close 
                    style={{
                        display: "flex",
                        flex: '50%',
                        alignItems: 'center'
                    }}
                    onClick={() => setSelectedSubjects(SelectedSubjectsHandler(selectedSubjects, name))} /> 
                    
                </Badge>
            )
        );
    }
    return (
        <div id="panel-wrapper">
            <Navbar className={classNames("navbar-expand-lg", "navbar-light", "bg-light", "border-bottom", "shadow")}>
                <Button className="btn-red" id="menu-toggle" onClick={() => {
                    dispatch(actions.toggleSidebar());
                }}>☰</Button>
            </Navbar>

            <Container fluid className="background" style={{marginBottom:'10em'}}>
                <hr></hr>
                <Row xs="2" className="parent">

                </Row>
                <div style={{display:'flex', flexDirection:'column', flexWrap:'wrap', alignContent:'center'}}>
                    {props.isLoading ? (
                        <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
                            <Spinner style={{color:'#E66064'}}></Spinner>
                        </div>) 
                    : (
                    <div>
                        <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'10em'}}>
                            <h5>You are not currently signed up as a tutor. This dashboard is for tutors only. You can apply to be a TutorBase tutor below!
                            </h5></div>
                            
                            <div style={{display:'flex', flexDirection:'row', flex:'1 1 0px', flexWrap:'wrap', justifyContent:'center', marginTop:'1em'}}>
                            <Button 
                                className="btn-red" 
                                style={{height:'4em', width:'10em', borderRadius:'20em'}}
                                onClick={() => setModalOpen(true)}
                            >
                                Sign up as tutor
                            </Button>
                            <Modal
                                centered={true}
                                scrollable={true}
                                isOpen={modalOpen}
                            >
                                <ModalHeader toggle={() => setModalOpen(!modalOpen)}>
                                    Tutor Application Form
                                </ModalHeader>
                                <ModalBody>
                                <h5>RIN</h5>
                                <Input 
                                    defaultValue={RIN}
                                    onChange={(e) => checkRIN(e.target.value)}
                                    valid={validRIN}
                                    invalid={!validRIN}
                                    />
                                <p />
                                <h5>Cohort</h5>
                                <Input 
                                type="select"
                                    onChange={(e) => setCohort(e.target.value)}
                                    initialValue="Select"
                                    invalid={cohort === "" || cohort === "Select"}>
                                        <option>
                                        Select
                                    </option>
                                    <option>
                                        Freshman
                                    </option>
                                    <option>
                                        Sophomore
                                    </option>
                                    <option>
                                        Junior
                                    </option>
                                    <option>
                                        Senior
                                    </option>
                                    <option>
                                        Graduate
                                    </option>
                                    </Input>
                                <p />
                                <h5>Select Subjects to tutor</h5>
                                <ButtonGroup>
                                    {subjects}
                                    
                                </ButtonGroup>
                                <p>
                                    Selected:
                                    <Card
                                    outline={selectedSubjects.size === 0}
                                    color= {selectedSubjects.size === 0 ? "danger" : ""}>
                                <CardBody 
                                    style={{
                                        display: "flex",
                                        background: "lightgray",
                                        minHeight: "4em",
                                        flexWrap: 'wrap'
                                    }}>
                                {selectedSubjectsOutput}


                            </CardBody></Card>
                            </p>
                            <p>
                                <h5>Hourly Rate ($) (optional)</h5>
                                <Input
                                type="number"
                                    onChange={(e) => setRate(+(e.target.value))} />
                                </p>
                                <h5>Comments (optional)</h5>
                                <Input 
                                    type="textarea"
                                    onChange={(e) => setComments(e.target.value)} />

                                </ModalBody>
                                <ModalFooter>
                                <p style={{color: footerMessage === "Application submitted." ? 'green' : 'red'}}>
                                    {footerMessage}
                                </p>

                                <Button
                                    color="primary"
                                    onClick={() => submit()}
                                >
                                    Submit
                                </Button>
                                {' '}
                                <Button onClick={() => setModalOpen(false)}>
                                    Cancel
                                </Button>
                                </ModalFooter>
                            </Modal>
                        </div>
                    </div>
                    )}
                </div>
            </Container>
        </div>
    );
}
Example #7
Source File: place_selector.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function PlaceSelector(props: PlaceSelectorProps): JSX.Element {
  const [childPlaceTypes, setChildPlaceTypes] = useState([]);
  useEffect(() => {
    if (_.isNull(props.selectedPlace.types)) {
      return;
    }
    loadChildPlaceTypes();
  }, [props.selectedPlace]);

  return (
    <Card className={`${SELECTOR_PREFIX}-card`}>
      <Container className={`${SELECTOR_PREFIX}-container`} fluid={true}>
        <div className={`${SELECTOR_PREFIX}-main-selectors`}>
          <div
            className={`${SELECTOR_PREFIX}-section`}
            id={`${SELECTOR_PREFIX}-search-section`}
          >
            <div className={`${SELECTOR_PREFIX}-label`}>Plot places in</div>
            <SearchBar
              places={
                props.selectedPlace.dcid
                  ? { [props.selectedPlace.dcid]: props.selectedPlace.name }
                  : {}
              }
              addPlace={(e) => selectPlace(e, props.onPlaceSelected)}
              removePlace={() =>
                unselectPlace(props.onPlaceSelected, setChildPlaceTypes)
              }
              numPlacesLimit={1}
              customPlaceHolder={props.customSearchPlaceholder}
            />
          </div>
          <div className={`${SELECTOR_PREFIX}-section`}>
            <div className={`${SELECTOR_PREFIX}-label`}>of type</div>
            <div>
              <CustomInput
                id={`${SELECTOR_PREFIX}-place-type`}
                type="select"
                value={props.enclosedPlaceType}
                onChange={(e) =>
                  selectEnclosedPlaceType(e, props.onEnclosedPlaceTypeSelected)
                }
                className="pac-target-input"
              >
                <option value="">Select a place type</option>
                {childPlaceTypes.map((type) => (
                  <option value={type} key={type}>
                    {ENCLOSED_PLACE_TYPE_NAMES[type] || type}
                  </option>
                ))}
              </CustomInput>
            </div>
          </div>
        </div>
        {props.children}
      </Container>
    </Card>
  );

  function loadChildPlaceTypes(): void {
    getParentPlacesPromise(props.selectedPlace.dcid)
      .then((parentPlaces) => {
        const getEnclosedPlaceTypesFn =
          props.getEnclosedPlaceTypes || getEnclosedPlaceTypes;
        const newChildPlaceTypes = getEnclosedPlaceTypesFn(
          props.selectedPlace,
          parentPlaces
        );
        if (_.isEqual(newChildPlaceTypes, childPlaceTypes)) {
          return;
        }
        if (_.isEmpty(newChildPlaceTypes)) {
          alert(
            `Sorry, we don't support ${props.selectedPlace.name}. Please select a different place.`
          );
        } else if (newChildPlaceTypes.length === 1) {
          props.onEnclosedPlaceTypeSelected(newChildPlaceTypes[0]);
        }
        setChildPlaceTypes(newChildPlaceTypes);
      })
      .catch(() => setChildPlaceTypes([]));
  }
}
Example #8
Source File: chart.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function Chart(props: ChartProps): JSX.Element {
  const statVar = props.statVar.value;
  const [errorMessage, setErrorMessage] = useState("");
  const mainSvInfo: StatVarInfo =
    statVar.dcid in statVar.info ? statVar.info[statVar.dcid] : {};
  const title = getTitle(
    Array.from(props.dates),
    mainSvInfo.title || statVar.dcid,
    statVar.perCapita
  );
  const placeDcid = props.placeInfo.enclosingPlace.dcid;
  const statVarDcid = statVar.dcid;
  const [mapPoints, setMapPoints] = useState(null);
  const [mapPointsFetched, setMapPointsFetched] = useState(false);
  const [zoomTransformation, setZoomTransformation] = useState(
    DEFAULT_ZOOM_TRANSFORMATION
  );
  const chartContainerRef = useRef<HTMLDivElement>();

  // load mapPoints in the background.
  useEffect(() => {
    props.mapPointsPromise
      .then((mapPoints) => {
        setMapPoints(mapPoints);
        setMapPointsFetched(true);
      })
      .catch(() => setMapPointsFetched(true));
  }, []);

  function replot() {
    draw(
      props,
      setErrorMessage,
      mapPoints,
      zoomTransformation,
      setZoomTransformation,
      props.display.value.color,
      props.display.value.domain
    );
  }

  // Replot when data changes.
  useEffect(() => {
    if (props.display.value.showMapPoints && !mapPointsFetched) {
      loadSpinner(SECTION_CONTAINER_ID);
      return;
    } else {
      removeSpinner(SECTION_CONTAINER_ID);
    }
    replot();
  }, [props, mapPointsFetched]);

  // Replot when chart width changes on sv widget toggle.
  useEffect(() => {
    const debouncedHandler = _.debounce(() => {
      if (!props.display.value.showMapPoints || mapPointsFetched) {
        replot();
      }
    }, DEBOUNCE_INTERVAL_MS);
    const resizeObserver = new ResizeObserver(debouncedHandler);
    if (chartContainerRef.current) {
      resizeObserver.observe(chartContainerRef.current);
    }
    return () => {
      resizeObserver.unobserve(chartContainerRef.current);
      debouncedHandler.cancel();
    };
  }, [props, chartContainerRef]);

  return (
    <div className="chart-section-container">
      <Card className="chart-section-card">
        <Container id={SECTION_CONTAINER_ID} fluid={true}>
          <div className="chart-section">
            <div className="map-title">
              <h3>
                {title}
                {props.dates.size > 1 && (
                  <span
                    onMouseOver={onDateRangeMouseOver}
                    onMouseOut={onDateRangeMouseOut}
                    id={DATE_RANGE_INFO_ID}
                  >
                    <i className="material-icons-outlined">info</i>
                  </span>
                )}
              </h3>
              <div id={DATE_RANGE_INFO_TEXT_ID}>
                The date range represents the dates of the data shown in this
                map.
              </div>
            </div>
            {errorMessage ? (
              <div className="error-message">{errorMessage}</div>
            ) : (
              <div className="map-section-container">
                <div id={CHART_CONTAINER_ID} ref={chartContainerRef}>
                  <div id={MAP_CONTAINER_ID}></div>
                  <div id={LEGEND_CONTAINER_ID}></div>
                </div>
                <div className="zoom-button-section">
                  <div id={ZOOM_IN_BUTTON_ID} className="zoom-button">
                    <i className="material-icons">add</i>
                  </div>
                  <div id={ZOOM_OUT_BUTTON_ID} className="zoom-button">
                    <i className="material-icons">remove</i>
                  </div>
                </div>
              </div>
            )}
            {props.display.value.showTimeSlider &&
              props.sampleDates &&
              props.sampleDates.length > 1 && (
                <TimeSlider
                  currentDate={_.max(Array.from(props.dates))}
                  dates={props.sampleDates}
                  metahash={props.metahash}
                  onPlay={props.onPlay}
                  startEnabled={props.dates.size === 1}
                  updateDate={props.updateDate}
                />
              )}
            <div className="map-links">
              {mainSvInfo.ranked && (
                <a className="explore-timeline-link" href={props.rankingLink}>
                  <span className="explore-timeline-text">
                    Explore rankings
                  </span>
                  <i className="material-icons">keyboard_arrow_right</i>
                </a>
              )}
              {!mainSvInfo.ranked &&
                (props.placeInfo.selectedPlace.dcid in props.mapDataValues ||
                  props.placeInfo.selectedPlace.dcid in
                    props.breadcrumbDataValues) && (
                  <a
                    className="explore-timeline-link"
                    href={`/tools/timeline#place=${placeDcid}&statsVar=${statVarDcid}`}
                  >
                    <span className="explore-timeline-text">
                      Explore timeline
                    </span>
                    <i className="material-icons">keyboard_arrow_right</i>
                  </a>
                )}
            </div>
          </div>
        </Container>
      </Card>
      <ToolChartFooter
        chartId="map"
        sources={props.sources}
        mMethods={null}
        sourceSelectorSvInfoList={[props.sourceSelectorSvInfo]}
        onSvMetahashUpdated={(svMetahashMap) =>
          props.statVar.setMetahash(svMetahashMap[props.statVar.value.dcid])
        }
        hideIsRatio={false}
        isPerCapita={props.statVar.value.perCapita}
        onIsPerCapitaUpdated={(isPerCapita: boolean) =>
          props.statVar.setPerCapita(isPerCapita)
        }
      >
        {props.placeInfo.mapPointPlaceType && (
          <div className="chart-option">
            <FormGroup check>
              <Label check>
                <Input
                  id="show-installations"
                  type="checkbox"
                  checked={props.display.value.showMapPoints}
                  onChange={(e) =>
                    props.display.setShowMapPoints(e.target.checked)
                  }
                />
                Show Installations
              </Label>
            </FormGroup>
          </div>
        )}
      </ToolChartFooter>
      <div id="map-chart-screen" className="screen">
        <div id="spinner"></div>
      </div>
    </div>
  );
}
Example #9
Source File: chart.tsx    From website with Apache License 2.0 4 votes vote down vote up
export function Chart(props: ChartPropsType): JSX.Element {
  const svgContainerRef = useRef<HTMLDivElement>();
  const tooltipRef = useRef<HTMLDivElement>();
  const chartContainerRef = useRef<HTMLDivElement>();
  const mapLegendRef = useRef<HTMLDivElement>();
  const [geoJson, setGeoJson] = useState(null);
  const [geoJsonFetched, setGeoJsonFetched] = useState(false);
  const xDates: Set<string> = new Set();
  const yDates: Set<string> = new Set();
  Object.values(props.points).forEach((point) => {
    xDates.add(point.xDate);
    yDates.add(point.yDate);
  });
  const xTitle = getTitle(Array.from(xDates), props.xLabel);
  const yTitle = getTitle(Array.from(yDates), props.yLabel);
  // Tooltip needs to start off hidden
  d3.select(tooltipRef.current)
    .style("visibility", "hidden")
    .style("position", "absolute");

  // Fetch geojson in the background when component is first mounted.
  useEffect(() => {
    axios
      .get(
        `/api/choropleth/geojson?placeDcid=${props.placeInfo.enclosingPlace.dcid}&placeType=${props.placeInfo.enclosedPlaceType}`
      )
      .then((resp) => {
        setGeoJson(resp.data);
        setGeoJsonFetched(true);
      })
      .catch(() => setGeoJsonFetched(true));
  }, []);

  function replot() {
    if (!_.isEmpty(props.points)) {
      if (svgContainerRef.current) {
        clearSVGs();
        plot(svgContainerRef, tooltipRef, mapLegendRef, props, geoJson);
      }
    }
  }

  // Replot when props or chart width changes.
  useEffect(() => {
    const entrySet = new Set();
    if (props.display.chartType === ScatterChartType.MAP && !geoJsonFetched) {
      loadSpinner(CONTAINER_ID);
      return;
    } else {
      removeSpinner(CONTAINER_ID);
      replot();
    }
    // ResizeObserver callback function documentation:
    // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver
    const debouncedHandler = _.debounce((entries) => {
      if (_.isEmpty(entries)) return;
      if (entrySet.has(entries[0].target)) {
        replot();
      } else {
        entrySet.add(entries[0].target);
      }
    }, DEBOUNCE_INTERVAL_MS);
    const resizeObserver = new ResizeObserver(debouncedHandler);
    if (chartContainerRef.current) {
      resizeObserver.observe(chartContainerRef.current);
    }
    return () => {
      resizeObserver.unobserve(chartContainerRef.current);
      debouncedHandler.cancel();
    };
  }, [props, chartContainerRef, geoJsonFetched]);

  return (
    <div id="chart" className="chart-section-container" ref={chartContainerRef}>
      <Card className="chart-card">
        <div className="chart-title">
          <h3>{yTitle}</h3>
          <span>vs</span>
          <h3>{xTitle}</h3>
        </div>
        <div className="scatter-chart-container">
          <div id={SVG_CONTAINER_ID} ref={svgContainerRef}></div>
          <div id={MAP_LEGEND_CONTAINER_ID} ref={mapLegendRef}></div>
          <div id="scatter-tooltip" ref={tooltipRef} />
        </div>
      </Card>
      <ToolChartFooter
        chartId="scatter"
        sources={props.sources}
        mMethods={null}
        sourceSelectorSvInfoList={props.sourceSelectorSvInfo}
        onSvMetahashUpdated={props.onSvMetahashUpdated}
        hideIsRatio={true}
      >
        <PlotOptions />
      </ToolChartFooter>
      <div id="scatter-chart-screen" className="screen">
        <div id="spinner"></div>
      </div>
    </div>
  );
}
Example #10
Source File: plot_options.tsx    From website with Apache License 2.0 4 votes vote down vote up
// TODO: Add a new API that given a statvar, a parent place, and a child type,
// returns the available dates for the statvar. Then, fill the datapicker with
// the dates.
function PlotOptions(): JSX.Element {
  const { place, x, y, display } = useContext(Context);
  const [lowerBound, setLowerBound] = useState(
    place.value.lowerBound.toString()
  );
  const [upperBound, setUpperBound] = useState(
    place.value.upperBound.toString()
  );
  const [xDenomInput, setXDenomInput] = useState(x.value.denom);
  const [yDenomInput, setYDenomInput] = useState(y.value.denom);
  const yAxisLabel =
    display.chartType === ScatterChartType.SCATTER
      ? "Y-axis"
      : y.value.statVarInfo.title || y.value.statVarDcid;
  const xAxisLabel =
    display.chartType === ScatterChartType.SCATTER
      ? "X-axis"
      : x.value.statVarInfo.title || x.value.statVarDcid;
  const axisLabelStyle = {};
  if (
    yAxisLabel.length > MIN_WIDTH_LABEL_LENGTH ||
    xAxisLabel.length > MIN_WIDTH_LABEL_LENGTH
  ) {
    axisLabelStyle["width"] =
      Math.min(
        MAX_WIDTH_LABEL_LENGTH,
        Math.max(xAxisLabel.length, yAxisLabel.length)
      ) /
        2 +
      "rem";
  }

  return (
    <Card id="plot-options">
      <Container fluid={true}>
        <div className="plot-options-row">
          <div className="plot-options-label" style={axisLabelStyle}>
            {yAxisLabel}:
          </div>
          <div className="plot-options-input-section">
            <div className="plot-options-input">
              <FormGroup check>
                <Label check>
                  <Input
                    id="per-capita-y"
                    type="checkbox"
                    checked={y.value.perCapita}
                    onChange={(e) => y.setPerCapita(e.target.checked)}
                  />
                  Per Capita
                </Label>
              </FormGroup>
            </div>
            <div className="plot-options-input">
              <FormGroup check>
                <Input
                  id="log-y"
                  type="checkbox"
                  checked={y.value.log}
                  onChange={(e) => checkLog(y, e)}
                />
                <Label check>Log scale</Label>
              </FormGroup>
            </div>
          </div>
        </div>
        <div className="plot-options-row">
          <div className="plot-options-label" style={axisLabelStyle}>
            {xAxisLabel}:
          </div>
          <div className="plot-options-input-section">
            <div className="plot-options-input">
              <FormGroup check>
                <Label check>
                  <Input
                    id="per-capita-x"
                    type="checkbox"
                    checked={x.value.perCapita}
                    onChange={(e) => x.setPerCapita(e.target.checked)}
                  />
                  Per Capita
                </Label>
              </FormGroup>
            </div>
            <div className="plot-options-input">
              <FormGroup check>
                <Input
                  id="log-x"
                  type="checkbox"
                  checked={x.value.log}
                  onChange={(e) => checkLog(x, e)}
                />
                <Label check>Log scale</Label>
              </FormGroup>
            </div>
          </div>
        </div>
        {display.chartType === ScatterChartType.SCATTER && (
          <>
            <div className="plot-options-row">
              <div className="plot-options-label">Display:</div>
              <div className="plot-options-input-section">
                <div className="plot-options-input">
                  <Button
                    id="swap-axes"
                    size="sm"
                    color="light"
                    onClick={() => swapAxes(x, y)}
                    className="plot-options-swap-button"
                  >
                    Swap X and Y axes
                  </Button>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="quadrants"
                        type="checkbox"
                        checked={display.showQuadrants}
                        onChange={(e) => checkQuadrants(display, e)}
                      />
                      Show quadrants
                    </Label>
                  </FormGroup>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="quadrants"
                        type="checkbox"
                        checked={display.showLabels}
                        onChange={(e) => checkLabels(display, e)}
                      />
                      Show labels
                    </Label>
                  </FormGroup>
                </div>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Label check>
                      <Input
                        id="density"
                        type="checkbox"
                        checked={display.showDensity}
                        onChange={(e) => checkDensity(display, e)}
                      />
                      Show density
                    </Label>
                  </FormGroup>
                </div>
              </div>
            </div>
            <div className="plot-options-row">
              <div className="plot-options-label">Filter by population:</div>
              <div className="plot-options-input-section pop-filter">
                <div className="plot-options-input">
                  <FormGroup check>
                    <Input
                      className="pop-filter-input"
                      type="number"
                      onChange={(e) =>
                        selectLowerBound(place, e, setLowerBound)
                      }
                      value={lowerBound}
                      onBlur={() =>
                        setLowerBound(place.value.lowerBound.toString())
                      }
                    />
                  </FormGroup>
                </div>
                <span>to</span>
                <div className="plot-options-input">
                  <FormGroup check>
                    <Input
                      className="pop-filter-input"
                      type="number"
                      onChange={(e) =>
                        selectUpperBound(place, e, setUpperBound)
                      }
                      value={upperBound}
                      onBlur={() =>
                        setUpperBound(place.value.upperBound.toString())
                      }
                    />
                  </FormGroup>
                </div>
              </div>
            </div>
          </>
        )}
      </Container>
    </Card>
  );
}
Example #11
Source File: page.tsx    From website with Apache License 2.0 4 votes vote down vote up
render(): JSX.Element {
    const numPlaces = Object.keys(this.state.placeName).length;
    const numStatVarInfo = Object.keys(this.state.statVarInfo).length;
    const namedPlaces: NamedPlace[] = [];
    for (const place in this.state.placeName) {
      namedPlaces.push({ dcid: place, name: this.state.placeName[place] });
    }
    const statVarTokens = Array.from(
      getTokensFromUrl(TIMELINE_URL_PARAM_KEYS.STAT_VAR, statVarSep)
    );
    const statVars = statVarTokens.map((sv) =>
      sv.includes("|") ? sv.split("|")[0] : sv
    );

    const deselectSVs = (svList: string[]) => {
      const availableSVs = statVars.filter((sv) => svList.indexOf(sv) === -1);
      const statVarTokenInfo = {
        name: TIMELINE_URL_PARAM_KEYS.STAT_VAR,
        sep: statVarSep,
        tokens: new Set(availableSVs),
      };
      setTokensToUrl([statVarTokenInfo]);
    };

    const svToSvInfo = {};
    for (const sv of statVars) {
      svToSvInfo[sv] =
        sv in this.state.statVarInfo ? this.state.statVarInfo[sv] : {};
    }

    return (
      <>
        <StatVarWidget
          openSvHierarchyModal={this.state.showSvHierarchyModal}
          openSvHierarchyModalCallback={this.toggleSvHierarchyModal}
          collapsible={true}
          svHierarchyType={StatVarHierarchyType.SCATTER}
          samplePlaces={namedPlaces}
          deselectSVs={deselectSVs}
          selectedSVs={svToSvInfo}
          selectSV={(sv) =>
            addToken(TIMELINE_URL_PARAM_KEYS.STAT_VAR, statVarSep, sv)
          }
        />
        <div id="plot-container">
          <Container fluid={true}>
            {numPlaces === 0 && <h1 className="mb-4">Timelines Explorer</h1>}
            <Card id="place-search">
              <Row>
                <Col sm={12}>
                  <p>Select places:</p>
                </Col>
                <Col sm={12}>
                  <SearchBar
                    places={this.state.placeName}
                    addPlace={(place) =>
                      addToken(TIMELINE_URL_PARAM_KEYS.PLACE, placeSep, place)
                    }
                    removePlace={(place) => {
                      removeToken(
                        TIMELINE_URL_PARAM_KEYS.PLACE,
                        placeSep,
                        place
                      );
                    }}
                  />
                </Col>
              </Row>
              <Row className="d-lg-none">
                <Col>
                  <Button color="primary" onClick={this.toggleSvHierarchyModal}>
                    Select variables
                  </Button>
                </Col>
              </Row>
            </Card>
            {numPlaces === 0 && <Info />}
            {numPlaces !== 0 && numStatVarInfo !== 0 && (
              <div id="chart-region">
                <ChartRegion
                  placeName={this.state.placeName}
                  statVarInfo={this.state.statVarInfo}
                  statVarOrder={statVars}
                ></ChartRegion>
              </div>
            )}
          </Container>
        </div>
      </>
    );
  }
Example #12
Source File: ExportDialogButton.tsx    From nextclade with MIT License 4 votes vote down vote up
export function ExportDialogButton() {
  const { t } = useTranslationSafe()

  const [isOpen, setIsOpen] = useState<boolean>(false)
  const toggleOpen = useCallback(() => setIsOpen((isOpen) => !isOpen), [])
  const open = useCallback(() => setIsOpen(true), [])
  const close = useCallback(() => setIsOpen(false), [])

  const canDownload = useRecoilValue(canDownloadAtom)

  // TODO: We could probably use a map and then iterate over it, to reduce duplication
  const exportZip = useExportZip()
  const exportFasta = useExportFasta()
  const exportCsv = useExportCsv()
  const exportTsv = useExportTsv()
  const exportJson = useExportJson()
  const exportNdjson = useExportNdjson()
  const exportPeptides = useExportPeptides()
  const exportTree = useExportTree()
  const exportInsertionsCsv = useExportInsertionsCsv()
  const exportErrorsCsv = useExportErrorsCsv()

  const exportParams = useMemo(() => DEFAULT_EXPORT_PARAMS, [])

  return (
    <>
      <PanelButton type="button" onClick={open} title={t('Download results')} disabled={!canDownload}>
        <DownloadIcon />
      </PanelButton>
      <Modal centered isOpen={isOpen} toggle={toggleOpen} fade={false} size="lg">
        <ModalHeader toggle={close} tag="div" className="d-flex">
          <h4 className="mx-auto">
            <DownloadIcon />
            {t('Download results')}
          </h4>
        </ModalHeader>
        <ModalBody>
          <Row>
            <Col>
              <Card>
                <ListGroup flush>
                  <ExportFileElement
                    Icon={FileIconJson}
                    filename={exportParams.filenameJson}
                    HelpMain={t('Results of the analysis in JSON format.')}
                    HelpDetails={t(
                      'Contains detailed results of the analysis, such as clades, mutations, QC metrics etc., in JSON format. Convenient for further automated processing.',
                    )}
                    HelpDownload={t('Download results of the analysis in JSON format.')}
                    onDownload={exportJson}
                  />

                  <ExportFileElement
                    Icon={FileIconNdjson}
                    filename={exportParams.filenameNdjson}
                    HelpMain={t('Results of the analysis in NDJSON format (newline-delimited JSON).')}
                    HelpDetails={t(
                      'Contains detailed results of the analysis, such as clades, mutations, QC metrics etc., in NDJSON format. Convenient for further automated processing.',
                    )}
                    HelpDownload={t('Download results of the analysis in NDJSON format.')}
                    onDownload={exportNdjson}
                  />

                  <ExportFileElement
                    Icon={FileIconCsv}
                    filename={exportParams.filenameCsv}
                    HelpMain={t('Summarized results of the analysis in CSV format.')}
                    HelpDetails={t(
                      'Contains summarized results of the analysis, such as clades, mutations, QC metrics etc., in tabular format. Convenient for further review and processing using spreadsheets or data-science tools.',
                    )}
                    HelpDownload={t('Download summarized results in CSV format')}
                    onDownload={exportCsv}
                  />

                  <ExportFileElement
                    Icon={FileIconTsv}
                    filename={exportParams.filenameTsv}
                    HelpMain={t('Summarized results of the analysis in TSV format.')}
                    HelpDetails={t(
                      'Contains summarized results of the analysis, such as clades, mutations, QC metrics etc in tabular format. Convenient for further review and processing using spreadsheets or data-science tools.',
                    )}
                    HelpDownload={t('Download summarized results in TSV format')}
                    onDownload={exportTsv}
                  />

                  <ExportFileElement
                    Icon={FileIconJson}
                    filename={exportParams.filenameTree}
                    HelpMain={t('Phylogenetic tree with sequenced placed onto it.')}
                    HelpDetails={
                      <>
                        {t('The tree is in Nextstrain format.')}{' '}
                        {t('Can be viewed locally with Nextstrain Auspice or in {{auspice_us}}')}
                        <LinkExternal url="https://auspice.us">{'auspice.us'}</LinkExternal>
                        {'.'}
                      </>
                    }
                    HelpDownload={t(
                      'Download phylogenetic tree with sequenced placed onto it, in Auspice JSON v2 format.',
                    )}
                    onDownload={exportTree}
                  />

                  <ExportFileElement
                    Icon={FileIconFasta}
                    filename={exportParams.filenameFasta}
                    HelpMain={t('Aligned sequences in FASTA format.')}
                    HelpDetails={t('Contains aligned sequences in FASTA format.')}
                    HelpDownload={t('Download aligned sequences in FASTA format.')}
                    onDownload={exportFasta}
                  />

                  <ExportFileElement
                    Icon={FileIconZip}
                    filename={exportParams.filenamePeptidesZip}
                    HelpMain={t('Aligned peptides in FASTA format, zipped')}
                    HelpDetails={t(
                      'Contains results of translation of your sequences. One FASTA file per gene, all in a zip archive.',
                    )}
                    HelpDownload={t(
                      'Download aligned peptides in FASTA format, one file per gene, all in a zip archive.',
                    )}
                    onDownload={exportPeptides}
                  />

                  <ExportFileElement
                    Icon={FileIconCsv}
                    filename={exportParams.filenameInsertionsCsv}
                    HelpMain={t('Insertions in CSV format.')}
                    HelpDetails={t('Contains insertions stripped from aligned sequences.')}
                    HelpDownload={t('Download insertions in CSV format')}
                    onDownload={exportInsertionsCsv}
                  />

                  <ExportFileElement
                    Icon={FileIconCsv}
                    filename={exportParams.filenameErrorsCsv}
                    HelpMain={t('Errors, warnings, and failed genes in CSV format.')}
                    HelpDetails={t(
                      'Contains a list of errors, a list of warnings and a list of genes that failed processing, per sequence, in CSV format.',
                    )}
                    HelpDownload={t('Download warnings, and failed genes in CSV format')}
                    onDownload={exportErrorsCsv}
                  />

                  <ExportFileElement
                    Icon={FileIconZip}
                    filename={exportParams.filenameZip}
                    HelpMain={t('All files in a zip archive.')}
                    HelpDetails={t('Contains all of the above files in a single zip file.')}
                    HelpDownload={t('Download all in zip archive')}
                    onDownload={exportZip}
                  />
                </ListGroup>
              </Card>
            </Col>
          </Row>
        </ModalBody>
        <ModalFooter>
          <Button type="button" onClick={close} title={t('Close')}>
            <div>{t('Close')}</div>
          </Button>
        </ModalFooter>
      </Modal>
    </>
  )
}
Example #13
Source File: CovidCard.tsx    From health-cards-tests with MIT License 4 votes vote down vote up
CovidCard: React.FC<{
    holderState: HolderState,
    smartState: SmartState,
    uiState: UiState,
    displayQr: (vc) => Promise<void>,
    openScannerUi: () => Promise<void>,
    connectToIssuer: () => Promise<void>,
    connectToFhir: () => Promise<void>,
    dispatchToHolder: (e: Promise<any>) => Promise<void>
}> = ({ holderState, smartState, uiState, displayQr, openScannerUi, connectToFhir, connectToIssuer, dispatchToHolder }) => {
    const issuerInteractions = holderState.interactions.filter(i => i.siopPartnerRole === 'issuer').slice(-1)
    const issuerInteraction = issuerInteractions.length ? issuerInteractions[0] : null


    let currentStep = CardStep.CONFIGURE_WALLET;
    /* tslint:disable-next-line:prefer-conditional-expression */
    if (issuerInteraction?.status !== 'complete') {
        currentStep = CardStep.CONNECT_TO_ISSUER;
    } else {
        currentStep = CardStep.DOWNLOAD_CREDENTIAL;
    }
    if (holderState.vcStore.length) {
        currentStep = CardStep.COMPLETE
    }

    const retrieveVcClick = async () => {
        const onMessage = async ({ data, source }) => {
            const { verifiableCredential } = data
            window.removeEventListener("message", onMessage)
            await dispatchToHolder(receiveVcs(verifiableCredential, holderState))
        }
        window.addEventListener("message", onMessage)
        window.open(uiState.issuer.issuerDownloadUrl)
    }

    useEffect(() => {
        if (smartState?.access_token && holderState.vcStore.length === 0) {
            const credentials = axios.post(uiState.fhirClient.server + `/Patient/${smartState.patient}/$health-cards-issue`, {
                "resourceType": "Parameters",
                "parameter": [{
                    "name": "credentialType",
                    "valueUri": "https://smarthealth.cards#immunization"
                },{
                    "name": "presentationContext",
                    "valueUri": "https://smarthealth.cards#presentation-context-online"
                }, {
                    "name": "encryptForKeyId",
                    "valueString": "#encryption-key-1"
                }]
            })
            credentials.then(response => {
                const vcs = response.data.parameter.filter(p => p.name === 'verifiableCredential').map(p => p.valueString)
                dispatchToHolder(receiveVcs(vcs, holderState))
            })
        }
    }, [smartState])


    const covidVcs = holderState.vcStore.filter(vc => vc.type.includes("https://smarthealth.cards#covid19"));

    const resources = covidVcs.flatMap(vc =>
        vc.vcPayload.vc.credentialSubject.fhirBundle.entry
            .flatMap(e => e.resource))

    const doses = resources.filter(r => r.resourceType === 'Immunization').length;
    const patient = resources.filter(r => r.resourceType === 'Patient')[0];
    console.log("P", patient);


    useEffect(() => {
        if (covidVcs.length === 0) {
            return;
        }
        let file = new File([JSON.stringify({
            "verifiableCredential": covidVcs.map(v => v.vcSigned)
        })], "c19.smart-health-card", {
            type: "application/smart-health-card"
        })
        const url = window.URL.createObjectURL(file);
        setDownloadFileUrl(url)
    }, [covidVcs.length])
    const [downloadFileUrl, setDownloadFileUrl] = useState("");

    return <> {
        currentStep === CardStep.COMPLETE && <Card style={{ border: "1px solid grey", padding: ".5em", marginBottom: "1em" }}>
            <CardTitle style={{ fontWeight: "bolder" }}>
                COVID Cards ({covidVcs.length})
                </CardTitle>

            <CardSubtitle className="text-muted">Your COVID results are ready to share, based on {" "}
                {resources && <>{resources.length} FHIR Resource{resources.length > 1 ? "s" : ""} <br /> </>}
            </CardSubtitle>
            <ul>
                <li> Name: {patient.name[0].given[0]} {patient.name[0].family}</li>
                <li> Birthdate: {patient.birthDate}</li>
                <li> Immunization doses received: {doses}</li>
            </ul>
            <Button className="mb-1" color="info" onClick={() => displayQr(covidVcs[0])}>Display QR</Button>
            <a href={downloadFileUrl} download="covid19.smart-health-card">Download file</a>
        </Card>
    } {currentStep < CardStep.COMPLETE &&
        <Card style={{ border: ".25em dashed grey", padding: ".5em", marginBottom: "1em" }}>
            <CardTitle>COVID Cards </CardTitle>
            <CardSubtitle className="text-muted">You don't have any COVID cards in your wallet yet.</CardSubtitle>

            <Button disabled={true} className="mb-1" color="info">
                {currentStep > CardStep.CONFIGURE_WALLET && '✓ '} 1. Set up your Health Wallet</Button>

            <RS.UncontrolledButtonDropdown className="mb-1" >
                <DropdownToggle caret color={currentStep === CardStep.CONNECT_TO_ISSUER ? 'success' : 'info'} >
                    {currentStep > CardStep.CONNECT_TO_ISSUER && '✓ '}
                                    2. Get your Vaccination Credential
                                </DropdownToggle>
                <DropdownMenu style={{ width: "100%" }}>
                    <DropdownItem onClick={connectToFhir} >Connect with SMART on FHIR </DropdownItem>
                    <DropdownItem >Load from file (todo)</DropdownItem>
                </DropdownMenu>
            </RS.UncontrolledButtonDropdown>
            <Button
                disabled={currentStep !== CardStep.DOWNLOAD_CREDENTIAL}
                onClick={retrieveVcClick}
                className="mb-1"
                color={currentStep === CardStep.DOWNLOAD_CREDENTIAL ? 'success' : 'info'} >
                3. Save COVID card to wallet</Button>
        </Card>
        }
    </>

}
Example #14
Source File: holder-page.tsx    From health-cards-tests with MIT License 4 votes vote down vote up
App: React.FC<AppProps> = (props) => {
    const [holderState, setHolderState] = useState<HolderState>(props.initialHolderState)
    const [uiState, dispatch] = useReducer(uiReducer, props.initialUiState)

    const [smartState, setSmartState] = useState<SmartState | null>(null)

    const issuerInteractions = holderState.interactions.filter(i => i.siopPartnerRole === 'issuer').slice(-1)
    const verifierInteractions = holderState.interactions.filter(i => i.siopPartnerRole === 'verifier').slice(-1)
    const siopAtNeedQr = issuerInteractions.concat(verifierInteractions).filter(i => i.status === 'need-qrcode').slice(-1)

    useEffect(() => {
        holderState.interactions.filter(i => i.status === 'need-redirect').forEach(i => {
            const redirectUrl = i.siopRequest.client_id + '#' + qs.encode(i.siopResponse.formPostBody)
            const opened = window.open(redirectUrl, "_blank")
            dispatchToHolder({ 'type': "siop-response-complete" })
        })
    }, [holderState.interactions])

    const dispatchToHolder = async (ePromise) => {
        const e = await ePromise
        const holder = await holderReducer(holderState, e)
        setHolderState(state => holder)
        console.log("After event", e, "Holder state is", holder)
    }

    const connectTo = who => async () => {
        dispatchToHolder({ 'type': 'begin-interaction', who })
    }

    const onScanned = async (qrCodeUrl: string) => {
        dispatch({ type: 'scan-barcode' })
        await dispatchToHolder(receiveSiopRequest(qrCodeUrl, holderState));
    }

    const connectToFhir = async () => {
        const connected = await makeFhirConnector(uiState, holderState)
        setSmartState(connected.newSmartState)
    }

    const [isOpen, setIsOpen] = useState(false);
    const toggle = () => setIsOpen(!isOpen);


    return <div style={{ paddingTop: "5em" }}>
        <RS.Navbar expand="" className="navbar-dark bg-info fixed-top">
            <RS.Container>
                <NavbarBrand style={{ marginRight: "2em" }} href="/">
                    <img className="d-inline-block" style={{ maxHeight: "1em", maxWidth: "1em", marginRight: "10px" }} src={logo} />
                            Health Wallet Demo
                    </NavbarBrand>
                <NavbarToggler onClick={toggle} />
                <Collapse navbar={true} isOpen={isOpen}>
                    <Nav navbar={true}>
                        <NavLink href="#" onClick={() => {
                            dispatch({ type: 'open-scanner', 'label': 'Verifier' })
                        }}>Scan QR to Share</NavLink>
                        <NavLink href="#" onClick={connectTo('verifier')}> Open Employer Portal</NavLink>
                        <NavLink href="#config" onClick={e => dispatch({ type: 'toggle-editing-config' })}> Edit Config</NavLink>
                        <NavLink target="_blank" href="https://github.com/microsoft-healthcare-madison/health-wallet-demo">Source on GitHub</NavLink>
                    </Nav>
                </Collapse></RS.Container>
        </RS.Navbar>

        {uiState.scanningBarcode?.active &&
            <SiopRequestReceiver
                onReady={onScanned}
                onCancel={() => dispatch({'type': 'close-scanner'})}
                redirectMode="qr"
                label={uiState.scanningBarcode?.label}
            />
        }

        {siopAtNeedQr.length > 0 &&
            <SiopRequestReceiver
                onReady={onScanned}
                onCancel={() => dispatch({'type': 'close-scanner'})}
                redirectMode="window-open"
                label={siopAtNeedQr[0].siopPartnerRole}
                startUrl={siopAtNeedQr[0].siopPartnerRole === 'issuer' ? uiState.issuer.issuerStartUrl : uiState.verifier.verifierStartUrl}
            />}

        {uiState.editingConfig && <ConfigEditModal uiState={uiState} defaultUiState={props.defaultUiState} dispatch={dispatch} />}
        {uiState.presentingQr?.active && <QRPresentationModal healthCard={uiState.presentingQr.vcToPresent} dispatch={dispatch} />}

        <SiopApprovalModal  {...parseSiopApprovalProps(holderState, dispatchToHolder)} />

        <RS.Container >
            <RS.Row>
                <RS.Col xs="12">
                    <CovidCard
                        holderState={holderState}
                        smartState={smartState}
                        uiState={uiState}
                        displayQr={async (vc) => {
                            dispatch({type: 'begin-qr-presentation', vc})
                        }}
                        openScannerUi={async () => {
                            dispatch({ type: 'open-scanner', 'label': 'Lab' })
                        }}
                        connectToIssuer={connectTo('issuer')}
                        connectToFhir={connectToFhir}
                        dispatchToHolder={dispatchToHolder}
                    />
                    <Card style={{ padding: ".5em" }}>
                        <CardTitle style={{ fontWeight: "bolder" }}>
                            Debugging Details
                        </CardTitle>
                        <CardSubtitle className="text-muted">Your browser's dev tools will show details on current page state + events </CardSubtitle>
                    </Card>
                </RS.Col>
            </RS.Row>
        </RS.Container>
        <div>
        </div>
    </div>
}