gatsby-plugin-google-analytics#trackCustomEvent JavaScript Examples

The following examples show how to use gatsby-plugin-google-analytics#trackCustomEvent. 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: high-risk.js    From warsinhk with MIT License 6 votes vote down vote up
HighRiskCardItem = ({ node, i18n, t, isActive }) => (
  <HighRiskCardContainer
    isActive={isActive}
    onClick={() =>
      trackCustomEvent({
        category: "high_risk_list",
        action: "click_item",
        label: `${node.node.sub_district_zh} | ${node.node.location_zh}`,
      })
    }
  >
    <Item node={node.node} i18n={i18n} t={t} />
  </HighRiskCardContainer>
)
Example #2
Source File: RandomButton.js    From pdxtipjar with MIT License 6 votes vote down vote up
RandomButton = ({ text, handleClick }) => {
  return (
    <button
      className="random-button"
      onClick={() => {
        trackCustomEvent({
          category: "Random Button",
          action: "Click",
          label: text,
        });

        getSheets()
          .then(response => {
            const rows = response.slice(1); // Skip header row
            const randomInt = getRandomInt(rows.length);
            return handleClick(rows[randomInt]);
          })
          .catch(err => console.log(err));
      }}
    >
      {text}
    </button>
  );
}
Example #3
Source File: page-layout.js    From guitar-book with MIT License 5 votes vote down vote up
function handleToggleAll(expanded) {
  trackCustomEvent({
    category: GA_EVENT_CATEGORY_SIDEBAR,
    action: 'Toggle all',
    label: expanded ? 'expand' : 'collapse'
  });
}
Example #4
Source File: wars-tips.js    From warsinhk with MIT License 5 votes vote down vote up
WarTipsPage = ({ data, location }) => {
  const { t, i18n } = useTranslation()
  const [selectedTag, setSelectedTag] = useState(null)

  const filterByTags = ({ node }) => {
    if (!node.tags || !selectedTag) {
      return true
    }
    return !!node.tags.split(",").find(tag => tag === selectedTag)
  }

  const getAllTags = edges => {
    return _uniq(
      _flatten(
        edges.map(edge => (edge.node.tags ? edge.node.tags.split(",") : []))
      )
    )
  }

  const shorten = str => {
    return str ? `${str.substring(0, 50)}...` : ""
  }
  React.useEffect(() => {
    if (location.hash) {
      const tag = decodeURIComponent(location.hash.replace(/^#/, ""))
      setSelectedTag(tag)
    }
  }, [location.hash])

  return (
    <Layout>
      <SEO title="WarsTipsPage" />
      <Typography variant="h2">{t("wars_tips.title")}</Typography>
      {getAllTags(data.allWarsTip.edges).map(tag => (
        <Button
          key={tag}
          size="small"
          color={tag === selectedTag ? "secondary" : "primary"}
          onClick={evt => {
            const tagToSet = tag === selectedTag ? null : tag
            setSelectedTag(tagToSet)
            window.location.href = `#${tagToSet || ""}`
            trackCustomEvent({
              category: "wars_tips",
              action: "click_tag",
              label: tag,
            })
            evt.stopPropagation()
            evt.preventDefault()
          }}
        >
          {`#${tag}`}
        </Button>
      ))}
      <InfiniteScroll
        list={data.allWarsTip.edges.filter(filterByTags)}
        step={{ mobile: 5 }}
        Wrapper={ResponsiveWrapper}
        onItem={({ node }, index) => (
          <CardContainer key={index}>
            <MediaCard
              imageUrl={node.image_url}
              title={node.title}
              text={shorten(node.text)}
              tags={node.tags ? node.tags.split(",") : []}
              sourceDescription={node.source_description}
              sourceUrl={node.source_url}
              onTagClicked={tag => {
                setSelectedTag(tag === selectedTag ? null : tag)
              }}
              uri={getWarTipPath(i18n.language, node.title)}
            />
          </CardContainer>
        )}
      />
    </Layout>
  )
}
Example #5
Source File: MediaCard.js    From warsinhk with MIT License 5 votes vote down vote up
export function MediaCard(props) {
  const {
    title,
    text,
    imageUrl,
    sourceDescription,
    sourceUrl,
    tags,
    onTagClicked,
    uri,
  } = props
  const { t } = useTranslation()
  return (
    <StyledMediaCard>
      <>
        <Link to={uri}>
          <StyledCardMedia image={imageUrl} title="Contemplative Reptile" />
        </Link>

        <StyledCardContent>
          {tags &&
            tags.map((tag, index) => (
              <Button
                key={index}
                size="small"
                color="primary"
                href={sourceUrl}
                onClick={evt => {
                  onTagClicked(tag)
                  trackCustomEvent({
                    category: "wars_tips",
                    action: "click_tag",
                    label: tag,
                  })
                  evt.stopPropagation()
                  evt.preventDefault()
                }}
              >
                {`#${tag}`}
              </Button>
            ))}
          <Link to={uri}>
            <Typography gutterBottom variant="h5" component="h2">
              {title}
            </Typography>

            {text && (
              <Typography variant="body2" color="textSecondary" component="p">
                {text}
              </Typography>
            )}
          </Link>
        </StyledCardContent>
      </>

      <CardActions>
        <Button
          size="small"
          color="primary"
          href={sourceUrl}
          onClick={() => {
            trackCustomEvent({
              category: "wars_tips",
              action: "click_source",
              label: sourceUrl,
            })
          }}
        >
          {`${t("wars_tips.source")}${sourceDescription}`}
        </Button>
      </CardActions>
    </StyledMediaCard>
  )
}
Example #6
Source File: section-nav.js    From webrtc-tutorial with MIT License 5 votes vote down vote up
function handleHeadingClick(event) {
  trackCustomEvent({
    category: 'Section Nav',
    action: 'Heading click',
    label: event.target.innerText
  });
}
Example #7
Source File: FeedbackForm.js    From Lambda with MIT License 5 votes vote down vote up
FeedbackForm = () => {
  const [feedbackGiven, setFeedbackGiven] = useState(false);

  if (feedbackGiven) {
    return 'Thanks for letting us know!';
  } else {
    return (
      <span>
        Is this page useful?
        <button
          css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '6px'}]}
          aria-label="Yes"
          onClick={(e) => {
            e.preventDefault();
            trackCustomEvent({
              category: 'Feedback Button',
              action: 'feedback',
              label: window.location.pathname,
              value: 1,
            });
            setFeedbackGiven(true);
          }}
        >
          <svg
            css={{
              transform: 'translateY(0.1em)',
            }}
            focusable="false"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 81.13 89.76"
          >
            <path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" />
          </svg>
        </button>
        <button
          css={[sharedStyles.articleLayout.feedbackButton, {marginLeft: '3px'}]}
          aria-label="No"
          onClick={(e) => {
            e.preventDefault();
            trackCustomEvent({
              category: 'Feedback Button',
              action: 'feedback',
              label: window.location.pathname,
              value: 0,
            });
            setFeedbackGiven(true);
          }}
        >
          <svg
            css={{
              transform: 'scale(-1, -1) translateY(-.6em)',
            }}
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 81.13 89.76"
          >
            <path d="M22.9 6a18.57 18.57 0 002.67 8.4 25.72 25.72 0 008.65 7.66c3.86 2 8.67 7.13 13.51 11 3.86 3.11 8.57 7.11 11.54 8.45s13.59.26 14.64 1.17c1.88 1.63 1.55 9-.11 15.25-1.61 5.86-5.96 10.55-6.48 16.86-.4 4.83-2.7 4.88-10.93 4.88h-1.35c-3.82 0-8.24 2.93-12.92 3.62a68 68 0 01-9.73.5c-3.57 0-7.86-.08-13.25-.08-3.56 0-4.71-1.83-4.71-4.48h8.42a3.51 3.51 0 000-7H12.28a2.89 2.89 0 01-2.88-2.88 1.91 1.91 0 01.77-1.78h16.46a3.51 3.51 0 000-7H12.29c-3.21 0-4.84-1.83-4.84-4a6.41 6.41 0 011.17-3.78h19.06a3.5 3.5 0 100-7H9.75A3.51 3.51 0 016 42.27a3.45 3.45 0 013.75-3.48h13.11c5.61 0 7.71-3 5.71-5.52-4.43-4.74-10.84-12.62-11-18.71-.15-6.51 2.6-7.83 5.36-8.56m0-6a6.18 6.18 0 00-1.53.2c-6.69 1.77-10 6.65-9.82 14.5.08 5.09 2.99 11.18 8.52 18.09H9.74a9.52 9.52 0 00-6.23 16.9 12.52 12.52 0 00-2.07 6.84 9.64 9.64 0 003.65 7.7 7.85 7.85 0 00-1.7 5.13 8.9 8.9 0 005.3 8.13 6 6 0 00-.26 1.76c0 6.37 4.2 10.48 10.71 10.48h13.25a73.75 73.75 0 0010.6-.56 35.89 35.89 0 007.58-2.18 17.83 17.83 0 014.48-1.34h1.35c4.69 0 7.79 0 10.5-1 3.85-1.44 6-4.59 6.41-9.38.2-2.46 1.42-4.85 2.84-7.62a41.3 41.3 0 003.42-8.13 48 48 0 001.59-10.79c.1-5.13-1-8.48-3.35-10.55-2.16-1.87-4.64-1.87-9.6-1.88a46.86 46.86 0 01-6.64-.29c-1.92-.94-5.72-4-8.51-6.3l-1.58-1.28c-1.6-1.3-3.27-2.79-4.87-4.23-3.33-3-6.47-5.79-9.61-7.45a20.2 20.2 0 01-6.43-5.53 12.44 12.44 0 01-1.72-5.36 6 6 0 00-6-5.86z" />
          </svg>
        </button>
      </span>
    );
  }
}
Example #8
Source File: hero.js    From bartzalewski.com-v2 with MIT License 5 votes vote down vote up
export default function Hero() {
  const data = useStaticQuery(graphql`
    query {
      site {
        siteMetadata {
          author
          role
        }
      }
    }
  `)
  const { author, role } = data.site.siteMetadata

  return (
    <HeroSection id="home">
      <Header />
      <HeroContainer className="container hero__container">
        <div className="container__hero--secondary container--secondary">
          <Container className="container--primary">
            <Left className="hero__left">
              <Greeting className="colored">Hi, I am</Greeting>
              <Author>{author}</Author>
              <Role className="hero__sub">{role}</Role>
              <Description className="hero__desc section__desc">
                I specialize in designing, building, shipping, and scaling
                beautiful, usable products with blazing-fast efficiency.
              </Description>
              <Link
                href="#projects"
                className="btn btn--primary"
                onClick={(e) => {
                  e.preventDefault()
                  trackCustomEvent({
                    category: 'See my work Button',
                    action: 'Click',
                    label: 'Gatsby Google Analytics See my work Button',
                  })
                }}
              >
                See my work
              </Link>
            </Left>
            <Right className="hero__right">
              <Board />
            </Right>
            <Image
              className="decoration decoration__cross"
              src={cross}
              alt="cross"
              loading="lazy"
            />
            <Image
              className="decoration decoration__tick"
              src={tick}
              alt="tick"
              loading="lazy"
            />
            <Image
              className="decoration decoration__circle"
              src={circle}
              alt="circle"
              loading="lazy"
            />
          </Container>
        </div>
      </HeroContainer>
    </HeroSection>
  )
}
Example #9
Source File: section-nav.js    From guitar-book with MIT License 5 votes vote down vote up
function handleHeadingClick(event) {
  trackCustomEvent({
    category: 'Section Nav',
    action: 'Heading click',
    label: event.target.innerText
  });
}
Example #10
Source File: page-layout.js    From guitar-book with MIT License 5 votes vote down vote up
function handleToggleCategory(label, expanded) {
  trackCustomEvent({
    category: GA_EVENT_CATEGORY_SIDEBAR,
    action: 'Toggle category',
    label,
    value: Number(expanded)
  });
}
Example #11
Source File: CTA.js    From website with MIT License 4 votes vote down vote up
CTA = props => {
  const {
    link: linkDefault,
    text: textDefault,
    align = 'left',
    newTab,
    iconConfig,
    color,
    button = false,
    showRightArrow = false,
    showLeftArrow = false,
    typeLayout = '',
    buttonSize,
    customClick,
    buttonDisplay,
    fontSize,
    buttonGradient,
    downloadBrowsers,
    eventCategory,
    eventLabel,
    hubSpotForm,
    buttonSecondary,
    socialLink,
  } = props
  const [keyBrowser, setKeyBrowser] = React.useState('chrome')
  const isButton = buttonDisplay || button
  const defaultIconConfig = { width: '1.5em', height: '0.5em', fill: 'black' }
  const icon = { ...defaultIconConfig, fill: color, ...iconConfig }
  const isDownloadBrowser = !isEmpty(downloadBrowsers)
  const [delayShow, setDelayShow] = React.useState(isDownloadBrowser)
  const [showPopup, setShowPopup] = React.useState(false)
  let text = textDefault,
    link = linkDefault,
    label = eventLabel,
    lowerBrowserName = lowerCase(browserName),
    iconBrowser = ''
  if (isDownloadBrowser && keyBrowser && downloadBrowsers[keyBrowser]) {
    label = eventLabel.replace('$browser', downloadBrowsers[keyBrowser]?.text)
    text = textDefault.replace('$browser', downloadBrowsers[keyBrowser]?.text)
    if (['ios', 'android', 'not-supported'].includes(keyBrowser)) {
      text = downloadBrowsers[keyBrowser]?.text
    }
    link = downloadBrowsers[keyBrowser]?.link
    iconBrowser = downloadBrowsers[keyBrowser]?.icon
  }
  const onClosePopup = () => {
    setShowPopup(false)
  }
  const handleCustomClick = e => {
    if (hubSpotForm) {
      setShowPopup(true)
      return
    }
    if (customClick) {
      e.preventDefault()
      customClick()
    }
    if (eventCategory && eventLabel) {
      trackCustomEvent({
        category: eventCategory,
        action: 'Click',
        label: label,
      })
    }
  }
  React.useEffect(() => {
    if (isDownloadBrowser) {
      if (
        isMobile &&
        ((isAndroid && downloadBrowsers['android']) ||
          (isIOS && downloadBrowsers['ios']))
      ) {
        if (isAndroid && downloadBrowsers['android']) {
          setKeyBrowser('android')
        } else if (isIOS && downloadBrowsers['ios']) {
          setKeyBrowser('ios')
        }
      } else {
        if (typeof navigator?.brave !== 'undefined') {
          lowerBrowserName = 'brave'
        }
        if (downloadBrowsers[lowerBrowserName]) {
          setKeyBrowser(lowerBrowserName)
        } else if (downloadBrowsers['not-supported']) {
          setKeyBrowser('not-supported')
        } else {
          setKeyBrowser('chrome')
        }
      }
      setDelayShow(false)
    }
  }, [downloadBrowsers, isDownloadBrowser, lowerBrowserName])
  let ele = (
    <CTAContainer
      className={classnames('ctaModuleContainer', {
        socialLink: socialLink,
      })}
      align={align}
    >
      <ContentWrapper
        to={link}
        newTab={newTab || isDownloadBrowser}
        color={color}
        typeLayout={typeLayout}
        onClick={handleCustomClick}
      >
        {socialLink ? <SocialIcon name={socialLink} /> : null}
        <LinkTitle
          className={classnames({
            [`leftArrow`]: showLeftArrow,
            [`rightArrow`]: showRightArrow || socialLink,
          })}
        >
          {showLeftArrow ? <Arrow {...icon} transform={'rotate(180)'} /> : null}
          {text}
          {showRightArrow || socialLink ? <Arrow {...icon} /> : null}
        </LinkTitle>
      </ContentWrapper>
    </CTAContainer>
  )
  if (isButton) {
    ele = (
      <Button
        size={buttonSize}
        link={link}
        text={text}
        className={keyBrowser}
        newTab={newTab || isDownloadBrowser}
        color={buttonSecondary ? 'secondary' : color}
        customClick={handleCustomClick}
        fontSize={fontSize}
        buttonGradient={buttonGradient}
        eventCategory={eventCategory}
        eventLabel={eventLabel}
        iconUrl={!delayShow ? iconBrowser : ''}
        iconPosition={['ios', 'android'].includes(keyBrowser) ? 'start' : 'end'}
        hide={delayShow}
      />
    )
  }

  if (
    isDownloadBrowser &&
    !Object.keys(downloadBrowsers).includes(lowerBrowserName) &&
    downloadBrowsers['browsers-supported']
  ) {
    ele = (
      <BrowserWrapper>
        <BrowserInfo>
          <BrowserInfoTitle>
            {downloadBrowsers['browsers-supported'].text}
          </BrowserInfoTitle>
          <BrowserInfoDesc>
            {downloadBrowsers['browsers-supported'].description}
          </BrowserInfoDesc>
        </BrowserInfo>
        <BrowserList>
          {Object.keys(downloadBrowsers).map(key => {
            const { link, icon, text } = downloadBrowsers[key]
            if (['chrome', 'firefox', 'brave', 'edge'].includes(key)) {
              return (
                <BrowserItem key={text} to={link} newTab>
                  <Image src={icon} />
                  <BrowserName>{text}</BrowserName>
                </BrowserItem>
              )
            }
            return null
          })}
        </BrowserList>
      </BrowserWrapper>
    )
  }

  return (
    <>
      {ele}
      {hubSpotForm ? (
        <Popup showPopup={showPopup} onClosePopup={onClosePopup}>
          {contentfulModuleToComponent({
            ...hubSpotForm,
          })}
        </Popup>
      ) : null}
    </>
  )
}
Example #12
Source File: hero.js    From personal-site with MIT License 4 votes vote down vote up
export default function Hero() {
  const data = useStaticQuery(graphql`
    query {
      site {
        siteMetadata {
          author
          role
        }
      }
    }
  `)
  const { author, role } = data.site.siteMetadata
  return (
    <StyledHero id="home">
      <Header />
      <div className="container hero__container">
        <div className="container__hero--secondary container--secondary">
          <StyledContainer className="container--primary">
            <p className="colored" data-sal="slide-up" data-sal-easing="ease">
              Hi, I am
            </p>
            <h1 data-sal="slide-up" data-sal-delay="100" data-sal-easing="ease">
              {author}
            </h1>
            <h2
              data-sal="slide-up"
              data-sal-delay="200"
              data-sal-easing="ease"
              className="hero__sub"
            >
              {role}
            </h2>
            <p
              data-sal="slide-up"
              data-sal-delay="300"
              data-sal-easing="ease"
              className="hero__desc section__desc"
            >
              I specialize in designing, building, shipping, and scaling
              beautiful, usable products with blazing-fast efficiency.
            </p>
            <div
              data-sal="slide-up"
              data-sal-delay="400"
              data-sal-easing="ease"
            >
              <a
                href="#contact"
                className="btn btn--primary"
                onClick={e => {
                  e.preventDefault()
                  trackCustomEvent({
                    category: "Get in touch Button",
                    action: "Click",
                    label: "Gatsby Google Analytics Get in touch Button",
                  })
                }}
              >
                Get in touch
              </a>
            </div>
            <img
              className="decoration decoration__cross"
              src={cross}
              alt="cross"
              loading="lazy"
            />
            <img
              className="decoration decoration__tick"
              src={tick}
              alt="tick"
              loading="lazy"
            />
            <img
              className="decoration decoration__circle"
              src={circle}
              alt="circle"
              loading="lazy"
            />
          </StyledContainer>
        </div>
        <Board />
      </div>
    </StyledHero>
  )
}
Example #13
Source File: highRiskMap.js    From warsinhk with MIT License 4 votes vote down vote up
render() {
    const { useHorizontalLayout } = this.state
    const {
      height,
      width,
      theme,
      fullscreenEnabled,
      toggleFullScreen,
    } = this.props
    return (
      <div
        style={{
          position: "relative",
          height,
          width,
        }}
      >
        <LeafletStyleOverride />
        <div
          style={{
            position: "absolute",
            top: 0,
            left: useHorizontalLayout ? 480 : 0,
            bottom: useHorizontalLayout ? 0 : height / 2,
            right: 0,
            zIndex: 0,
          }}
        >
          <div
            ref={el => (this.mapContainer = el)}
            style={{ width: "100%", height: "100%" }}
          />

          {this.state.showLegend && (
            <div
              style={{
                position: "absolute",
                background: "rgba(255,255,255,0.9)",
                bottom: "64px",
                right: 0,
                zIndex: 500,
                pointerEvents: "none",
              }}
            >
              {this.state.legend}
            </div>
          )}
          <div
            style={{
              position: "absolute",
              bottom: "16px",
              right: "48px",
              zIndex: 501,
            }}
          >
            <IconButton
              color={this.state.showLegend ? "secondary" : "primary"}
              onClick={() => {
                trackCustomEvent({
                  category: "high_risk_map",
                  action: "toggle_legend",
                  label: this.state.showLegend ? "enable" : "disable",
                })
                this.setState({ showLegend: !this.state.showLegend })
              }}
            >
              <NotListedLocationIcon />
            </IconButton>
          </div>
          <div
            style={{
              position: "absolute",
              bottom: "16px",
              right: 0,
              zIndex: 550,
            }}
          >
            <IconButton
              color="primary"
              onClick={() => {
                trackCustomEvent({
                  category: "high_risk_map",
                  action: "toggle_fullscreen",
                  label: fullscreenEnabled ? "enable" : "disable",
                })
                toggleFullScreen()
              }}
            >
              {fullscreenEnabled ? <FullscreenExitIcon /> : <FullscreenIcon />}
            </IconButton>
          </div>
        </div>
        <div
          style={{
            position: "absolute",
            top: useHorizontalLayout ? 56 : height / 2,
            left: 0,
            width: useHorizontalLayout ? 480 : width,
            height: useHorizontalLayout ? height - 56 : height / 2,
            backgroundColor: theme.palette.background.paper,
          }}
        >
          <AutoSizer>
            {({ width, height }) => (
              <List
                ref={el => (this.list = el)}
                height={height}
                overscanRowCount={8}
                rowCount={this.props.filteredLocations.length}
                rowHeight={this.cache.rowHeight}
                rowRenderer={this.rowRenderer}
                deferredMeasurementCache={this.cache}
                width={width}
                scrollToIndex={this.state.scrollToIndex || 0}
                scrollToAlignment="start"
                activeDataPoint={this.state.activeDataPoint}
              />
            )}
          </AutoSizer>
        </div>

        <div
          style={
            !useHorizontalLayout
              ? {
                  position: "absolute",
                  top: theme.spacing(1),
                  left: theme.spacing(2),
                  right: theme.spacing(2),
                  opacity: 0.96,
                }
              : {
                  position: "absolute",
                  top: 0,
                  left: 0,
                  width: 480,
                  height: 56,
                  paddingTop: theme.spacing(2),
                  paddingBottom: theme.spacing(1),
                  paddingLeft: "20px",
                  paddingRight: "20px",
                  backgroundColor: theme.palette.background.paper,
                }
          }
        >
          <div
            style={{
              display: "flex",
              flexDirection: "row",
              alignItems: "flex-start",
            }}
          >
            <div style={{ flex: 1 }}>{this.props.selectBar}</div>
            <DateButton
              color={this.props.dateFilterEnabled ? "secondary" : "primary"}
              onClick={() => {
                trackCustomEvent({
                  category: "high_risk_map",
                  action: "click_date_filter",
                  label: this.props.dateFilterEnabled ? "enable" : "disable",
                })
                this.setState({ showDatePicker: !this.state.showDatePicker })
              }}
            >
              <DateRangeIcon />
            </DateButton>
          </div>
          {this.state.showDatePicker && this.props.datePicker}
        </div>
      </div>
    )
  }
