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