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


import { Checkbox } from 'exchange-elements/v2';

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

interface CheckboxFieldProps {
  label: string;
  checked: boolean;
  onValueChange: (value: boolean) => void;
}

export const CheckboxField = ({ label, checked, onValueChange }: CheckboxFieldProps) => {
  return (
    <label className={styles.checkboxField}>
      <Checkbox
        checked={checked}
        onChange={(event) => {
          onValueChange(Boolean(event.checked));
        }}
      />
      <span>{label}</span>
    </label>
  );
};








export type SettingValue = string | number | boolean;
export type SettingsValues = Record<string, SettingValue>;

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

export interface NumberSettingField extends BaseSettingField {
  type: 'number';
  defaultValue: number;
  min?: number;
  max?: number;
}

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

export interface ColorSettingField extends BaseSettingField {
  type: 'color';
  defaultValue: string;
}

export interface TextSettingField extends BaseSettingField {
  type: 'text';
  defaultValue: string;
  placeholder?: string;
}

export interface TextAreaSettingField extends BaseSettingField {
  type: 'textarea';
  defaultValue: string;
  placeholder?: string;
}

export interface BooleanSettingField extends BaseSettingField {
  type: 'boolean';
  defaultValue: boolean;
}

export type SettingField =
  | NumberSettingField
  | SelectSettingField
  | ColorSettingField
  | TextSettingField
  | TextAreaSettingField
  | BooleanSettingField;

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







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

import {
  CheckboxField,
  ColorField,
  NumberField,
  SelectField,
  TextAreaField,
  TextField,
} from '@src/components/FormFields';
import { SettingField, SettingsTab, SettingsValues, SettingValue } from '@src/types/settings';

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

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

export const EntitySettingsModal = <
  TValues extends SettingsValues,
  TField extends SettingField = SettingField,
>({
  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: SettingValue) => {
    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);
          }}
        />
      );
    }

    if (field.type === 'boolean') {
      return (
        <CheckboxField
          key={field.key}
          label={field.label}
          checked={Boolean(value)}
          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>
      )}

      <div className={styles.form}>{activeTab?.fields.map(renderField)}</div>
    </div>
  );
};