pages/ValuePartnership/GlobePage/GlobeDetailCard.jsxjavascript
import { Fragment, useState, useMemo, useRef, useEffect } from "react";
import PropTypes from "prop-types";
import { UIMCButton } from "@ui-marcom/react/button";
import { UIMCControlButton } from "@ui-marcom/react/control-button";
import { UIMCTabGroup } from "@ui-marcom/react/tab-group";
import { UIMCTab } from "@ui-marcom/react/tab";
import { UIMCTabPanel } from "@ui-marcom/react/tab-panel";
import { IMPACT_CATEGORIES, TAG_META } from "../../../services/config/globeDefaults";
import { resolveAssetPath } from "../../../utils/resolveAssetPath";
import "./GlobeDetailCard.scss";

const placeLine = (point) =>
  point.customer ? `${point.customer}, ${point.country}` : point.country;

// content-authored text fields (figure/keyfacts/partnership/outcomes/impacts)
// carry inline markup - footnote refs and ™ as <sup>, chemical formulas as
// <sub> - so they need dangerouslySetInnerHTML rather than plain JSX text,
// which would render the tags literally instead of interpreting them
const richText = (html) => ({ __html: html });

// keyfacts.challenges[]/value_adds[] items carry either an `icon` (a name
// like "Clinical-workflow", resolved against the shared icon set ported
// from value-globe.js) or a `number` ("1st", "83%") - never both. Shared
// between the two keyfacts columns below (renderOverview) so both render
// the same way; a plain function rather than a component since it's called
// inline inside a .map(), not used as JSX with its own props.
const renderFactIcon = (item) =>
  (item.icon || item.number) && (
    <div className="globe-detail-card__fact-icon-container">
      {item.icon && (
        <img
          className="globe-detail-card__fact-icon"
          src={resolveAssetPath(`/assets/icons/challenges/${item.icon}.svg`)}
          alt=""
        />
      )}
      {item.number && <p className="globe-detail-card__fact-number">{item.number}</p>}
    </div>
  );

/**
 * Detail overlay for a clicked globe marker, ported from value-globe.js's
 * card__big/card__details. Two views:
 * - "preview" (default): compact popup with a short blurb and a "Show
 *   details" button, shown right after clicking a marker.
 * - "details": the full card - a single "Overview" tab (headline/image/
 *   keyfacts/partnership/quote) for outcome points; one tab per impact
 *   category for impact points instead, no separate "Overview" tab there -
 *   each category tab renders its own outcomes/impacts, key facts & team
 *   setup, and customer key-challenges/goals sections, see
 *   value-globe.js:900-983 for the ported-from markup.
 *
 * @param {Object} props
 * @param {Object} props.point - The clicked location's full content entry.
 * @param {Object} props.labels - `content.valuePartnership.labels`.
 * @param {function(): void} props.onClose
 */