Example #14
Source File: MultiPurposeSearch.js    From warsinhk with MIT License 4 votes vote down vote up
MultiPurposeSearch = props => {
  const {
    options,
    list,
    placeholder,
    onListFiltered,
    searchKey,
    filterWithOr = true,
  } = props

  const [filters, setFilters] = useState([])
  const [histories, setHistories] = useState([])
  const sortedOptions = options.map(opt => ({
    ...opt,
    options: sortOptionsWithHistories(opt.options, histories),
  }))
  const { t, i18n } = useTranslation()
  useEffect(() => {
    const v = loadFromLocalStorage(searchKey)
    if (v) {
      setHistories(JSON.parse(v))
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const customStyles = {
    control: () => ({
      display: "flex",
      backgroundColor: "#FFF",
      border: "1px solid hsl(0, 0%, 80%)",
      borderRadius: "6px",
      padding: "2px",
    }),
    placeholder: () => ({
      fontSize: "14px",
    }),
    menu: () => ({
      backgroundColor: "#FFF",
      zIndex: 999,
    }),
    multiValue: styles => ({
      ...styles,
      background: "#505096",
      color: "#ffffff",
      padding: "3px 3px 3px 10px",
      borderRadius: "16px",
    }),
    multiValueLabel: () => ({
      fontSize: "14px",
      fontWeight: 700,
    }),
    groupHeading: styles => ({
      ...styles,
      fontVariant: "normal",
      fontSize: "14px",
    }),
  }

  return (
    <AsyncSelect
      styles={customStyles}
      closeMenuOnSelect={false}
      loadOptions={(input, callback) =>
        callback(filterSearchOptions(sortedOptions, input, 5))
      }
      isMulti
      placeholder={placeholder}
      noOptionsMessage={() => t("text.not_found")}
      defaultOptions={filterSearchOptions(sortedOptions, null, 10)}
      onChange={selectedArray => {
        trackCustomEvent({
          category: searchKey,
          action: "search_input",
          label: selectedArray ? selectedArray.join(",") : "",
        })
        if (selectedArray && selectedArray.length > (filters || []).length) {
          let historiesToSave = [
            ...histories,
            selectedArray[selectedArray.length - 1],
          ]
          if (historiesToSave.length >= 10) {
            historiesToSave.shift()
          }
          setHistories(historiesToSave)
          saveToLocalStorage(searchKey, JSON.stringify(historiesToSave))
          onListFiltered(filterValues(i18n, list, selectedArray, filterWithOr))
        } else if (selectedArray && selectedArray.length > 0) {
          onListFiltered(filterValues(i18n, list, selectedArray, filterWithOr))
        } else {
          // return whole list if input is empty
          onListFiltered(list)
        }
        setFilters(selectedArray)
      }}
    />
  )
}
Example #15
Source File: ShareButton.js    From warsinhk with MIT License 4 votes vote down vote up
function ShareButton(props) {
  const [anchorEl, setAnchorEl] = React.useState(null)
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            siteUrl
          }
        }
      }
    `
  )

  function getPageUrl() {
    let url = `${site.siteMetadata.siteUrl}${fullPath}`

    if (props.caseId) {
      url = `${site.siteMetadata.siteUrl}${fullPath}/${props.caseId}`
    }

    if (!isSSR()) {
      url = url + decodeURIComponent(window.location.hash)
    }

    return url
  }

  function handleShareButtonClick(event) {
    if (!isSSR() && isWebShareAPISupported()) {
      const data = getWebShareData(getPageUrl())
      if (navigator.canShare(data)) {
        navigator.share(data).then(() => {
          trackCustomEvent({
            category: "general",
            action: "click",
            label: "share",
          })
        })

        return
      }
    }

    setAnchorEl(event.currentTarget)
  }

  function handleShareButtonClose(media) {
    setAnchorEl(null)
    if (typeof media === "string") {
      trackCustomEvent({
        category: "general",
        action: "click",
        label: `share_${media}`,
      })
    }
  }
  const { pathname: fullPath } = useLocation()

  const url = getPageUrl()

  return (
    <>
      <StyledIconButton
        color="inherit"
        aria-label="Share"
        aria-controls="share-menu"
        aria-haspopup="true"
        onClick={handleShareButtonClick}
      >
        <ShareIcon />
      </StyledIconButton>
      <Menu
        id="share-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleShareButtonClose}
      >
        <MenuItem onClick={() => handleShareButtonClose("facebook")}>
          <FacebookShareButton
            url={getShareUrl(url, "facebook")}
            children={<FacebookIcon size={32} round={true} />}
          />
        </MenuItem>
        <MenuItem onClick={() => handleShareButtonClose("telegram")}>
          <TelegramShareButton
            url={getShareUrl(url, "telegram")}
            children={<TelegramIcon size={32} round={true} />}
          />
        </MenuItem>
        <MenuItem onClick={() => handleShareButtonClose("whatsapp")}>
          <WhatsappShareButton
            url={getShareUrl(url, "whatsapp")}
            children={<WhatsappIcon size={32} round={true} />}
          />
        </MenuItem>
        <MenuItem onClick={() => handleShareButtonClose("twitter")}>
          <TwitterShareButton
            url={getShareUrl(url, "twitter")}
            children={<TwitterIcon size={32} round={true} />}
          />
        </MenuItem>
        <MenuItem onClick={() => handleShareButtonClose("link")}>
          <StyledCopyIcon
            onClick={() => {
              navigator.clipboard.writeText(url)
            }}
          />
        </MenuItem>
      </Menu>
    </>
  )
}
Example #16
Source File: about-us.js    From warsinhk with MIT License 4 votes vote down vote up
AboutUsPage = props => {
  const { data } = props
  const { t } = useTranslation()

  const contributors = React.useMemo(
    () =>
      []
        .concat(data.configJson.credits.contributors)
        .sort((a, b) => (a > b ? 1 : -1)),
    [data.configJson.credits]
  )

  const volunteers = React.useMemo(
    () =>
      []
        .concat(data.configJson.credits.volunteers)
        .sort((a, b) => (a.name > b.name ? 1 : -1)),
    [data.configJson.credits.volunteers]
  )

  const designers = React.useMemo(
    () =>
      []
        .concat(data.configJson.credits.designers)
        .sort((a, b) => (a.name > b.name ? 1 : -1)),
    [data.configJson.credits.designers]
  )

  return (
    <Layout>
      <SEO title="AboutUsPage" />
      <SplitWrapper>
        <SessionWrapper>
          <Typography variant="h2" style={{ marginBottom: 16 }}>
            {t("about_us.title")}
          </Typography>
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_1") }}
          />
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_2") }}
          />
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_3") }}
          />
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.donation") }}
          />
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.who_are_we_4") }}
          />
          <Button
            style={{ marginTop: 8 }}
            variant="outlined"
            color="primary"
            size="small"
            startIcon={<FaFacebookF size="0.8rem" />}
            href="https://www.facebook.com/vote4hongkong/"
            target="_blank"
            rel="noopener noreferer"
            onClick={() => {
              trackCustomEvent({
                category: "about_us",
                action: "click",
                label: "https://www.facebook.com/vote4hongkong/",
              })
            }}
          >
            {t("about_us.vote4hk_fb")}
          </Button>
          <Grid container spacing={2} style={{ marginTop: 16 }}>
            <Grid item md={6}>
              <Typography variant="h3" style={{ marginBottom: 8 }}>
                {t("about_us.g0vhk_title")}
              </Typography>
              <Paragraph
                dangerouslySetInnerHTML={{ __html: t("about_us.g0vhk_1") }}
              />
              {/* <Button
                component="button"
                variant="outlined"
                color="primary"
                size="small"
                startIcon={mapIcon("attach_money")}
                href="https://www.collaction.hk/s/g0vhk/fund"
                target="_blank"
                rel="noopener noreferer"
                onClick={() => {
                  trackCustomEvent({
                    category: "about_us",
                    action: "click",
                    label: "https://www.collaction.hk/s/g0vhk/fund",
                  })
                }}
              >
                {t("about_us.donate_g0vhk")}
              </Button> */}
            </Grid>
            <Grid item md={6}>
              <Typography variant="h3" style={{ marginBottom: 8 }}>
                {t("about_us.sooc_title")}
              </Typography>
              <Paragraph
                dangerouslySetInnerHTML={{ __html: t("about_us.sooc_1") }}
              />
              {/* <Button
                variant="outlined"
                color="primary"
                size="small"
                startIcon={mapIcon("attach_money")}
                href="https://www.collaction.hk/s/station/fund"
                target="_blank"
                rel="noopener noreferer"
                onClick={() => {
                  trackCustomEvent({
                    category: "about_us",
                    action: "click",
                    label: "https://www.collaction.hk/s/station/fund",
                  })
                }}
              >
                {t("about_us.donate_sooc")}
              </Button> */}
            </Grid>

            <Grid item xs={12}>
              <Typography variant="h3" style={{ marginBottom: 8 }}>
                {t("about_us.contact_title")}
              </Typography>
              <Paragraph
                dangerouslySetInnerHTML={{
                  __html: t("about_us.contact_method"),
                }}
              />
            </Grid>
          </Grid>
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.open_source") }}
            style={{ marginTop: 16 }}
          />
          <Paragraph
            dangerouslySetInnerHTML={{ __html: t("about_us.citation") }}
            style={{ marginTop: 16 }}
          />
          <LinkBox>
            <Button
              variant="outlined"
              color="primary"
              size="small"
              startIcon={<FaGithubAlt />}
              href="https://github.com/nandiheath/warsinhk"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "https://github.com/nandiheath/warsinhk",
                })
              }}
            >
              {t("about_us.github")}
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vT6aoKk3iHmotqb5_iHggKc_3uAA901xVzwsllmNoOpGgRZ8VAA3TSxK6XreKzg_AUQXIkVX5rqb0Mo/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "high_risk_source_data",
                })
              }}
            >
              {t("about_us.high_risk")} 2020
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vQVRg6iiYOHZwLsXdZE6TVWBO7Cldi07NUnbeVY3nI97_IjyG3jiWnjaUS51HRNJI1fN3io1paMa6jZ/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "high_risk_source_data",
                })
              }}
            >
              {t("about_us.high_risk")} 2021
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vTl_YWJy_osrNeOD0ufyQH4CuWTKCX9ng-tUPpIFXsAdk_ry2uciIt752f9a-yd83IGUtsw2rHQNB0s/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "high_risk_source_data",
                })
              }}
            >
              {t("about_us.high_risk")} 2022
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vSr2xYotDgnAq6bqm5Nkjq9voHBKzKNWH2zvTRx5LU0jnpccWykvEF8iB_0g7Tzo2pwzkTuM3ETlr_h/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "wars_cases_source_data",
                })
              }}
            >
              {t("about_us.wars_cases")} 2020
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vT-Xw-QHYydz_kJCJLBqTKGbb2OF8_gisdUsduPbdR6Dp3tLbWxy_mkfRx2tMmGJ0q64uNsLLv3bbfb/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "wars_cases_source_data",
                })
              }}
            >
              {t("about_us.wars_cases")} 2021
            </Button>
            <Button
              variant="outlined"
              color="secondary"
              size="small"
              startIcon={mapIcon("insert_drive_file")}
              href="https://docs.google.com/spreadsheets/d/e/2PACX-1vTEJyeLTOgntmUjeRhyEB1w_eFD6BUKAEgkR47pp3yXY_XB3IlF7DstsAA0pHz33h2pzGIxGbvGhjMe/pub?gid=0&range=A2:ZZ&output=csv"
              target="_blank"
              rel="noopener noreferer"
              onClick={() => {
                trackCustomEvent({
                  category: "about_us",
                  action: "click",
                  label: "wars_cases_source_data",
                })
              }}
            >
              {t("about_us.wars_cases")} 2022
            </Button>
          </LinkBox>
        </SessionWrapper>
        <SessionWrapper>
          <Typography variant="h2">{t("about_us.volunteers")}</Typography>
          <Grid container spacing={1} style={{ marginTop: 8 }}>
            {volunteers.map(item => (
              <Grid item key={item.id} xs={6} md={4}>
                <Volunteer
                  item={item}
                  siteUrl={data.site.siteMetadata.siteUrl}
                />
              </Grid>
            ))}
          </Grid>
          <Typography variant="h2" style={{ marginTop: 16 }}>
            {t("about_us.designers")}
          </Typography>
          <Grid container spacing={1} style={{ marginTop: 8 }}>
            {designers.map(item => (
              <Grid item key={item.id} xs={6} md={4}>
                <Volunteer
                  item={item}
                  siteUrl={data.site.siteMetadata.siteUrl}
                />
              </Grid>
            ))}
          </Grid>
          <Typography variant="h2" style={{ marginTop: 16 }}>
            {t("about_us.contributors")}
          </Typography>
          <Grid container spacing={1} style={{ marginTop: 8 }}>
            {contributors.map(item => (
              <Grid item xs={6} md={4} key={item}>
                <Contributor githubId={item} />
              </Grid>
            ))}
          </Grid>
        </SessionWrapper>
      </SplitWrapper>
    </Layout>
  )
}
Example #17
Source File: cases.js    From warsinhk with MIT License 4 votes vote down vote up
CasesPageMain = ({ dispatch, view }) => {
  const data = useAllCasesData()
  const { i18n, t } = useTranslation()
  const patientTrackKeyedByCaseNo = useMemo(
    () => _keyBy(data.patient_track.group, "fieldValue"),
    [data]
  )
  // Do the sorting here since case_no is string instead of int
  const [cases, groupArrayColumnOptions] = useMemo(() => {
    const groupArray = data.allWarsCaseRelation.edges.flatMap(
      ({ node }, index) =>
        node.case_no.split`,`.map(nodeCase => ({
          ...node,
          id: index + 1,
          related_cases: node.case_no,
          case_no: +nodeCase,
        }))
    )
    const groupArrayByCaseNo = _groupBy(groupArray, "case_no")
    const groupArrayColumnOptions = data.allWarsCaseRelation.edges.map(
      ({ node }, index) => ({
        value: index + 1,
        label: node[`name_${i18n.language}`],
      })
    )
    const cases = data.allWarsCase.edges
      .map(i => ({
        node: {
          ...i.node,
          case_no_num: +i.node.case_no,
          age_num: +i.node.age,
          groups: groupArrayByCaseNo[i.node.case_no] || [],
          group_ids: (groupArrayByCaseNo[i.node.case_no] || []).map(i => i.id),
        },
      }))
      .sort((edge1, edge2) => {
        const res = edge2.node.confirmation_date.localeCompare(
          edge1.node.confirmation_date
        )
        if (res === 0) {
          return parseInt(edge2.node.case_no) - parseInt(edge1.node.case_no)
        }
        return res
      })
    return [cases, groupArrayColumnOptions]
  }, [data, i18n.language])
  const [internalCount, setInternalCounter] = useState(0)
  const [filteredCases, setFilteredCases] = useState([])
  const [selectedCase, setSelectedCase] = useState(null)
  // 1: by date   : from latest to oldest
  // 2: by date   : from oldest to latest
  // 3: by area   : from greatest to least
  // 4: by area   : from least to greatest
  // 5: by group  : from more to less
  // 6: by group  : from less to more
  // 7: by status

  const [selectedGroupButton, setGroupButton] = useState(1)
  const { pathname } = useLocation()
  const caseCodeMatch = pathname.match(/cases\/([^/]+)/)
  const toFilterEntry = ([key, value]) => [`node.${key}`, value]
  const parseToFilter = str => {
    if (/^[-A-Z0-9]+\.\.+[-A-Z0-9]+$/i.test(str))
      return { between: str.split(/\.\.+/) }
    if (/^[><]=[-A-Z0-9]+$/i.test(str))
      return { [str[0] === ">" ? "gte" : "lte"]: str.slice(2, str.length) }
    if (/^[><][-A-Z0-9]+$/i.test(str))
      return { [str[0] === ">" ? "gt" : "lt"]: str.slice(1, str.length) }
    if (/^[-A-Z0-9]+$/i.test(str)) return str
    return
  }
  const dateRangeOptionPresets = [
    {
      label: t("cases.filters_last_n_days", { n: 7 }),
      value: `${moment()
        .subtract(6, "day")
        .format("YYYY-MM-DD")}..${moment().format(`YYYY-MM-DD`)}`,
    },
    {
      label: t("cases.filters_previous_n_days", { n: 7 }),
      value: `${moment()
        .subtract(13, "day")
        .format("YYYY-MM-DD")}..${moment()
        .subtract(7, "day")
        .format(`YYYY-MM-DD`)}`,
    },
    {
      label: t("cases.filters_last_n_days", { n: 14 }),
      value: `${moment()
        .subtract(13, "day")
        .format("YYYY-MM-DD")}..${moment().format(`YYYY-MM-DD`)}`,
    },
    {
      label: t("cases.filters_previous_n_days", { n: 14 }),
      value: `${moment()
        .subtract(27, "day")
        .format("YYYY-MM-DD")}..${moment()
        .subtract(14, "day")
        .format(`YYYY-MM-DD`)}`,
    },
    {
      label: t("cases.filters_this_month"),
      value: `${moment().format(`[>]YYYY-MM`)}`,
    },
    {
      label: t("cases.filters_previous_month"),
      value: `${moment()
        .subtract(1, "month")
        .format("YYYY-MM")}..${moment().format(`YYYY-MM`)}`,
    },
  ]
  const options = useMemo(() => {
    const stringOrFilterEntry = ([key, value]) => {
      const filterPhrases = value
        .split(/,|\s+/g)
        .filter(phase => phase && /\w$/.test(phase))
        .map(parseToFilter)
        .reduce(
          (acc, curr) => {
            if (curr) {
              curr.constructor === String
                ? acc.inq.push(curr)
                : acc.or.push(curr)
            }
            return acc
          },
          { or: [], inq: [] }
        )

      return [
        filterPhrases.inq.length
          ? { [`node.${key}`]: { inq: filterPhrases.inq } }
          : undefined,
        ...filterPhrases.or.map(phrase => ({ [`node.${key}`]: phrase })),
      ].filter(Boolean)
    }
    return [
      {
        label: t("search.group"),
        options: groupArrayColumnOptions,
        orderOptionsByFilterCount: true,
        realFieldName: "group_ids",
        toFilterEntry,
      },
      {
        label: t("search.classification"),
        options: createDedupOptions(i18n, cases, "classification"),
        orderOptionsByFilterCount: true,
        realFieldName: "classification_" + i18n.language,
        toFilterEntry,
      },
      {
        label: t("search.district"),
        options: createDedupOptions(i18n, cases, "citizenship_district"),
        orderOptionsByFilterCount: true,
        realFieldName: "citizenship_district_" + i18n.language,
        toFilterEntry,
      },
      {
        label: t("search.citizenship"),
        options: createDedupOptions(i18n, cases, "citizenship"),
        orderOptionsByFilterCount: true,
        realFieldName: "citizenship_" + i18n.language,
        toFilterEntry,
      },
      {
        label: t("search.case_status"),
        options: createDedupOptions(i18n, cases, "status"),
        orderOptionsByFilterCount: true,
        realFieldName: "status_" + i18n.language,
        toFilterEntry,
      },
      {
        label: t("search.hospital"),
        options: createDedupOptions(i18n, cases, "hospital"),
        orderOptionsByFilterCount: true,
        realFieldName: "hospital_" + i18n.language,
        toFilterEntry,
      },
      {
        label: t("search.case_no"),
        realFieldName: "case_no_num",
        filterType: "string",
        options: [],
        toFilterEntry: stringOrFilterEntry,
        isOrFilter: true,
        filterPlaceholder: "e.g. 1,3,10..20",
      },
      {
        label: t("dashboard.patient_confirm_date"),
        realFieldName: "confirmation_date",
        filterType: "string",
        options: dateRangeOptionPresets,
        toFilterEntry: stringOrFilterEntry,
        isOrFilter: true,
        filterPlaceholder: "e.g. 2020-06..2020-07-21",
      },
      {
        label: t("dashboard.patient_onset_date"),
        realFieldName: "onset_date",
        options: [
          { label: t("cases.status_asymptomatic"), value: "asymptomatic,none" },
          ...dateRangeOptionPresets,
        ],
        filterType: "string",
        toFilterEntry: stringOrFilterEntry,
        isOrFilter: true,
        filterPlaceholder: "e.g. 2020-06..2020-07-21",
      },
      {
        label: t("cases_visual.age"),
        realFieldName: "age_num",
        filterType: "string",
        options: [],
        toFilterEntry: stringOrFilterEntry,
        isOrFilter: true,
        filterPlaceholder: "e.g. 10..20,>60,>=50",
      },
      {
        label: t("cases_visual.gender"),
        realFieldName: "gender",
        options: [
          {
            value: "M",
            label: t("dashboard.gender_M"),
          },
          {
            value: "F",
            label: t("dashboard.gender_F"),
          },
        ],
        toFilterEntry,
      },
    ]
  }, [cases, dateRangeOptionPresets, groupArrayColumnOptions, i18n, t])
  const onListFiltered = useCallback(data => {
    if (data !== filteredCases) {
      setFilteredCases(data)
      setInternalCounter(i => i + 1)
    }
  }, [filteredCases])
  // Calculate how much cards we should preload in order to scorll to that position
  let preloadedCases = cases.length - parseInt(selectedCase) + 1
  if (isNaN(preloadedCases)) {
    preloadedCases = 15
  }
  const renderCaseCard = useMemo(() => {
    const RenderSingleCaseCard = (node, i) => (
      <WarsCaseCard
        node={node}
        i18n={i18n}
        t={t}
        key={`${i}-${node.id}`}
        // isSelected={selectedCase === item.node.case_no}
        // ref={selectedCase === item.node.case_no ? selectedCard : null}
        patientTrack={
          patientTrackKeyedByCaseNo[node.case_no]
            ? [patientTrackKeyedByCaseNo[node.case_no]]
            : null
        }
        handleClose={
          view === CASES_BOX_VIEW ? e => setSelectedCase(null) : undefined
        }
      />
    )
    return RenderSingleCaseCard
  }, [i18n, patientTrackKeyedByCaseNo, t, view])

  const Legend = () => {
    const items = [
      {
        icon: <MaleIcon />,
        text: t("dashboard.gender_M"),
      },
      {
        icon: <FemaleIcon />,
        text: t("dashboard.gender_F"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("discharged").main}
          />
        ),
        text: t("cases.status_discharged"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("pending_admission").main}
          />
        ),
        text: t("cases.status_pending_admission"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("stable").main}
          />
        ),
        text: t("cases.status_stable"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("hospitalised_again").main}
          />
        ),
        text: t("cases.status_hospitalised_again"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("serious").main}
          />
        ),
        text: t("cases.status_serious"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("critical").main}
          />
        ),
        text: t("cases.status_critical"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("deceased").main}
          />
        ),
        text: t("cases.status_deceased"),
      },
      {
        icon: (
          <Circle
            width={48}
            height={48}
            bgColor={mapColorForStatus("no_admission").main}
          />
        ),
        text: t("cases.status_no_admission"),
      },
      {
        icon: <ImportIcon />,
        text: t("cases.imported"),
      },
      {
        icon: <UnknownIcon />,
        text: t("cases.unknown"),
      },
    ]

    return (
      <Accordion
        style={{ marginBottom: 16 }}
        title={
          <LegendTitle>
            <QuestionIcon />
            <span>{t("cases.legend")}</span>
          </LegendTitle>
        }
        content={
          <LegendContent>
            {items.map((item, i) => (
              <div key={i} className="item">
                {item.icon}
                <span>{item.text}</span>
              </div>
            ))}
          </LegendContent>
        }
      />
    )
  }

  const toggleGroupingButtons = [
    "cases.toggle_date",
    "cases.toggle_date_reverse",
    "cases.toggle_area",
    "cases.toggle_area_reverse",
    "cases.toggle_group",
    "cases.toggle_group_reverse",
    "cases.toggle_status",
  ]

  const handleBoxClick = item => {
    setSelectedCase(item)

    trackCustomEvent({
      category: "cases",
      action: "click_avatar",
      label: item.case_no,
    })
  }
  const isCaseNumberMatch =
    caseCodeMatch && filteredCases.length === 1 && filteredCases[0]
  return (
    <Layout
      onClick={e =>
        typeof e.target.className === "string" &&
        !e.target.className.includes("wars_box") &&
        setSelectedCase(null)
      }
    >
      {isCaseNumberMatch ? (
        <SEO
          titleOveride={t("case.title")}
          // TODO: duplicated entries, filter out in SEO later?
          meta={[
            {
              property: `og:title`,
              content: `${t("index.title")} | ${t("case.case_no", {
                case_no: filteredCases[0].node.case_no,
              })}`,
            },
            {
              property: `og:description`,
              content: withLanguage(i18n, filteredCases[0].node, "detail"),
            },
          ]}
        />
      ) : (
        <SEO title="ConfirmedCasePage" />
      )}
      <TitleContainer>
        <Typography variant="h2">{t("cases.title")}</Typography>
        <span>
          <BoxViewIcon
            className={view === CASES_BOX_VIEW && "active"}
            onClick={() => {
              dispatch({
                type: CASES_BOX_VIEW,
              })
              trackCustomEvent({
                category: "cases",
                action: "toggle_view",
                label: "BOX_VIEW",
              })
            }}
          />
          <CardViewIcon
            className={view === CASES_CARD_VIEW && "active"}
            onClick={() => {
              dispatch({
                type: CASES_CARD_VIEW,
              })
              trackCustomEvent({
                category: "cases",
                action: "toggle_view",
                label: "CARD_VIEW",
              })
            }}
          />
        </span>
      </TitleContainer>
      <PageContent>
        <ConfirmedCasesSummary />
        {view === CASES_BOX_VIEW && <Legend />}
        <Typography variant="h5" style={{ marginTop: 16 }}>
          {t("cases.filters")}
        </Typography>
        <TagStyleFilter
          key={pathname}
          list={cases}
          placeholder={t("search.case_placeholder")}
          options={options}
          searchKey="case"
          onListFiltered={onListFiltered}
          filterWithOr={false}
          initialFilters={
            caseCodeMatch
              ? [
                  {
                    label: caseCodeMatch[1],
                    filterName: t("search.case_no"),
                    realFieldName: "case_no_num",
                    field: "case_no_num",
                    value: caseCodeMatch[1],
                  },
                ]
              : []
          }
        />
        {view === CASES_BOX_VIEW && (
          <DefaultSelect
            value={selectedGroupButton}
            onChange={event => {
              setGroupButton(event.target.value)
              trackCustomEvent({
                category: "cases",
                action: "set_grouping",
                label: toggleGroupingButtons[event.target.value - 1],
              })
            }}
            displayEmpty
            IconComponent={SortIcon}
          >
            {toggleGroupingButtons.map((groupBy, index) => (
              <MenuItem key={index} value={index + 1}>
                {t(groupBy)}
              </MenuItem>
            ))}
          </DefaultSelect>
        )}

        <Typography variant="h6" style={{ marginTop: 16 }}>
          {filteredCases.length > 1
            ? t("cases.filter_results_plural", { count: filteredCases.length })
            : t("cases.filter_results", { count: filteredCases.length })}
        </Typography>
      </PageContent>
      {view === CASES_BOX_VIEW ? (
        <>
          <WarsCaseBoxContainer
            filteredCases={filteredCases}
            handleBoxClick={handleBoxClick}
            selectedGroupButton={selectedGroupButton}
          />
          {selectedCase && (
            <SelectedCardContainer>
              {renderCaseCard(selectedCase)}
            </SelectedCardContainer>
          )}
        </>
      ) : (
        <InfiniteScroll
          key={internalCount} // The state of this InfiniteScroll Component need to be discarded when filteredCases changes.
          list={filteredCases.map(c => c.node)}
          step={{ mobile: 5, preload: preloadedCases }}
          onItem={renderCaseCard}
          Wrapper={ResponsiveWrapper}
        />
      )}
    </Layout>
  )
}
Example #18
Source File: index.js    From warsinhk with MIT License 4 votes vote down vote up
export default function IndexPage({ data }) {
  const { t } = useTranslation()

  const [modules, setModules] = React.useState([])
  const [showSettings, setShowSettings] = React.useState(false)

  // TODO: useMemo to cache the components
  const components = {}
  const registerComponent = (
    key,
    titleKey,
    component,
    { rowSpan = 1, showTitle = true } = {}
  ) => {
    components[key] = {
      id: key,
      title: t(titleKey),
      component,
      rowSpan,
      showTitle,
    }
  }

  const renderComponent = (key, data) => {
    if (components[key]) {
      const Component = components[key].component
      return (
        <ModuleContainer>
          {components[key].showTitle && (
            <Typography variant="h2">{components[key].title}</Typography>
          )}
          <Suspense fallback={<ComponentLoading />}>
            <Component data={data} />
          </Suspense>
        </ModuleContainer>
      )
    }
    return <></>
  }

  const registerComponents = () => {
    registerComponent(
      "daily_stat",
      "dashboard.latest_figures",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/DailyStats.js"
        )
      )
    )

    registerComponent(
      "important_information",
      "dashboard.important_information",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/ImportantInformation.js"
        )
      ),
      {
        showTitle: false,
      }
    )

    registerComponent(
      "confirmed_chart",
      "dashboard.case_highlights_area",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseVisual"
        )
      ),
      {
        rowSpan: 4,
      }
    )

    registerComponent(
      "confirmed_digest_gender",
      "dashboard.case_highlights_gender",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseDigestGender"
        )
      ),
      {
        rowSpan: 2,
      }
    )

    registerComponent(
      "passenger_daily",
      "dashboard.daily_passengers",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/PassengerDailyFigure"
        )
      )
    )

    registerComponent(
      "confirmed_digest_age",
      "dashboard.case_highlights_age",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedCaseDigestAge"
        )
      )
    )

    registerComponent(
      "outbound_alert",
      "dashboard.outbound_alert",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/OutboundAlert.js"
        )
      ),
      {
        rowSpan: 3,
        showTitle: false,
      }
    )

    registerComponent(
      "carousel",
      "dashboard.carousel",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/Carousel.js"
        )
      ),
      {
        showTitle: false,
      }
    )

    registerComponent(
      "friendly_links",
      "dashboard.friendly_links",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/FriendlyLinks.js"
        )
      ),
      {
        showTitle: false,
      }
    )

    registerComponent(
      "latest_cases",
      "dashboard.latest_cases",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/LatestCases.js"
        )
      ),
      {
        rowSpan: 6,
      }
    )

    registerComponent(
      "epidemic_chart",
      "dashboard.epidemic_chart",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/EpidemicChart.js"
        )
      ),
      {
        rowSpan: 4,
      }
    )

    registerComponent(
      "isolation_beds",
      "dashboard.isolation_beds",
      React.lazy(() =>
        import(
          /* webpackPrefetch: true */ "@/components/molecules/dashboard/ConfirmedLineChart.js"
        )
      ),
      {
        rowSpan: 2,
      }
    )
  }

  const handleModuleChange = id => {
    const index = modules.indexOf(id)
    if (index >= 0) {
      modules.splice(index, 1)
    } else {
      modules.push(id)
    }
    setModules([...modules])
    saveToLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD, [...modules])
    trackCustomEvent({
      category: "dashboard",
      action: "module_change",
      label: modules.join(","),
    })
  }

  // load the settings from localStorage
  const LOCAL_STORAGE_KEY_DASHBOARD = "index-dashboard-module"
  const LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES = "index-dashboard-module-new"
  registerComponents()

  React.useEffect(() => {
    const moduleString = loadFromLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD)
    const loadedNewModules = loadFromLocalStorage(
      LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES
    )
    let newModules = []
    if (loadedNewModules) {
      const alreadyLoadedNewModules = loadedNewModules.split(",")
      NEW_FEATURES.forEach(f => {
        if (alreadyLoadedNewModules.indexOf(f) < 0) {
          newModules.push(f)
        }
      })
    } else {
      newModules = NEW_FEATURES
    }

    saveToLocalStorage(LOCAL_STORAGE_KEY_DASHBOARD_NEW_MODULES, NEW_FEATURES)
    if (moduleString) {
      setModules(moduleString.split(",").concat(newModules))
    } else {
      // default modules
      setModules(DEFAULT_MODULES.concat(newModules))
    }
    // eslint-disable-line
  }, [])

  // store the information of which module on left/right (only for desktop)
  const columnMap = []
  let left = 0
  let right = 0
  for (let i = 0; i < modules.length; i++) {
    const m = components[modules[i]]
    if (right >= left) {
      left += _get(m, "rowSpan", 1)
      columnMap.push("left")
    } else {
      right += _get(m, "rowSpan", 1)
      columnMap.push("right")
    }
  }

  return (
    <>
      <SEO title="Home" />
      <Layout>
        <IndexContainer>
          {showSettings && (
            <ModuleContainer className="settingContainer">
              <FormControl component="fieldset">
                <FormLabel component="legend">
                  {t("dashboard.settings")}
                </FormLabel>
                {Object.values(components).map(component => (
                  <FormGroup>
                    <FormControlLabel
                      control={
                        <Checkbox
                          color="primary"
                          checked={modules.indexOf(component.id) >= 0}
                          onChange={() => handleModuleChange(component.id)}
                        />
                      }
                      label={component.title}
                    />
                  </FormGroup>
                ))}
              </FormControl>
            </ModuleContainer>
          )}
          <SplitWrapper>
            <SessionWrapper>
              <IndexAlertMessage />
              {modules
                .filter((_, i) => columnMap[i] === "left")
                .map((m, i) => (
                  <React.Fragment key={i}>
                    {renderComponent(m, data)}
                  </React.Fragment>
                ))}
            </SessionWrapper>
            <SessionWrapper>
              {modules
                .filter((_, i) => columnMap[i] === "right")
                .map((m, i) => (
                  <React.Fragment key={i}>
                    {renderComponent(m, data)}
                  </React.Fragment>
                ))}
            </SessionWrapper>
          </SplitWrapper>
          <Fab
            color="primary"
            className="fab"
            onClick={() => {
              if (!showSettings) {
                window.scrollTo(0, 0)
              }
              setShowSettings(!showSettings)
            }}
          >
            <SettingIcon />
          </Fab>
        </IndexContainer>
      </Layout>
    </>
  )
}
Example #19
Source File: updates.js    From warsinhk with MIT License 4 votes vote down vote up
NewsPage = props => {
  const { data } = props
  const { i18n, t } = useTranslation()

  const paragraphArray = data.allUpdates.edges
    .filter(e => e.node.type === "paragraph")
    .map(e => withLanguage(i18n, e.node, "text"))

  const listItemArray = data.allUpdates.edges
    .filter(e => e.node.type === "item")
    .map(e => withLanguage(i18n, e.node, "text"))

  const govNews = data.allGovNews.edges.map(edge => edge.node)

  const renderNotes = () => {
    return (
      <>
        <Typography variant="h2">{t("updates.title")}</Typography>
        <UpdatesContainer>
          <p
            dangerouslySetInnerHTML={{
              __html: t("importantInformation.chp_hotline"),
            }}
          />
          <p
            dangerouslySetInnerHTML={{
              __html: t("importantInformation.had_hotline"),
            }}
          />
          {paragraphArray.map((p, i) => (
            <p key={i} dangerouslySetInnerHTML={{ __html: p }} />
          ))}
          <ol type="i">
            {listItemArray.map((li, i) => (
              <li key={i} dangerouslySetInnerHTML={{ __html: li }} />
            ))}
          </ol>
        </UpdatesContainer>
      </>
    )
  }

  const renderGovNews = () => {
    return (
      <>
        <Typography variant="h2">{t("gov_news.title")}</Typography>
        <Typography variant="body2">
          <Link href={t("gov_news.url")} target="_blank">
            {t("gov_news.source")}
          </Link>
        </Typography>
        <Typography variant="body2">
          {t("waiting_time.last_updated")}
          {_get(govNews, "[0].date", "")}
        </Typography>
        <ResponsiveWrapper>
          {govNews.map((node, index) => (
            <GovNewsCard key={index} node={node} i18n={i18n} />
          ))}
        </ResponsiveWrapper>
      </>
    )
  }

  const tabs = [
    {
      name: "updates",
      title: t("updates.title"),
      content: renderNotes(),
    },
    {
      name: "gov_news",
      title: t("gov_news.title"),
      content: renderGovNews(),
    },
  ]

  return (
    <Layout>
      <SEO title="UpdatesPage" />
      <SimpleTabs
        tabs={tabs}
        onTabChange={name => {
          trackCustomEvent({
            category: "news",
            action: "tab_select",
            label: name,
          })
        }}
      />
    </Layout>
  )
}
Example #20
Source File: multi-code-block.js    From guitar-book with MIT License 4 votes vote down vote up
export function MultiCodeBlock(props) {
  const {codeBlocks, titles} = useMemo(() => {
    const defaultState = {
      codeBlocks: {},
      titles: {}
    };

    if (!Array.isArray(props.children)) {
      return defaultState;
    }

    return props.children.reduce((acc, child, index, array) => {
      const lang = getLang(child);
      if (lang) {
        return {
          ...acc,
          codeBlocks: {
            ...acc.codeBlocks,
            [lang]: child
          }
        };
      }

      if (child.props.className === 'gatsby-code-title') {
        const nextNode = array[index + 1];
        const title = child.props.children;
        const lang = getLang(nextNode);
        if (nextNode && title && lang) {
          return {
            ...acc,
            titles: {
              ...acc.titles,
              [lang]: title
            }
          };
        }
      }

      return acc;
    }, defaultState);
  }, [props.children]);

  const languages = useMemo(() => Object.keys(codeBlocks), [codeBlocks]);
  const [selectedLanguage, setSelectedLanguage] = useContext(
    SelectedLanguageContext
  );

  if (!languages.length) {
    return props.children;
  }

  function handleLanguageChange(language) {
    setSelectedLanguage(language);
    trackCustomEvent({
      category: GA_EVENT_CATEGORY_CODE_BLOCK,
      action: 'Change language',
      label: language
    });
  }

  const defaultLanguage = languages[0];
  const renderedLanguage =
    selectedLanguage in codeBlocks ? selectedLanguage : defaultLanguage;

  return (
    <Container>
      <MultiCodeBlockContext.Provider
        value={{
          selectedLanguage: renderedLanguage,
          languages: languages.map(lang => ({
            lang,
            label:
              // try to find a label or capitalize the provided lang
              langLabels[lang] || lang.charAt(0).toUpperCase() + lang.slice(1)
          })),
          onLanguageChange: handleLanguageChange
        }}
      >
        <div className="gatsby-code-title">{titles[renderedLanguage]}</div>
        {codeBlocks[renderedLanguage]}
      </MultiCodeBlockContext.Provider>
    </Container>
  );
}