/
sunchise
/
Test
Обзор
Документация
Войти
/
sunchise
/
Test
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/src/components/common/GenericCreateForm.tsx
166 строк
7 KB
sunchise
Запрос на слияние 'frontend2' (
#1
) из frontend2 в master
26 мар 2026, 18:24
Верифицирован
26 мар 2026, 18:24
761ae63
Код
Авторство
О чём код?
/* eslint-disable @typescript-eslint/no-explicit-any */ // Импорт типа FieldConfig из централизованного файла типов import type { FieldConfig } from '../../types/formTypes'; interface GenericCreateFormProps<T> { // Данные формы formData: T; // Конфигурация полей fields: FieldConfig<T>[]; // Обработчик изменения поля onFieldChange: (name: keyof T, value: any) => void; // Обработчик отправки формы onSubmit: (e: React.FormEvent) => void; // Состояние загрузки loading?: boolean; // Ошибки валидации errors?: Partial<Record<keyof T, string>>; // Текст кнопки отправки submitButtonText?: string; // Обработчик отмены onCancel?: () => void; } function GenericCreateForm<T extends Record<string, any>>({ formData, fields, onFieldChange, onSubmit, loading = false, errors = {}, submitButtonText = 'Создать', onCancel, }: GenericCreateFormProps<T>) { const handleChange = (name: keyof T, value: any) => { onFieldChange(name, value); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); onSubmit(e); }; return ( <form onSubmit={handleSubmit} className="space-y-6"> <div className="grid gap-6"> {fields.map((field) => ( <div key={String(field.name)} className="form-field"> <label htmlFor={String(field.name)} className="block text-sm font-medium text-text"> {field.label} {field.required && <span className="text-danger"> *</span>} </label> {field.type === 'text' && ( <input id={String(field.name)} type="text" value={ field.name === 'contact_info' && formData[field.name] && typeof formData[field.name] === 'object' ? Object.values(formData[field.name] as Record<string, any>).join(', ') : (formData[field.name] as string) || '' } onChange={(e) => handleChange(field.name, e.target.value)} placeholder={field.placeholder} disabled={field.disabled || loading} className={`block w-full rounded-md border border-border bg-surface px-3 py-2 text-text placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-primary ${errors[field.name] ? 'border-danger focus:ring-danger' : 'focus:ring-primary'}`} /> )} {field.type === 'textarea' && ( <textarea id={String(field.name)} value={(formData[field.name] as string) || ''} onChange={(e) => handleChange(field.name, e.target.value)} placeholder={field.placeholder} disabled={field.disabled || loading} className={`block w-full rounded-md border border-border bg-surface px-3 py-2 text-text placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-primary ${errors[field.name] ? 'border-danger focus:ring-danger' : 'focus:ring-primary'}`} rows={4} /> )} {field.type === 'number' && ( <input id={String(field.name)} type="number" value={(formData[field.name] as number) || ''} onChange={(e) => handleChange(field.name, e.target.value ? Number(e.target.value) : null)} placeholder={field.placeholder} disabled={field.disabled || loading} className={`block w-full rounded-md border border-border bg-surface px-3 py-2 text-text placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-primary ${errors[field.name] ? 'border-danger focus:ring-danger' : 'focus:ring-primary'}`} /> )} {field.type === 'select' && field.options && ( <select id={String(field.name)} value={(formData[field.name] as string | number) || ''} onChange={(e) => handleChange(field.name, e.target.value)} disabled={field.disabled || loading} className={`block w-full rounded-md border border-border bg-surface px-3 py-2 text-text focus:outline-none focus:ring-2 focus:ring-primary ${errors[field.name] ? 'border-danger focus:ring-danger' : 'focus:ring-primary'}`} > <option value="">{field.placeholder || 'Выберите значение'}</option> {field.options.map((option) => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> )} {field.type === 'date' && ( <input id={String(field.name)} type="date" value={(formData[field.name] as string) || ''} onChange={(e) => handleChange(field.name, e.target.value)} disabled={field.disabled || loading} className={`block w-full rounded-md border border-border bg-surface px-3 py-2 text-text focus:outline-none focus:ring-2 focus:ring-primary ${errors[field.name] ? 'border-danger focus:ring-danger' : 'focus:ring-primary'}`} /> )} {field.type === 'checkbox' && ( <div className="flex items-center"> <input id={String(field.name)} type="checkbox" checked={!!formData[field.name]} onChange={(e) => handleChange(field.name, e.target.checked)} disabled={field.disabled || loading} className="h-4 w-4 rounded border-border bg-surface text-primary focus:ring-primary" /> <label htmlFor={String(field.name)} className="ml-2 block text-sm text-text"> {field.label} </label> </div> )} {errors[field.name] && ( <p className="mt-1 text-sm text-danger">{errors[field.name]}</p> )} </div> ))} </div> <div className="flex justify-end gap-3"> {onCancel && ( <button type="button" onClick={onCancel} className="inline-flex items-center rounded-md border border-border bg-surface px-4 py-2 text-sm font-medium text-text hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" > Отмена </button> )} <button type="submit" disabled={loading} className="inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-on-primary shadow-sm hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:opacity-50" > {loading ? 'Создание...' : submitButtonText} </button> </div> </form> ); } export default GenericCreateForm;