pages/ValuePartnership/GlobePage/ValueCheckboxPanel.jsxjavascript
import { useState } from "react";
import PropTypes from "prop-types";
import { UIMCButton } from "@ui-marcom/react/button";
import { UIMCIcon } from "@ui-marcom/react/icon";
import { UIMCControlButton } from "@ui-marcom/react/control-button";
import { UIMCSearchField } from "@ui-marcom/react/search-field";
import { UIMCToggleSwitch } from "@ui-marcom/react/toggle-switch";
import { UIMCAccordion } from "@ui-marcom/react/accordion";
import { UIMCCollapsible } from "@ui-marcom/react/collapsible";
import { UIMCCheckbox } from "@ui-marcom/react/checkbox";
import { useAnalytics } from "../../../services/analytics/analytics";
import { useScreensaver } from "../../../services/screensaver";
import { MARKER_ICONS } from "../../../services/config/globeDefaults";
import "./ValueCheckboxPanel.scss";

/**
 * Slide-in filter panel for the globe's markers, ported from the old
 * value-checkbox-panel Lit component. Controlled component for filtering:
 * `filters` is owned by GlobePage (see its isPointVisible-equivalent
 * useMemo), this just renders it and reports changes via `onChange`. The
 * open/collapsed state of the drawer itself is local UI state - it doesn't
 * affect which markers are visible, so it has no reason to live in GlobePage.
 *
 * @param {Object} props
 * @param {Object} props.filters - `{ search, impactCasesActive, outcomeCasesActive, checkedImpactCases, checkedOutcomeCases, checkedStrategyCases }`.
 * @param {function(Object): void} props.onChange - Called with the next filters object.
 */