export const GlobeDetailCard = ({ point, labels, onClose }) => {
  const [view, setView] = useState("preview");
  const categoryTabs = useMemo(() => IMPACT_CATEGORIES.filter(([key]) => point[key]), [point]);
  // outcome points get a single "Overview" tab (headline/image/keyfacts/
  // partnership/quote); impact points get one tab per available category
  // instead, with no separate "Overview" tab of their own
  const tabs = point.type === "impact" ? categoryTabs : [["overview", "Overview", "checkmark"]];
  const [activeTabIndex, setActiveTabIndex] = useState(0);
  const activeCategory = tabs[activeTabIndex]?.[0];
  const activeData = activeCategory && activeCategory !== "overview" ? point[activeCategory] : null;

  // listen for the documented "uimc-change" event directly on the element
  // (rather than trusting the `onChange` prop to reach the Lit component
  // through @lit/react's property-vs-event heuristic). UIMCTabGroup only
  // renders once `view` is "details", so the effect must depend on `view`
  // too - an empty dep array would run once while the ref is still null
  // (during the "preview" view, before the tab group exists) and never
  // re-run once the ref is actually populated.
  const tabGroupRef = useRef(null);
  useEffect(() => {
    const el = tabGroupRef.current;
    if (!el) return undefined;
    const handleChange = (e) => setActiveTabIndex(e.detail.index);
    el.addEventListener("uimc-change", handleChange);
    return () => el.removeEventListener("uimc-change", handleChange);
  }, [view]);

  const tagMeta = TAG_META[point.type];
  // preview shows the short figure (e.g. "83%", "1st") ported from
  // value-globe.js's card__big - the long descriptive headline (point.headline
  // for outcome points, or the active category's headline for impact points,
  // see renderCategory below) is reserved for card__details/"details" view
  const previewHeadline = point.figure?.headline || "";
  const previewText = point.figure?.text || "";

  // a handful of points only have the preview teaser filled in (figure/
  // drivers) with the rest of the content still pending editorially - for
  // those, "details" view would render as an almost-empty overlay (no
  // image, no keyfacts, no partnership text), so skip offering the button
  // rather than leading to a dead end. Impact points have no dedicated
  // "overview" fields of their own - their content lives entirely in the
  // per-category tabs, so completeness there means at least one category tab.
  const hasDetailContent =
    point.type === "impact"
      ? categoryTabs.length > 0
      : Boolean(
          point.headline || point.image || point.keyfacts || point.partnership || point.quote?.text
        );

  if (view === "preview") {
    return (
      <div className="globe-detail-card globe-detail-card--preview">
        <div className="globe-detail-card-head-container">
          <div className="globe-detail-card globe-detail-card__head">
            {tagMeta && (
              <div className={`globe-detail-card__tag globe-detail-card__tag--${tagMeta.modifier}`}>
                {tagMeta.label}
              </div>
            )}
            <UIMCControlButton
              className="globe-detail-card__close"
              icon="close"
              title="Schließen"
              onClick={onClose}
            />
          </div>

          <h6 className="globe-detail-card__place globe-detail-card__place--preview">
            <ui-marcom-icon icon="hospital" className="globe-detail-card__place-icon" />
            {placeLine(point)}
          </h6>
        </div>

        {previewHeadline && (
          <div className="globe-detail-card__details-container">
            <h1 className="globe-detail-card__headline globe-detail-card__headline--preview">
              {previewHeadline}
            </h1>

            {previewText && (
              <p
                className="globe-detail-card__preview-text"
                dangerouslySetInnerHTML={richText(previewText)}
              />
            )}
          </div>
        )}
        <div className="globe-detail-card__footer">
          {hasDetailContent && (
            <UIMCButton
              className="globe-detail-card__show-details"
              variant="primary-cta"
              title={labels.showDetails}
              onClick={() => setView("details")}>
              <ui-marcom-icon slot="prefix" icon="arrow-right-medium"></ui-marcom-icon>
              {labels.showDetails}
            </UIMCButton>
          )}
        </div>
      </div>
    );
  }

  const renderOverview = () => (
    <>
      <div className="globe-detail-card__header">
        <div className="globe-detail-card__header-content">
          <h1
            className={`globe-detail-card__headline globe-detail-card__headline--${tagMeta?.modifier}`}>
            {point.headline}
          </h1>
          <h6 className="globe-detail-card__place">{placeLine(point)}</h6>
        </div>
        {point.image && <img className="globe-detail-card__image" src={point.image} alt="" />}
      </div>

      {point.keyfacts && (
        <div className="globe-detail-card__keyfacts">
          <h3 className="globe-detail-card__keyfacts-headline">{labels.keyFacts}</h3>
          {/* a single grid (not two independent flex columns) so each
              challenge/value-add pair lands in the same row and stretches to
              match height - the two arrays are paired 1:1 by index for
              nearly every point, but a few have one more entry on one side
              (see GlobeDetailCard.jsx history), hence Math.max + the
              per-side null checks below rather than a plain zip */}
          <div className="globe-detail-card__keyfacts-grid">
            <h4 className="globe-detail-card__keyfacts-heading">{labels.keyChallenges}</h4>
            <h4 className="globe-detail-card__keyfacts-heading globe-detail-card__keyfacts-heading--orange">
              {labels.keyValueAdds}
            </h4>
            {Array.from({
              length: Math.max(point.keyfacts.challenges.length, point.keyfacts.value_adds.length),
            }).map((_, index) => {
              const challenge = point.keyfacts.challenges[index];
              const valueAdd = point.keyfacts.value_adds[index];
              return (
                <Fragment key={index}>
                  {challenge ? (
                    <div className="globe-detail-card__fact-box globe-detail-card__fact-box--grey">
                      {renderFactIcon(challenge)}
                      <p dangerouslySetInnerHTML={richText(challenge.text)} />
                    </div>
                  ) : (
                    <div />
                  )}
                  {valueAdd ? (
                    <div className="globe-detail-card__fact-box globe-detail-card__fact-box--orange">
                      {renderFactIcon(valueAdd)}
                      <p dangerouslySetInnerHTML={richText(valueAdd.text)} />
                    </div>
                  ) : (
                    <div />
                  )}
                </Fragment>
              );
            })}
          </div>
        </div>
      )}

      {point.partnership && (
        <div className="globe-detail-card__partnership">
          <h3>{point.partnership.headline}</h3>
          <ul>
            {point.partnership.content.map((item) => (
              <li key={item} dangerouslySetInnerHTML={richText(item)} />
            ))}
          </ul>
        </div>
      )}

      {point.quote?.text && (
        <div className="globe-detail-card__quote">
          <h3>{labels.quote}</h3>
          <p>&ldquo;{point.quote.text}&rdquo;</p>
          <div className="globe-detail-card__quote-author-info">
            <span className="globe-detail-card__quote-author">{point.quote.author}</span>
            <span className="globe-detail-card__quote-info">
              {point.quote.info ? `${point.quote.info}` : ""}
            </span>
          </div>
        </div>
      )}
    </>
  );

  const renderCategory = () =>
    activeData && (
      <>
        <h1 className="globe-detail-card__headline globe-detail-card__headline--petrol">
          {activeData.headline}
        </h1>
        {/* paired grid, same reasoning/pattern as renderOverview's
            challenges/value-adds keyfacts-grid: two independent flex columns
            let each outcome/impact box size off only its own text, so
            corresponding rows didn't line up in height */}
        <div className="globe-detail-card__keyfacts-grid">
          <h4 className="globe-detail-card__keyfacts-heading">{labels.outcomes}</h4>
          <h4 className="globe-detail-card__keyfacts-heading globe-detail-card__keyfacts-heading--petrol">
            {labels.potentialImpacts}
          </h4>
          {Array.from({
            length: Math.max(activeData.outcomes?.length ?? 0, activeData.impacts?.length ?? 0),
          }).map((_, index) => {
            const outcome = activeData.outcomes?.[index];
            const impact = activeData.impacts?.[index];
            return (
              <Fragment key={index}>
                {outcome ? (
                  <div className="globe-detail-card__fact-box globe-detail-card__fact-box--grey">
                    {outcome.highlight && (
                      <>
                        <strong className="globe-detail-card__fact-number globe-detail-card__fact-number--petrol">
                          {outcome.highlight.headline}
                        </strong>
                        <span
                          className="globe-detail-card__fact-sub"
                          dangerouslySetInnerHTML={richText(outcome.highlight.subline)}
                        />
                      </>
                    )}
                    <p dangerouslySetInnerHTML={richText(outcome.text)} />
                  </div>
                ) : (
                  <div />
                )}
                {impact ? (
                  <div className="globe-detail-card__fact-box globe-detail-card__fact-box--petrol">
                    <p dangerouslySetInnerHTML={richText(impact.text)} />
                    {impact.badges?.map((badge) => (
                      <span key={badge} className="globe-detail-card__badge">
                        {badge.toUpperCase()}
                      </span>
                    ))}
                  </div>
                ) : (
                  <div />
                )}
              </Fragment>
            );
          })}
        </div>

        {activeData.keyfacts && (
          <div className="globe-detail-card__keyfacts">
            {/* one grey box for region/size/team - ported from value-globe.js's
                card__setup, which the old .keyfacts-col/.fact-box split (grey
                region+size boxes, separate petrol team box) didn't match */}
            <div className="globe-detail-card__setup">
              <h3 className="globe-detail-card__keyfacts-headline">{labels.keyFactsTeamSetup}</h3>
              <div className="globe-detail-card__setup-row">
                {activeData.keyfacts.region && (
                  <div className="globe-detail-card__setup-item">
                    <img
                      className="globe-detail-card__setup-icon"
                      src={resolveAssetPath("/assets/icons/icon-location.svg")}
                      alt=""
                    />
                    <div>
                      <p className="globe-detail-card__setup-label">{labels.region}</p>
                      <p className="globe-detail-card__setup-value globe-detail-card__setup-value--black-70">
                        {activeData.keyfacts.region}
                      </p>
                    </div>
                  </div>
                )}
                {activeData.keyfacts.size && (
                  <div className="globe-detail-card__setup-item">
                    <img
                      className="globe-detail-card__setup-icon"
                      src={resolveAssetPath("/assets/icons/hospital_grey-1.svg")}
                      alt=""
                    />
                    <div>
                      <p className="globe-detail-card__setup-value globe-detail-card__setup-value--black-70">
                        {activeData.keyfacts.size.headline}
                      </p>
                      <p className="globe-detail-card__setup-label">
                        {activeData.keyfacts.size.subline}
                      </p>
                    </div>
                  </div>
                )}
              </div>
              {activeData.keyfacts.team?.length > 0 && (
                <div className="globe-detail-card__setup-item">
                  <img
                    className="globe-detail-card__setup-icon"
                    src={resolveAssetPath("/assets/icons/icon-team.svg")}
                    alt=""
                  />
                  <div>
                    <p className="globe-detail-card__setup-value globe-detail-card__setup-value--petrol">
                      {labels.esTeam}
                    </p>
                    <div className="globe-detail-card__setup-team">
                      {activeData.keyfacts.team.map((member) => (
                        <div key={member.name}>
                          <p className="globe-detail-card__setup-label globe-detail-card__setup-value--petrol">
                            {member.name}
                          </p>
                          <p className="globe-detail-card__setup-label">{member.role}</p>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        )}

        {activeData.customer && (
          // reuses the same grey box as "Key facts & team setup" above,
          // same as value-globe.js's card__setup which both sections share
          <div className="globe-detail-card__setup">
            <h3>{activeData.customer.headline}</h3>
            {activeData.customer.description && (
              <p className="globe-detail-card__challenges-description">
                {activeData.customer.description}
              </p>
            )}
            <div className="globe-detail-card__challenges-row">
              {activeData.customer.challenges.headlines.map((item, index) => (
                <div
                  key={`${item.headline}-${index}`}
                  className="globe-detail-card__challenge-card globe-detail-card__challenge-card--icon">
                  {item.icon && (
                    <img
                      className="globe-detail-card__challenge-icon"
                      // item.icon is content-authored as a bare relative path
                      // ("assets/icons/sustainable.png", no leading slash) -
                      // resolveAssetPath needs the leading slash to
                      // recognize and rewrite it for sub-path deployments
                      src={resolveAssetPath(`/${item.icon}`)}
                      alt=""
                      onError={(e) => {
                        e.currentTarget.style.display = "none";
                      }}
                    />
                  )}
                  <p>{item.headline}</p>
                </div>
              ))}
            </div>
            <div className="globe-detail-card__challenges-row">
              {activeData.customer.challenges.challenges.map((text, index) => (
                <div key={`challenge-${index}`} className="globe-detail-card__challenge-card">
                  <h4>{labels.challenge}</h4>
                  <p className="globe-detail-card__challenge-card-text">{text}</p>
                </div>
              ))}
            </div>
            <div className="globe-detail-card__challenges-row">
              {activeData.customer.challenges.goals.map((text, index) => (
                <div key={`goal-${index}`} className="globe-detail-card__challenge-card">
                  <h4>{labels.goal}</h4>
                  <p className="globe-detail-card__challenge-card-text">{text}</p>
                </div>
              ))}
            </div>
          </div>
        )}
      </>
    );

  return (
    <div className="globe-detail-card-overlay">
      <UIMCControlButton
        className="globe-detail-card__close globe-detail-card__close--floating"
        icon="close"
        title="Schließen"
        onClick={onClose}
      />
      <div className="globe-detail-card-wrapper">
        <div className="globe-detail-card globe-detail-card--details">
          <div className="globe-detail-card__tags">
            {tagMeta && (
              <div className={`globe-detail-card__tag globe-detail-card__tag--${tagMeta.modifier}`}>
                {tagMeta.label}
              </div>
            )}
          </div>

          {tabs.length > 0 ? (
            <div className="globe-detail-card__tabs">
              <UIMCTabGroup
                ref={tabGroupRef}
                activeTabIndex={activeTabIndex}
                className="globe-detail-card__tab-group">
                {tabs.map(([key, label, icon], index) => (
                  <UIMCTab
                    key={key}
                    slot="nav"
                    size="medium"
                    for={key}
                    active={index === activeTabIndex}
                    className="globe-detail-card__tab">
                    <ui-marcom-icon
                      icon={icon}
                      className="globe-detail-card__tab-icon"></ui-marcom-icon>
                    {label}
                  </UIMCTab>
                ))}
                {tabs.map(([key]) => (
                  <UIMCTabPanel key={key} id={key} active={key === activeCategory}>
                    {key === activeCategory &&
                      (key === "overview" ? renderOverview() : renderCategory())}
                  </UIMCTabPanel>
                ))}
              </UIMCTabGroup>
            </div>
          ) : (
            renderOverview()
          )}

          <p className="globe-detail-card__disclaimer">{labels.uniqueDisclaimer}</p>
        </div>
      </div>
    </div>
  );
};

GlobeDetailCard.propTypes = {
  point: PropTypes.object.isRequired,
  labels: PropTypes.object.isRequired,
  onClose: PropTypes.func.isRequired,
};