diff --git a/src/components/common/Custom/CaliForm.js b/src/components/common/Custom/CaliForm.js deleted file mode 100644 index 4fa933f..0000000 --- a/src/components/common/Custom/CaliForm.js +++ /dev/null @@ -1,526 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { getOptionsArray } from '../../../utils'; -import { - SelectInput, - SearchBarAlert -} from '../../../styles/Components'; -import { - FormInput, FormInputSuffix, FormInputSuffixWrapper, - FormLabel, - FormRowGroup, - FormStatusBar, - FormStatusLabel, - FormStatusWarning, -} from '../../../styles/ModuleComponents'; -import { CheckBox, SingleDatePicker, SingleTimePicker } from '../../common'; -import Button from '../../common/button/Button'; -import styled from 'styled-components'; -import ImageUploadBtn from '../../ServiceManage/ImageUploadBtn'; - -const CaliForm = ({ - config, // 폼 설정 JSON - mode, // 'create', 'update', 'view' 중 하나 - initialData, // 초기 데이터 - externalData, // 외부 데이터(옵션 등) - onSubmit, // 제출 핸들러 - onCancel, // 취소 핸들러 - className, // 추가 CSS 클래스 - onFieldValidation, // 필드 유효성 검사 콜백 - formRef // 폼 ref - }) => { - const { t } = useTranslation(); - const [formData, setFormData] = useState({ ...(config?.initData || {}), ...(initialData || {}) }); - const [errors, setErrors] = useState({}); - const [isFormValid, setIsFormValid] = useState(false); - - // 필드 변경 핸들러 - const handleFieldChange = (fieldId, value) => { - setFormData(prev => ({ - ...prev, - [fieldId]: value - })); - }; - - // 날짜 변경 핸들러 - const handleDateChange = (fieldId, date) => { - if (!date) return; - setFormData(prev => ({ - ...prev, - [fieldId]: date - })); - }; - - // 시간 변경 핸들러 - const handleTimeChange = (fieldId, time) => { - if (!time) return; - - const newDateTime = formData[fieldId] ? new Date(formData[fieldId]) : new Date(); - newDateTime.setHours(time.getHours(), time.getMinutes(), 0, 0); - - setFormData(prev => ({ - ...prev, - [fieldId]: newDateTime - })); - }; - - // 폼 유효성 검사 - useEffect(() => { - const validateForm = () => { - const newErrors = {}; - let isValid = true; - - if (!config) return false; - - // 필수 필드 검사 - const requiredFields = config.fields - .filter(f => - f.visibleOn.includes(mode) && - f.validations?.includes("required") - ) - .map(f => f.id); - - requiredFields.forEach(fieldId => { - if (!formData[fieldId] && formData[fieldId] !== 0) { - newErrors[fieldId] = t('REQUIRED_FIELD'); - isValid = false; - } - }); - - // 조건부 유효성 검사 - if (config.validations && config.validations[mode]) { - for (const validation of config.validations[mode]) { - const conditionResult = evaluateCondition(validation.condition, { - ...formData, - current_time: new Date().getTime() - }); - - if (conditionResult) { - // 전체 폼 검증 오류 - newErrors._form = t(validation.message); - isValid = false; - } - } - } - - setErrors(newErrors); - setIsFormValid(isValid); - - if (onFieldValidation) { - onFieldValidation(isValid, newErrors); - } - - return isValid; - }; - - validateForm(); - }, [config, formData, mode, t, onFieldValidation]); - - // 간단한 조건식 평가 함수 - const evaluateCondition = (conditionStr, context) => { - try { - const fn = new Function(...Object.keys(context), `return ${conditionStr}`); - return fn(...Object.values(context)); - } catch (e) { - console.error('Error evaluating condition:', e); - return false; - } - }; - - // 필드 렌더링 - const renderField = (field) => { - const isEditable = field.editableOn.includes(mode); - const value = formData[field.id] !== undefined ? formData[field.id] : ''; - const hasError = errors[field.id]; - - switch (field.type) { - case 'text': - return ( -
- handleFieldChange(field.id, e.target.value)} - disabled={!isEditable} - width={field.width} - className={hasError ? 'error' : ''} - /> - {hasError &&
{hasError}
} -
- ); - - case 'number': - return ( -
- handleFieldChange(field.id, Number(e.target.value))} - disabled={!isEditable} - width={field.width} - min={field.min} - max={field.max} - step={field.step || 1} - className={hasError ? 'error' : ''} - /> - {hasError &&
{hasError}
} -
- ); - - case 'select': - let options = []; - - if (field.optionsKey) { - // 옵션 설정에서 가져오기 - options = getOptionsArray(field.optionsKey); - } else if (field.dataSource && externalData) { - // 외부 데이터 소스 사용 - const dataSource = externalData[field.dataSource] || []; - - options = dataSource.map(item => ({ - value: item[field.valueField], - label: field.displayFormat - ? field.displayFormat.replace('{value}', item[field.valueField]) - .replace('{display}', item[field.displayField]) - : `${item[field.displayField]}(${item[field.valueField]})` - })); - } else if (field.options) { - options = field.options; - } - - return ( -
- handleFieldChange(field.id, e.target.value)} - disabled={!isEditable} - width={field.width} - className={hasError ? 'error' : ''} - > - {options.map((option, index) => ( - - ))} - - {hasError &&
{hasError}
} -
- ); - - case 'datePicker': - return ( -
- handleDateChange(field.id, date)} - selectedDate={value} - minDate={field.minDate} - maxDate={field.maxDate} - className={hasError ? 'error' : ''} - /> - {hasError &&
{hasError}
} -
- ); - - case 'timePicker': - return ( -
- handleTimeChange(field.id, time)} - className={hasError ? 'error' : ''} - /> - {hasError &&
{hasError}
} -
- ); - - case 'status': - let statusText = ""; - if (field.optionsKey && formData[field.statusField]) { - const statusOptions = getOptionsArray(field.optionsKey); - const statusItem = statusOptions.find(item => item.value === formData[field.statusField]); - statusText = statusItem ? statusItem.name : "등록"; - } - - return ( -
- - - {field.label}: {statusText} - - {mode === 'update' && field.warningMessage && ( - - {t(field.warningMessage)} - - )} - -
- ); - - case 'dateTimeRange': - return ( -
-
- handleDateChange(field.startDateField, date)} - selectedDate={formData[field.startDateField]} - /> - handleTimeChange(field.startDateField, time)} - /> - handleDateChange(field.endDateField, date)} - selectedDate={formData[field.endDateField]} - /> - handleTimeChange(field.endDateField, time)} - /> -
- {hasError &&
{hasError}
} -
- ); - - case 'imageUpload': - const imageLanguage = field.language; - const imageList = formData.image_list || []; - const imageData = imageList.find(img => img.language === imageLanguage) || { content: '' }; - - return ( -
- - {imageLanguage} - { - const updatedImageList = [...imageList]; - const index = updatedImageList.findIndex(img => img.language === imageLanguage); - - if (index !== -1) { - updatedImageList[index] = { - ...updatedImageList[index], - content: fileName - }; - } else { - updatedImageList.push({ - language: imageLanguage, - content: fileName - }); - } - - handleFieldChange('image_list', updatedImageList); - }} - onFileDelete={() => { - const updatedImageList = [...imageList]; - const index = updatedImageList.findIndex(img => img.language === imageLanguage); - - if (index !== -1) { - updatedImageList[index] = { - ...updatedImageList[index], - content: '' - }; - - handleFieldChange('image_list', updatedImageList); - } - }} - fileName={imageData.content} - disabled={!isEditable} - /> - - {hasError &&
{hasError}
} -
- ); - - case 'checkbox': - return ( -
- handleFieldChange(field.id, e.target.checked)} - disabled={!isEditable} - /> - {hasError &&
{hasError}
} -
- ); - - case 'textWithSuffix': - const linkLanguage = field.suffix; - const linkList = formData.link_list || []; - const linkData = linkList.find(link => link.language === linkLanguage) || { content: '' }; - - return ( -
- {field.label && {field.label}} - - { - const updatedLinkList = [...linkList]; - const index = updatedLinkList.findIndex(link => link.language === linkLanguage); - - if (index !== -1) { - updatedLinkList[index] = { - ...updatedLinkList[index], - content: e.target.value - }; - } else { - updatedLinkList.push({ - language: linkLanguage, - content: e.target.value - }); - } - - handleFieldChange('link_list', updatedLinkList); - }} - disabled={!isEditable} - width={field.width} - suffix="true" - /> - {linkLanguage} - - {hasError &&
{hasError}
} -
- ); - - default: - return null; - } - }; - - // 조건부 렌더링을 위한 필드 필터링 - const getVisibleFields = () => { - if (!config) return []; - - return config.fields.filter(field => { - if (!field.visibleOn.includes(mode)) return false; - - // 조건부 표시 필드 처리 - if (field.conditional) { - const { field: condField, operator, value } = field.conditional; - - if (operator === "==" && formData[condField] !== value) return false; - if (operator === "!=" && formData[condField] === value) return false; - } - - return true; - }); - }; - - // 그리드 기반 필드 렌더링 - const renderGridFields = () => { - if (!config) return null; - - const visibleFields = getVisibleFields(); - const { rows, columns } = config.grid; - - // 그리드 레이아웃 생성 - return ( -
- {visibleFields.map((field) => { - const { row, col, width } = field.position; - - return ( -
- - {field.label}{field.validations?.includes("required") && *} - {renderField(field)} - -
- ); - })} -
- ); - }; - - // 버튼 렌더링 - const renderButtons = () => { - if (!config || !config.actions || !config.actions[mode]) return null; - - return ( -
- {config.actions[mode].map(action => ( -
- ); - }; - - if (!config) return
로딩 중...
; - - return ( -
-
- {renderGridFields()} - - {errors._form && ( - - {errors._form} - - )} -
- -
- {renderButtons()} -
-
- ); -}; - -export default CaliForm; - -const LanguageWrapper = styled.div` - width: ${props => props.width || '100%'}; - //margin-bottom: 20px; - padding-bottom: 20px; - padding-left: 90px; - - &:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; - } -`; - -const LanguageLabel = styled.h4` - color: #444; - margin: 0 0 10px 20px; - font-size: 16px; - font-weight: 500; -`; \ No newline at end of file diff --git a/src/components/common/Layout/DetailGrid.js b/src/components/common/Layout/DetailGrid.js index baca099..7c2ee12 100644 --- a/src/components/common/Layout/DetailGrid.js +++ b/src/components/common/Layout/DetailGrid.js @@ -2,6 +2,7 @@ import React from 'react'; import { Row, Col, Form, Input, Select, DatePicker, TimePicker, InputNumber, Switch, Button, Checkbox } from 'antd'; import styled from 'styled-components'; import dayjs from 'dayjs'; +import { AnimatedTabs } from '../index'; const { RangePicker } = DatePicker; /** @@ -52,7 +53,10 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 } max, format, required, - showTime + showTime, + tabItems, + activeKey, + onTabChange } = item; // 현재 값 가져오기 (formData에서 또는 항목에서) @@ -105,6 +109,7 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 } return ( onChange(key, date, handler)} @@ -191,8 +196,8 @@ const DetailGrid = ({ items, formData, onChange, disabled = false, columns = 4 } case 'tab': return case 'custom': @@ -249,13 +254,15 @@ const StatusDisplay = ({ status }) => { let color = ''; let text = ''; - switch (status) { + const lowerStatus = typeof status === 'string' ? status.toLowerCase() : status; + + switch (lowerStatus) { case 'wait': - color = '#faad14'; + color = '#FAAD14'; text = '대기'; break; case 'running': - color = '#52c41a'; + color = '#4287f5'; text = '진행중'; break; case 'finish': @@ -271,7 +278,7 @@ const StatusDisplay = ({ status }) => { text = '삭제'; break; default: - color = '#1890ff'; + color = '#DEBB46'; text = status; } diff --git a/src/components/common/control/AnimatedTabs.js b/src/components/common/control/AnimatedTabs.js index 5dcaef9..76217e0 100644 --- a/src/components/common/control/AnimatedTabs.js +++ b/src/components/common/control/AnimatedTabs.js @@ -5,38 +5,72 @@ import { motion, AnimatePresence } from 'framer-motion'; // 통합된 애니메이션 탭 컴포넌트 const AnimatedTabs = ({ items, activeKey, onChange }) => { + // 각 항목의 children을 애니메이션 래퍼로 감싸기 + const tabItems = items.map(item => ({ + key: item.key, + label: item.label, + children: ( + + + {item.children} + + + ) + })); + return ( - {items.map(item => ( - - - - {item.children} - - - - ))} - + items={tabItems} + /> ); }; +// const AnimatedTabs = ({ items, activeKey, onChange }) => { +// return ( +// +// {items.map(item => ( +// +// +// +// {item.children} +// +// +// +// ))} +// +// ); +// }; + const StyledTabs = styled(Tabs)` margin-top: 20px; width: 100%; diff --git a/src/components/common/index.js b/src/components/common/index.js index 595585c..3320f30 100644 --- a/src/components/common/index.js +++ b/src/components/common/index.js @@ -21,6 +21,7 @@ import CDivider from './CDivider'; import TopButton from './button/TopButton'; import AntButton from './button/AntButton'; import DetailLayout from './Layout/DetailLayout'; +import AnimatedTabs from './control/AnimatedTabs'; import CaliTable from './Custom/CaliTable' @@ -56,5 +57,6 @@ export { DateTimeInput, FrontPagination, DownloadProgress, CaliTable, - DetailLayout + DetailLayout, + AnimatedTabs }; \ No newline at end of file