export const ValueCheckboxPanel = ({ filters, onChange }) => {
  const [isOpen, setIsOpen] = useState(true);
  const { trackEvent } = useAnalytics();
  const { showNow } = useScreensaver();
  const {
    search,
    impactCasesActive,
    outcomeCasesActive,
    checkedImpactCases,
    checkedOutcomeCases,
    checkedStrategyCases,
  } = filters;

  const toggleActive = (key) => onChange({ ...filters, [key]: !filters[key] });

  const toggleOutcomeGroup = (groupIndex) => {
    const allChecked = checkedOutcomeCases[groupIndex].items.every((item) => item.checked);
    const nextGroups = checkedOutcomeCases.map((group, idx) =>
      idx === groupIndex
        ? { ...group, items: group.items.map((item) => ({ ...item, checked: !allChecked })) }
        : group
    );
    onChange({ ...filters, checkedOutcomeCases: nextGroups });
  };

  const toggleOutcomeItem = (groupIndex, itemIndex) => {
    const nextGroups = checkedOutcomeCases.map((group, idx) =>
      idx === groupIndex
        ? {
            ...group,
            items: group.items.map((item, i) =>
              i === itemIndex ? { ...item, checked: !item.checked } : item
            ),
          }
        : group
    );
    onChange({ ...filters, checkedOutcomeCases: nextGroups });
  };

  const toggleOutcomeSelectAll = () => {
    const allChecked = checkedOutcomeCases.every((group) =>
      group.items.every((item) => item.checked)
    );
    const nextGroups = checkedOutcomeCases.map((group) => ({
      ...group,
      items: group.items.map((item) => ({ ...item, checked: !allChecked })),
    }));
    onChange({ ...filters, checkedOutcomeCases: nextGroups });
  };

  const toggleImpactItem = (index) => {
    const nextItems = checkedImpactCases.map((item, i) =>
      i === index ? { ...item, checked: !item.checked } : item
    );
    onChange({ ...filters, checkedImpactCases: nextItems });
  };

  const toggleStrategyItem = (index) => {
    const nextItems = checkedStrategyCases.map((item, i) =>
      i === index ? { ...item, checked: !item.checked } : item
    );
    onChange({ ...filters, checkedStrategyCases: nextItems });
  };

  const toggleStrategySelectAll = () => {
    const allChecked = checkedStrategyCases.every((item) => item.checked);
    onChange({
      ...filters,
      checkedStrategyCases: checkedStrategyCases.map((item) => ({ ...item, checked: !allChecked })),
    });
  };

  const toggleImpactSelectAll = () => {
    const allChecked = checkedImpactCases.every((item) => item.checked);
    onChange({
      ...filters,
      checkedImpactCases: checkedImpactCases.map((item) => ({ ...item, checked: !allChecked })),
    });
  };

  return (
    <div className={`value-checkbox-panel${isOpen ? "" : " value-checkbox-panel--collapsed"}`}>
      <UIMCControlButton
        className="value-checkbox-panel__toggle"
        icon={isOpen ? "arrow-left-large" : "arrow-right-large"}
        title={isOpen ? "Collapse filters" : "Expand filters"}
        onClick={() => setIsOpen((open) => !open)}
      />

      <div className="value-checkbox-panel__scroll">
        <h2>Browse our cases</h2>

        <UIMCSearchField
          className="value-checkbox-panel__search"
          placeholder="Open search"
          size="large"
          value={search}
          onInput={(e) => onChange({ ...filters, search: e.target.value })}
        />

        <div className="value-checkbox-panel__case-filter">
          <UIMCToggleSwitch
            isChecked={outcomeCasesActive}
            onChange={() => toggleActive("outcomeCasesActive")}
          />
          <span>Outcome Cases</span>
          <img
            className="value-checkbox-panel__case-filter-pin"
            src={MARKER_ICONS.outcome}
            alt=""
          />
        </div>
        <div className="value-checkbox-panel__case-filter">
          <UIMCToggleSwitch
            isChecked={impactCasesActive}
            onChange={() => toggleActive("impactCasesActive")}
          />
          <span>Impact Cases</span>
          <img className="value-checkbox-panel__case-filter-pin" src={MARKER_ICONS.impact} alt="" />
        </div>

        <UIMCAccordion independent>
          <UIMCCollapsible open={true} className="value-checkbox-panel__collapsible">
            <span slot="headline">Filter Outcome Cases by Strategy</span>
            <UIMCButton
              size="small"
              variant="standard"
              className="value-checkbox-panel__button"
              onClick={toggleStrategySelectAll}>
              {checkedStrategyCases.every((item) => item.checked) ? "Deselect all" : "Select all"}
            </UIMCButton>
            <div className="value-checkbox-panel__checkboxes">
              {checkedStrategyCases.map((item, index) => (
                <div key={item.name} className="value-checkbox-panel__group">
                  <UIMCCheckbox
                    className="value-checkbox-panel__checkbox-item"
                    size="medium"
                    key={item.value}
                    checked={item.checked}
                    onChange={() => toggleStrategyItem(index)}>
                    {item.name}
                  </UIMCCheckbox>
                </div>
              ))}
            </div>
          </UIMCCollapsible>
          <UIMCCollapsible open={outcomeCasesActive} className="value-checkbox-panel__collapsible">
            <span slot="headline">Filter Outcome Cases by Drivers</span>
            <UIMCButton
              size="small"
              variant="standard"
              className="value-checkbox-panel__button"
              onClick={toggleOutcomeSelectAll}>
              {checkedOutcomeCases.every((g) => g.items.every((i) => i.checked))
                ? "Deselect all"
                : "Select all"}
            </UIMCButton>
            <div className="value-checkbox-panel__checkboxes">
              {checkedOutcomeCases.map((group, groupIndex) => (
                <div key={group.name} className="value-checkbox-panel__group">
                  <UIMCCheckbox
                    className="value-checkbox-panel__checkbox-item"
                    checked={group.items.every((item) => item.checked)}
                    onChange={() => toggleOutcomeGroup(groupIndex)}>
                    {group.name}
                  </UIMCCheckbox>
                  <div className="value-checkbox-panel__subitems">
                    {group.items.map((item, itemIndex) => (
                      <UIMCCheckbox
                        className="value-checkbox-panel__checkbox-item"
                        key={item.value}
                        checked={item.checked}
                        onChange={() => toggleOutcomeItem(groupIndex, itemIndex)}>
                        {item.name}
                      </UIMCCheckbox>
                    ))}
                  </div>
                </div>
              ))}
            </div>
          </UIMCCollapsible>

          <UIMCCollapsible open={impactCasesActive} className="value-checkbox-panel__collapsible">
            <span slot="headline">Filter Impact Cases</span>
            <UIMCButton
              size="small"
              variant="standard"
              className="value-checkbox-panel__button"
              onClick={toggleImpactSelectAll}>
              {checkedImpactCases.every((i) => i.checked) ? "Deselect all" : "Select all"}
            </UIMCButton>
            <div className="value-checkbox-panel__checkboxes">
              {checkedImpactCases.map((item, index) => (
                <UIMCCheckbox
                  className="value-checkbox-panel__checkbox-item"
                  key={item.value}
                  checked={item.checked}
                  onChange={() => toggleImpactItem(index)}>
                  {item.name}
                </UIMCCheckbox>
              ))}
            </div>
          </UIMCCollapsible>
        </UIMCAccordion>
      </div>

      <div className="value-checkbox-panel__footer">
        <UIMCButton
          className="value-checkbox-panel__footer-button"
          variant="standard"
          title="Screensaver"
          onClick={() => {
            trackEvent?.("navigation_click", { element: "Screensaver" });
            showNow();
          }}>
          <UIMCIcon slot="prefix" icon="play" />
          Screensaver
        </UIMCButton>
      </div>
    </div>
  );
};

ValueCheckboxPanel.propTypes = {
  filters: PropTypes.shape({
    search: PropTypes.string.isRequired,
    impactCasesActive: PropTypes.bool.isRequired,
    outcomeCasesActive: PropTypes.bool.isRequired,
    checkedImpactCases: PropTypes.arrayOf(
      PropTypes.shape({
        name: PropTypes.string.isRequired,
        value: PropTypes.string.isRequired,
        checked: PropTypes.bool.isRequired,
      })
    ).isRequired,
    checkedOutcomeCases: PropTypes.arrayOf(
      PropTypes.shape({
        name: PropTypes.string.isRequired,
        items: PropTypes.arrayOf(
          PropTypes.shape({
            name: PropTypes.string.isRequired,
            value: PropTypes.string.isRequired,
            checked: PropTypes.bool.isRequired,
          })
        ).isRequired,
      })
    ).isRequired,
  }).isRequired,
  onChange: PropTypes.func.isRequired,
};