Загрузка данных


import { PriceScaleMode } from 'lightweight-charts';

import { Button } from '@src/components/Button';
import { Dropdown } from '@src/components/Dropdown';
import { RangeField } from '@src/components/FormFields';
import { CameraIcon, LineIcon, TableIcon } from '@src/components/Icon';
import { AverageIcon } from '@src/components/Icon/AverageIcon';
import { SnapshotMenu } from '@src/components/Menu';
import { SeasonalityScaleMenu } from '@src/components/Menu/SeasonalityScaleMenu';
import { Tooltip } from '@src/components/Tooltip';

import { t } from '@src/translations';
import { SeasonalityViewType } from '@src/types';
import { useObservable } from '@src/utils';

import styles from './index.module.scss';

import type { SeasonalityRange, SeasonalityScale, SeasonalityState } from '@src/types/seasonality';
import type { Observable } from 'rxjs';

interface SeasonalityControlsProps {
  stateObs: Observable<SeasonalityState>;
  setDisplayMode: (displayMode: SeasonalityViewType) => void;
  setScale: (scale: SeasonalityScale) => void;
  setRange: (range: SeasonalityRange) => void;
  setAverageVisible: (visible: boolean) => void;
  copyImage?: () => Promise<void>;
}

const MAX_SCALE_LABELS = 5;

function createYearMarks(minYear: number, maxYear: number): number[] {
  const span = maxYear - minYear;

  if (span <= 0) return [minYear];

  const intervals = Math.min(MAX_SCALE_LABELS - 1, span);

  return Array.from(
    { length: intervals + 1 },
    (_, index) => minYear + Math.floor((span * index) / intervals),
  );
}

export function SeasonalityControls({
  stateObs,
  setDisplayMode,
  setScale,
  setRange,
  setAverageVisible,
  copyImage,
}: SeasonalityControlsProps) {
  const state = useObservable(stateObs);

  if (!state || state.status === 'idle') {
    return null;
  }

  const selectedScaleLabel = state.scale === PriceScaleMode.Normal ? t('Price') : t('Percent');

  const isRangeDisabled = state.status === 'loading' || state.status === 'error';
  const canCopyImage = state.status === 'ready' && state.displayMode === SeasonalityViewType.Chart;

  const marks = createYearMarks(state.minYear, state.maxYear);

  const handleRangeChange = ([fromYear, toYear]: [number, number]): void => {
    setRange({ fromYear, toYear });
  };

  return (
    <div className={styles.controls}>
      <div className={styles.controls_header}>
        <div className={styles.controls_views}>
          <Button
            label={<LineIcon />}
            pressed={state.displayMode === SeasonalityViewType.Chart}
            onClick={() => setDisplayMode(SeasonalityViewType.Chart)}
          />
          <Button
            label={<TableIcon />}
            pressed={state.displayMode === SeasonalityViewType.Table}
            onClick={() => setDisplayMode(SeasonalityViewType.Table)}
          />
        </div>

        <div className={styles.controls_actions}>
          <Button
            label={
              <span className={styles.controls_buttonContent}>
                <AverageIcon />
                <span>{t('Average')}</span>
              </span>
            }
            pressed={state.showAverage}
            disabled={state.status !== 'ready'}
            onClick={() => setAverageVisible(!state.showAverage)}
          />

          <div className={styles.controls_scale}>
            <Dropdown
              selectedValue={selectedScaleLabel}
              renderTrigger={({ isOpen, onToggle }) => (
                <Button
                  pressed={isOpen}
                  onClick={onToggle}
                  label={
                    <span className={styles.controls_buttonContent}>
                      <span>{selectedScaleLabel}</span>
                      <span
                        className={`${styles.controls_chevron} ${isOpen ? styles.controls_chevronOpen : ''}`}
                      />
                    </span>
                  }
                />
              )}
            >
              <SeasonalityScaleMenu selectedScale={state.scale} onClick={setScale} />
            </Dropdown>
          </div>

          {copyImage && canCopyImage && (
            <Tooltip location="bottom" label={t('Snapshot')}>
              <Dropdown selectedValue={<CameraIcon />}>
                <SnapshotMenu onCopyImage={copyImage} />
              </Dropdown>
            </Tooltip>
          )}
        </div>
      </div>

      <div className={styles.controls_range}>
        <RangeField
          mode="range"
          value={[state.fromYear, state.toYear]}
          min={state.minYear}
          max={state.maxYear}
          marks={marks}
          step={1}
          suffix=""
          size="sm"
          disabled={isRangeDisabled}
          onRangeChange={handleRangeChange}
        />
      </div>
    </div>
  );
}







copyImage={
  config.chartCollectionPreset.showSnapshotButton
    ? () => this.seasonalityManager.copyImage()
    : undefined
}










[data-theme='mxt'] {
  --cell-background-positive: #24a249;
  --cell-background-negative: #d71d38;
  --cell-text-positive: #24a249;
  --cell-text-negative: #d71d38;
}

[data-theme='mb'] {
  --cell-background-positive: #3cae0b;
  --cell-background-negative: #d71d38;
  --cell-text-positive: #3cae0b;
  --cell-text-negative: #d71d38;
}

[data-theme='tr'] {
  --cell-background-positive: #27ef0085;
  --cell-background-negative: #ff000085;
  --cell-text-positive: #27ef00;
  --cell-text-negative: #ff0000;
}

.seasonality {
  font-variant-numeric: tabular-nums;
}

.positive {
  --table-cell-background: color-mix(
    in srgb,
    var(--cell-background-positive) 35%,
    var(--table-background, var(--neutral-0))
  );
}

.negative {
  --table-cell-background: color-mix(
    in srgb,
    var(--cell-background-negative) 35%,
    var(--table-background, var(--neutral-0))
  );
}

.total {
  --table-cell-font-weight: 600;
}

.average {
  --table-cell-background: var(--table-background, var(--neutral-0));
  --table-cell-font-weight: 600;

  color: var(--table-text, var(--neutral-12));
  border-top: var(--borderWidth-md) solid var(--table-border, var(--neutral-7));

  &.positive {
    color: var(--cell-text-positive);
  }

  &.negative {
    color: var(--cell-text-negative);
  }
}