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


import { useEffect, useMemo, useState } from 'react';

import { ColorField, NumberField, SelectField, TextAreaField, TextField } from '@src/components/FormFields';
import { DrawingStyleField } from '@src/core/Drawings/types';
import { IndicatorSetting } from '@src/types';

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

type SettingsValue = string | number | boolean;
type SettingsValues = Record<string, SettingsValue>;
type SettingsField = IndicatorSetting | DrawingStyleField;

interface SettingsTab<TField extends SettingsField = SettingsField> {
  key: string;
  label: string;
  fields: TField[];
}

interface EntitySettingsModalProps<TValues extends SettingsValues, TField extends SettingsField = SettingsField> {
  tabs: SettingsTab<TField>[];
  values: TValues;
  onChange: (settings: TValues) => void;
  initialTabKey?: string;
}

export const EntitySettingsModal = <TValues extends SettingsValues, TField extends SettingsField = SettingsField>({
  tabs,
  values,
  onChange,
  initialTabKey,
}: EntitySettingsModalProps<TValues, TField>) => {
  const availableTabs = useMemo(() => tabs.filter((tab) => tab.fields.length > 0), [tabs]);

  const [activeTabKey, setActiveTabKey] = useState(initialTabKey ?? availableTabs[0]?.key ?? '');
  const [form, setForm] = useState<TValues>(values);

  useEffect(() => {
    setForm(values);
  }, [values]);

  useEffect(() => {
    if (!availableTabs.find((tab) => tab.key === activeTabKey)) {
      setActiveTabKey(availableTabs[0]?.key ?? '');
    }
  }, [availableTabs, activeTabKey]);

  const activeTab = availableTabs.find((tab) => tab.key === activeTabKey) ?? availableTabs[0];

  const changeValue = (key: string, value: SettingsValue) => {
    setForm((current) => {
      const next = {
        ...current,
        [key]: value,
      } as TValues;

      onChange(next);

      return next;
    });
  };

  const renderField = (field: TField) => {
    const value = form[field.key] ?? field.defaultValue;

    if (field.type === 'select') {
      return (
        <SelectField
          key={field.key}
          label={field.label}
          value={String(value)}
          options={field.options}
          onValueChange={(nextValue) => {
            changeValue(field.key, nextValue);
          }}
        />
      );
    }

    if (field.type === 'color') {
      return (
        <ColorField
          key={field.key}
          label={field.label}
          value={String(value)}
          onValueChange={(nextValue) => {
            changeValue(field.key, nextValue);
          }}
        />
      );
    }

    if (field.type === 'text') {
      return (
        <TextField
          key={field.key}
          label={field.label}
          value={String(value)}
          placeholder={field.placeholder}
          onValueChange={(nextValue) => {
            changeValue(field.key, nextValue);
          }}
        />
      );
    }

    if (field.type === 'textarea') {
      return (
        <TextAreaField
          key={field.key}
          label={field.label}
          value={String(value)}
          placeholder={field.placeholder}
          onValueChange={(nextValue) => {
            changeValue(field.key, nextValue);
          }}
        />
      );
    }

    return (
      <NumberField
        key={field.key}
        label={field.label}
        value={typeof value === 'number' ? value : field.defaultValue}
        min={field.min}
        max={field.max}
        onValueChange={(nextValue) => {
          changeValue(field.key, nextValue);
        }}
      />
    );
  };

  return (
    <div className={styles.wrapper}>
      {availableTabs.length > 1 && (
        <div className={styles.tabs}>
          {availableTabs.map((tab) => (
            <button
              key={tab.key}
              type="button"
              className={tab.key === activeTabKey ? styles.tabActive : styles.tab}
              onClick={() => setActiveTabKey(tab.key)}
            >
              {tab.label}
            </button>
          ))}
        </div>
      )}

      {activeTab?.fields.map(renderField)}
    </div>
  );
};




import type { ISeriesApi, SeriesOptionsMap, Time } from 'lightweight-charts';

export type SeriesApi = ISeriesApi<keyof SeriesOptionsMap, Time>;

export interface Point {
  x: number;
  y: number;
}

export interface Bounds {
  left: number;
  right: number;
  top: number;
  bottom: number;
}

export interface ContainerSize {
  width: number;
  height: number;
}

export interface Anchor {
  time: Time;
  price: number;
}

export interface AxisSegment {
  from: number;
  to: number;
  color: string;
}

export interface AxisLabel {
  coordinate: number;
  text: string;
  textColor: string;
  backgroundColor: string;
}

export interface UpdatableView {
  update(): void;
}

export type DrawingStyleValue = string | number | boolean;
export type DrawingStyleSettings = Record<string, DrawingStyleValue>;

interface BaseDrawingStyleField {
  key: string;
  label: string;
}

export interface NumberDrawingStyleField extends BaseDrawingStyleField {
  type: 'number';
  defaultValue: number;
  min?: number;
  max?: number;
}

export interface SelectDrawingStyleField extends BaseDrawingStyleField {
  type: 'select';
  defaultValue: string | number;
  options: { label: string; value: string | number }[];
}

export interface ColorDrawingStyleField extends BaseDrawingStyleField {
  type: 'color';
  defaultValue: string;
}

export interface TextDrawingStyleField extends BaseDrawingStyleField {
  type: 'text';
  defaultValue: string;
  placeholder?: string;
}

export interface TextAreaDrawingStyleField extends BaseDrawingStyleField {
  type: 'textarea';
  defaultValue: string;
  placeholder?: string;
}

export type DrawingStyleField =
  | NumberDrawingStyleField
  | SelectDrawingStyleField
  | ColorDrawingStyleField
  | TextDrawingStyleField
  | TextAreaDrawingStyleField;



import {
  DeepPartial,
  HistogramData,
  LineData,
  LineWidth,
  PriceScaleOptions,
  SeriesDataItemTypeMap,
  SeriesPartialOptionsMap,
  SeriesType,
  Time,
  WhitespaceData,
} from 'lightweight-charts';

import { indicatorLabelById, IndicatorsIds } from '@src/constants';
import { IndicatorDataFormatter } from '@src/core/Indicators';
import { ChartSeriesType } from '@src/types/chart';

export type IndicatorData = HistogramData<Time> | LineData<Time> | WhitespaceData<Time>;

export type IndicatorType = 'SMA' | 'EMA' | 'RSI' | 'OHLC' | 'VOL';

export type MASource = 'open' | 'high' | 'low' | 'close';

export type IndicatorSetting = NumberIndicatorSetting | SelectIndicatorSetting;
export type IndicatorSettings = Record<string, string | number>;

export type IndicatorLabel = (typeof indicatorLabelById)[IndicatorsIds];

export interface IndicatorStateConfig {
  type: IndicatorType;
  id?: IndicatorsIds;
  period?: number;
  color?: string;
  lineWidth?: LineWidth;
  visible?: boolean;
  params?: Record<string, any>;
  pane?: string;
}

interface BaseIndicatorSetting {
  key: string;
  label: string;
}

interface NumberIndicatorSetting extends BaseIndicatorSetting {
  type: 'number';
  defaultValue: number;
  min?: number;
  max?: number;
}

interface SelectIndicatorSetting extends BaseIndicatorSetting {
  type: 'select';
  defaultValue: string;
  options: { label: string; value: string | number }[];
}

export interface IndicatorSerie {
  id: string;
  name: ChartSeriesType;
  seriesOptions?: SeriesPartialOptionsMap[ChartSeriesType];
  priceScaleOptions?: DeepPartial<PriceScaleOptions>;
  priceScaleId?: string;
  dataFormatter?<T extends SeriesType>(params: IndicatorDataFormatter<T>): SeriesDataItemTypeMap<Time>[T][];
}

export interface IndicatorConfig {
  series: IndicatorSerie[];
  settings?: IndicatorSetting[];
  newPane?: boolean;
  label?: string;
  seriesLabels?: Record<string, string>;
}