refactor(WEB): 코드 품질 개선 및 불필요 코드 제거

- 미사용 import/변수/console.log 대량 정리 (100+개 파일)
- ItemMasterContext 간소화 (미사용 로직 제거)
- IntegratedListTemplateV2 / UniversalListPage 개선
- 결재 컴포넌트(ApprovalBox, DraftBox, ReferenceBox) 정리
- HR 컴포넌트(급여/휴가/부서) 코드 간소화
- globals.css 스타일 정리 및 개선
- AuthenticatedLayout 개선
- middleware CSP 정리
- proxy route 불필요 로깅 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-10 20:55:11 +09:00
parent 437d5f6834
commit 0db6302652
138 changed files with 779 additions and 1034 deletions

View File

@@ -101,8 +101,7 @@ export function useFieldDetection({
// "구매 부품", "PURCHASED", "구매부품" 등 다양한 형태 지원
const isPurchased = currentPartType.includes('구매') || currentPartType.toUpperCase() === 'PURCHASED';
// console.log('[useFieldDetection] 부품 유형 감지:', { partTypeFieldKey: foundPartTypeKey, currentPartType, isBending, isAssembly, isPurchased });
//
return {
partTypeFieldKey: foundPartTypeKey,
selectedPartType: currentPartType,
@@ -133,8 +132,7 @@ export function useFieldDetection({
fieldKey.includes('부품구성');
if (isCheckbox && isBomRelated) {
// console.log('[useFieldDetection] BOM 체크박스 필드 발견:', { fieldKey, fieldName });
return field.field_key || `field_${field.id}`;
// return field.field_key || `field_${field.id}`;
}
}
}
@@ -154,12 +152,10 @@ export function useFieldDetection({
fieldKey.includes('부품구성');
if (isCheckbox && isBomRelated) {
// console.log('[useFieldDetection] BOM 체크박스 필드 발견 (직접필드):', { fieldKey, fieldName });
return field.field_key || `field_${field.id}`;
// return field.field_key || `field_${field.id}`;
}
}
// console.log('[useFieldDetection] BOM 체크박스 필드를 찾지 못함');
return '';
}, [structure]);

View File

@@ -115,7 +115,6 @@ export function useFileHandling({
if (typeof filesRaw === 'string') {
try {
filesRaw = JSON.parse(filesRaw);
console.log('[useFileHandling] files JSON 문자열 파싱 완료');
} catch (e) {
console.error('[useFileHandling] files JSON 파싱 실패:', e);
filesRaw = undefined;
@@ -125,12 +124,6 @@ export function useFileHandling({
const files = filesRaw as FilesObject | undefined;
// 2025-12-15: 파일 로드 디버깅
console.log('[useFileHandling] 파일 로드 시작');
console.log('[useFileHandling] initialData.files (raw):', initialData.files);
console.log('[useFileHandling] filesRaw 타입:', typeof filesRaw);
console.log('[useFileHandling] files 변수:', files);
console.log('[useFileHandling] specification_file:', files?.specification_file);
console.log('[useFileHandling] certification_file:', files?.certification_file);
// 전개도 파일 (배열의 마지막 파일 = 최신 파일을 가져옴)
const bendingFileArr = files?.bending_diagram;
@@ -138,12 +131,9 @@ export function useFileHandling({
? bendingFileArr[bendingFileArr.length - 1]
: undefined;
if (bendingFile) {
console.log('[useFileHandling] bendingFile 전체 객체:', bendingFile);
console.log('[useFileHandling] bendingFile 키 목록:', Object.keys(bendingFile));
setExistingBendingDiagram(bendingFile.file_path);
// API에서 id 또는 file_id로 올 수 있음
const bendingFileId = bendingFile.id || bendingFile.file_id;
console.log('[useFileHandling] bendingFile ID 추출:', { id: bendingFile.id, file_id: bendingFile.file_id, final: bendingFileId });
setExistingBendingDiagramFileId(bendingFileId as number);
} else if (initialData.bending_diagram) {
setExistingBendingDiagram(initialData.bending_diagram as string);
@@ -154,14 +144,11 @@ export function useFileHandling({
const specFile = specFileArr && specFileArr.length > 0
? specFileArr[specFileArr.length - 1]
: undefined;
console.log('[useFileHandling] specFile 전체 객체:', specFile);
console.log('[useFileHandling] specFile 키 목록:', specFile ? Object.keys(specFile) : 'undefined');
if (specFile?.file_path) {
setExistingSpecificationFile(specFile.file_path);
setExistingSpecificationFileName(specFile.file_name || '시방서');
// API에서 id 또는 file_id로 올 수 있음
const specFileId = specFile.id || specFile.file_id;
console.log('[useFileHandling] specFile ID 추출:', { id: specFile.id, file_id: specFile.file_id, final: specFileId });
setExistingSpecificationFileId(specFileId as number || null);
} else {
// 파일이 없으면 상태 초기화 (이전 값 제거)
@@ -175,14 +162,11 @@ export function useFileHandling({
const certFile = certFileArr && certFileArr.length > 0
? certFileArr[certFileArr.length - 1]
: undefined;
console.log('[useFileHandling] certFile 전체 객체:', certFile);
console.log('[useFileHandling] certFile 키 목록:', certFile ? Object.keys(certFile) : 'undefined');
if (certFile?.file_path) {
setExistingCertificationFile(certFile.file_path);
setExistingCertificationFileName(certFile.file_name || '인정서');
// API에서 id 또는 file_id로 올 수 있음
const certFileId = certFile.id || certFile.file_id;
console.log('[useFileHandling] certFile ID 추출:', { id: certFile.id, file_id: certFile.file_id, final: certFileId });
setExistingCertificationFileId(certFileId as number || null);
} else {
// 파일이 없으면 상태 초기화 (이전 값 제거)
@@ -244,13 +228,6 @@ export function useFileHandling({
onBendingDiagramDeleted?: () => void;
}
) => {
console.log('[useFileHandling] handleDeleteFile 호출:', {
fileType,
propItemId,
existingBendingDiagramFileId,
existingSpecificationFileId,
existingCertificationFileId,
});
if (!propItemId) {
console.error('[useFileHandling] propItemId가 없습니다');
@@ -267,7 +244,6 @@ export function useFileHandling({
fileId = existingCertificationFileId;
}
console.log('[useFileHandling] 삭제할 파일 ID:', fileId);
if (!fileId) {
console.error('[useFileHandling] 파일 ID를 찾을 수 없습니다:', fileType);

View File

@@ -34,12 +34,10 @@ export function useFormStructure(
const initData = await itemMasterApi.init();
// 단위 옵션 저장 (SimpleUnitOption 형식으로 변환)
console.log('[useFormStructure] API initData.unitOptions:', initData.unitOptions);
const simpleUnitOptions: SimpleUnitOption[] = (initData.unitOptions || []).map((u) => ({
label: u.unit_name,
value: u.unit_code,
}));
console.log('[useFormStructure] Processed unitOptions:', simpleUnitOptions.length, 'items');
setUnitOptions(simpleUnitOptions);
// 2. 품목 유형에 해당하는 페이지 찾기

View File

@@ -74,8 +74,7 @@ export function usePartTypeHandling({
// 이전 값이 있고, 현재 값과 다른 경우에만 초기화
if (prevPartType && prevPartType !== currentPartType) {
// console.log('[usePartTypeHandling] 부품 유형 변경 감지:', prevPartType, '→', currentPartType);
//
// setTimeout으로 다음 틱에서 초기화 실행
// → 부품 유형 Select 값 변경이 먼저 완료된 후 초기화
setTimeout(() => {
@@ -121,8 +120,7 @@ export function usePartTypeHandling({
// 중복 제거 후 초기화
const uniqueFields = [...new Set(fieldsToReset)];
// console.log('[usePartTypeHandling] 초기화할 필드:', uniqueFields);
//
uniqueFields.forEach((fieldKey) => {
setFieldValue(fieldKey, '');
});
@@ -152,11 +150,6 @@ export function usePartTypeHandling({
}, 0);
const sumString = totalSum.toString();
console.log('[usePartTypeHandling] bendingDetails 폭 합계 → formData 동기화:', {
widthSumKey: bendingFieldKeys.widthSum,
totalSum,
bendingDetailsCount: bendingDetails.length,
});
setFieldValue(bendingFieldKeys.widthSum, sumString);
bendingWidthSumSyncedRef.current = true;
@@ -175,14 +168,12 @@ export function usePartTypeHandling({
// 품목명이 변경되었고, 이전 값이 있었을 때만 종류 필드 초기화
if (prevItemNameValue && prevItemNameValue !== currentItemNameValue) {
// console.log('[usePartTypeHandling] 품목명 변경 감지:', prevItemNameValue, '→', currentItemNameValue);
//
// 모든 종류 필드 값 초기화
allCategoryKeysWithIds.forEach(({ key }) => {
const currentVal = (formData[key] as string) || '';
if (currentVal) {
// console.log('[usePartTypeHandling] 종류 필드 초기화:', key);
setFieldValue(key, '');
// setFieldValue(key, '');
}
});
}

View File

@@ -129,7 +129,6 @@ export default function DynamicItemForm({
useEffect(() => {
if (mode === 'edit' && initialBomLines && initialBomLines.length > 0) {
setBomLines(initialBomLines);
console.log('[DynamicItemForm] initialBomLines로 BOM 데이터 로드:', initialBomLines.length, '건');
}
}, [mode, initialBomLines]);
@@ -159,7 +158,6 @@ export default function DynamicItemForm({
.map((item: { code?: string; item_code?: string }) => item.code || item.item_code || '')
.filter((code: string) => code);
setExistingItemCodes(codes);
// console.log('[DynamicItemForm] PT 기존 품목코드 로드:', codes.length, '개');
}
} catch (err) {
console.error('[DynamicItemForm] PT 품목코드 조회 실패:', err);
@@ -213,18 +211,9 @@ export default function DynamicItemForm({
const [isEditDataMapped, setIsEditDataMapped] = useState(false);
useEffect(() => {
console.log('[DynamicItemForm] Edit useEffect 체크:', {
mode,
hasStructure: !!structure,
hasInitialData: !!initialData,
isEditDataMapped,
structureSections: structure?.sections?.length,
});
if (mode !== 'edit' || !structure || !initialData || isEditDataMapped) return;
console.log('[DynamicItemForm] Edit mode: initialData 직접 로드 (field_key 통일됨)');
console.log('[DynamicItemForm] initialData:', initialData);
// structure의 field_key들 확인
const fieldKeys: string[] = [];
@@ -233,8 +222,6 @@ export default function DynamicItemForm({
fieldKeys.push(f.field.field_key || `field_${f.field.id}`);
});
});
console.log('[DynamicItemForm] structure field_keys:', fieldKeys);
console.log('[DynamicItemForm] initialData keys:', Object.keys(initialData));
// field_key가 통일되었으므로 initialData를 그대로 사용
// 기존 레거시 데이터(98_unit 형식)도 그대로 동작
@@ -348,7 +335,6 @@ export default function DynamicItemForm({
// PT (절곡/조립) 전개도 이미지 업로드
if (selectedItemType === 'PT' && (isBendingPart || isAssemblyPart) && bendingDiagramFile) {
try {
console.log('[DynamicItemForm] 전개도 파일 업로드 시작:', bendingDiagramFile.name);
await uploadItemFile(itemId, bendingDiagramFile, 'bending_diagram', {
fieldKey: 'bending_diagram',
// 수정 모드: 기존 파일 ID가 있으면 덮어쓰기, 없으면 새 파일 등록
@@ -359,7 +345,6 @@ export default function DynamicItemForm({
type: d.shaded ? 'shaded' : 'normal',
})) : undefined,
});
console.log('[DynamicItemForm] 전개도 파일 업로드 성공');
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[DynamicItemForm] 전개도 파일 업로드 실패:', error);
@@ -370,13 +355,11 @@ export default function DynamicItemForm({
// FG (제품) 시방서 업로드
if (selectedItemType === 'FG' && specificationFile) {
try {
console.log('[DynamicItemForm] 시방서 파일 업로드 시작:', specificationFile.name);
await uploadItemFile(itemId, specificationFile, 'specification', {
fieldKey: 'specification_file',
// 수정 모드: 기존 파일 ID가 있으면 덮어쓰기, 없으면 새 파일 등록
fileId: existingSpecificationFileId ?? undefined,
});
console.log('[DynamicItemForm] 시방서 파일 업로드 성공');
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[DynamicItemForm] 시방서 파일 업로드 실패:', error);
@@ -387,7 +370,6 @@ export default function DynamicItemForm({
// FG (제품) 인정서 업로드
if (selectedItemType === 'FG' && certificationFile) {
try {
console.log('[DynamicItemForm] 인정서 파일 업로드 시작:', certificationFile.name);
// formData에서 인정서 관련 필드 추출
const certNumber = Object.entries(formData).find(([key]) =>
key.includes('certification_number') || key.includes('인정번호')
@@ -407,7 +389,6 @@ export default function DynamicItemForm({
certificationStartDate: certStartDate,
certificationEndDate: certEndDate,
});
console.log('[DynamicItemForm] 인정서 파일 업로드 성공');
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[DynamicItemForm] 인정서 파일 업로드 실패:', error);
@@ -457,7 +438,6 @@ export default function DynamicItemForm({
// 2025-12-09: field_key 통일로 변환 로직 제거
// formData의 field_key가 백엔드 필드명과 일치하므로 직접 사용
console.log('[DynamicItemForm] 저장 시 formData:', formData);
// is_active 필드만 boolean 변환 (드롭다운 값 → boolean)
const convertedData: Record<string, any> = {};
@@ -509,8 +489,7 @@ export default function DynamicItemForm({
finalSpec = convertedData.spec;
}
// console.log('[DynamicItemForm] 품목명/규격 결정:', { finalName, finalSpec });
//
// 품목코드 결정
// 2025-12-11: 수정 모드에서는 기존 코드 유지 (자동생성으로 코드가 변경되는 버그 수정)
// 생성 모드에서만 자동생성 코드 사용
@@ -569,20 +548,17 @@ export default function DynamicItemForm({
} : {}),
} as DynamicFormData;
// console.log('[DynamicItemForm] 제출 데이터:', submitData);
//
// 2025-12-11: 품목코드 중복 체크 (조립/절곡 부품만 해당)
// PT-조립부품, PT-절곡부품은 품목코드가 자동생성되므로 중복 체크 필요
const needsDuplicateCheck = selectedItemType === 'PT' && (isAssemblyPart || isBendingPart) && finalCode;
if (needsDuplicateCheck) {
console.log('[DynamicItemForm] 품목코드 중복 체크:', finalCode);
// 수정 모드에서는 자기 자신 제외 (propItemId)
const excludeId = mode === 'edit' ? propItemId : undefined;
const duplicateResult = await checkItemCodeDuplicate(finalCode, excludeId);
console.log('[DynamicItemForm] 중복 체크 결과:', duplicateResult);
if (duplicateResult.isDuplicate) {
// 중복 발견 → 다이얼로그 표시
@@ -983,8 +959,7 @@ export default function DynamicItemForm({
const isBomRequired = bomValue === true || bomValue === 'true' || bomValue === '1' || bomValue === 1;
// 디버깅 로그
// console.log('[DynamicItemForm] BOM 체크 디버깅:', { bomRequiredFieldKey, bomValue, isBomRequired });
//
if (!isBomRequired) return null;
return (
@@ -1021,7 +996,6 @@ export default function DynamicItemForm({
const blob = new Blob([uint8Array], { type: mimeType });
const file = new File([blob], `bending_diagram_${Date.now()}.png`, { type: mimeType });
setBendingDiagramFile(file);
console.log('[DynamicItemForm] 드로잉 캔버스 → File 변환 성공:', file.name);
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[DynamicItemForm] 드로잉 캔버스 → File 변환 실패:', error);

View File

@@ -120,7 +120,6 @@ function mapApiResponseToFormData(data: ItemApiResponse): DynamicFormData {
formData['is_active'] = true;
}
console.log('[ItemDetailEdit] mapApiResponseToFormData 결과:', formData);
return formData;
}
@@ -166,7 +165,6 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
queryParams.append('include_bom', 'true');
}
console.log('[ItemDetailEdit] Fetching:', { urlItemId, urlItemType, isMaterial });
const queryString = queryParams.toString();
const response = await fetch(`/api/proxy/items/${urlItemId}${queryString ? `?${queryString}` : ''}`);
@@ -185,15 +183,6 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
if (result.success && result.data) {
const apiData = result.data as ItemApiResponse;
console.log('========== [ItemDetailEdit] API 원본 데이터 (백엔드 응답) ==========');
console.log('id:', apiData.id);
console.log('specification:', apiData.specification);
console.log('unit:', apiData.unit);
console.log('is_active:', apiData.is_active);
console.log('files:', (apiData as any).files); // 파일 데이터 확인
console.log('전체:', apiData);
console.log('==============================================================');
// ID, 품목 유형 저장
// Product: product_type, Material: material_type 또는 type_code
setItemId(apiData.id);
@@ -202,13 +191,6 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
// 폼 데이터로 변환
const formData = mapApiResponseToFormData(apiData);
console.log('========== [ItemDetailEdit] 폼에 전달되는 initialData ==========');
console.log('specification:', formData['specification']);
console.log('unit:', formData['unit']);
console.log('is_active:', formData['is_active']);
console.log('files:', formData['files']); // 파일 데이터 확인
console.log('전체:', formData);
console.log('==========================================================');
setInitialData(formData);
// BOM 데이터 별도 API 호출 (expandBomItems로 품목 정보 포함)
@@ -238,7 +220,6 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
}));
setInitialBomLines(mappedBomLines);
console.log('[ItemDetailEdit] BOM 데이터 로드 (expanded):', mappedBomLines.length, '건', mappedBomLines);
}
} catch (bomErr) {
console.error('[ItemDetailEdit] BOM 조회 실패:', bomErr);
@@ -328,14 +309,6 @@ export function ItemDetailEdit({ itemCode, itemType: urlItemType, itemId: urlIte
}
// API 호출
console.log('========== [ItemDetailEdit] 수정 요청 데이터 ==========');
console.log('URL:', updateUrl);
console.log('Method:', method);
console.log('specification:', submitData.specification);
console.log('unit:', submitData.unit);
console.log('is_active:', submitData.is_active);
console.log('전체:', submitData);
console.log('=================================================');
const response = await fetch(updateUrl, {
method,

View File

@@ -26,9 +26,6 @@ function mapApiResponseToItemMaster(data: Record<string, unknown>): ItemMaster {
// details 객체 추출 (PT 부품의 상세 정보가 여기에 저장됨)
const details = (data.details || {}) as Record<string, unknown>;
console.log('[mapApiResponseToItemMaster] data.details:', data.details);
console.log('[mapApiResponseToItemMaster] details.part_type:', details.part_type);
console.log('[mapApiResponseToItemMaster] details.bending_details:', details.bending_details);
return {
id: String(data.id || ''),
@@ -169,7 +166,6 @@ export function ItemDetailView({ itemCode, itemType, itemId }: ItemDetailViewPro
try {
setIsLoading(true);
console.log('[ItemDetailView] Fetching item:', { itemCode, itemType, itemId });
// 모든 품목: GET /api/proxy/items/{id} (id 기반 통일)
if (!itemId) {
@@ -185,7 +181,6 @@ export function ItemDetailView({ itemCode, itemType, itemId }: ItemDetailViewPro
queryParams.append('include_bom', 'true');
}
console.log('[ItemDetailView] Fetching:', { itemId, itemType, isMaterial });
const queryString = queryParams.toString();
const response = await fetch(`/api/proxy/items/${itemId}${queryString ? `?${queryString}` : ''}`);
@@ -201,7 +196,6 @@ export function ItemDetailView({ itemCode, itemType, itemId }: ItemDetailViewPro
}
const result = await response.json();
console.log('[ItemDetailView] API Response:', result);
if (result.success && result.data) {
let mappedItem = mapApiResponseToItemMaster(result.data);
@@ -229,7 +223,6 @@ export function ItemDetailView({ itemCode, itemType, itemId }: ItemDetailViewPro
isBending: Boolean(bomItem.is_bending ?? false),
})),
};
console.log('[ItemDetailView] BOM 데이터 로드 (expanded):', mappedItem.bom?.length, '건');
}
} catch (bomErr) {
console.error('[ItemDetailView] BOM 조회 실패:', bomErr);

View File

@@ -154,13 +154,11 @@ export default function ItemListClient() {
if (!itemToDelete) return;
try {
console.log('[Delete] 삭제 요청:', itemToDelete);
// 2025-12-15: 백엔드 동적 테이블 라우팅 - 모든 품목이 /items 엔드포인트 사용
// /products/materials 라우트 삭제됨
const deleteUrl = `/api/proxy/items/${itemToDelete.id}?item_type=${itemToDelete.itemType}`;
console.log('[Delete] URL:', deleteUrl, '(itemType:', itemToDelete.itemType, ')');
const response = await fetch(deleteUrl, {
method: 'DELETE',
@@ -175,7 +173,6 @@ export default function ItemListClient() {
}
const result = await response.json();
console.log('[Delete] 응답:', { status: response.status, result });
if (result.success) {
refresh();
@@ -330,7 +327,6 @@ export default function ItemListClient() {
// TODO: 실제 API 호출로 데이터 저장
// 지금은 파싱 결과만 확인
console.log('[Excel Upload] 파싱 결과:', result.data);
alert(`${result.data.length}건의 데이터가 파싱되었습니다.\n(실제 등록 기능은 추후 구현 예정)`);
} catch (error) {

View File

@@ -55,7 +55,6 @@ export function DraggableField({ field, index, moveField, onDelete, onEdit }: Dr
const data = JSON.parse(e.dataTransfer.getData('application/json'));
// 2025-12-03: 타입 체크 - 필드 드래그만 처리
if (data.type !== 'field') {
console.log('[DraggableField] 필드 드래그가 아님, 무시:', data);
return;
}
if (data.id !== field.id) {

View File

@@ -462,25 +462,12 @@ export function FieldDialog({
<DialogFooter className="shrink-0 bg-white z-10 px-6 py-4 border-t">
<Button variant="outline" onClick={handleClose}></Button>
<Button onClick={async () => {
console.log('[FieldDialog] 🔵 저장 버튼 클릭!', {
fieldInputMode,
editingFieldId,
selectedMasterFieldId,
newFieldName,
newFieldKey,
isNameEmpty,
isKeyEmpty,
isKeyInvalid,
});
setIsSubmitted(true);
// 2025-11-28: field_key validation 추가
const shouldValidate = isCustomMode || editingFieldId;
console.log('[FieldDialog] 🔵 shouldValidate:', shouldValidate);
if (shouldValidate && (isNameEmpty || isKeyEmpty || isKeyInvalid)) {
console.log('[FieldDialog] ❌ 유효성 검사 실패로 return');
return;
}
console.log('[FieldDialog] ✅ handleAddField 호출');
await handleAddField();
setIsSubmitted(false);
}}></Button>

View File

@@ -47,7 +47,6 @@ export function useDeleteManagement({ itemPages }: UseDeleteManagementProps): Us
const sectionIds = pageToDelete?.sections.map(s => s.id) || [];
const fieldIds = pageToDelete?.sections.flatMap(s => s.fields?.map(f => f.id) || []) || [];
deleteItemPage(pageId);
console.log('페이지 삭제 완료:', { pageId, removedSections: sectionIds.length, removedFields: fieldIds.length });
}, [itemPages, deleteItemPage]);
// 섹션 삭제 핸들러
@@ -56,14 +55,12 @@ export function useDeleteManagement({ itemPages }: UseDeleteManagementProps): Us
const sectionToDelete = page?.sections.find(s => s.id === sectionId);
const fieldIds = sectionToDelete?.fields?.map(f => f.id) || [];
deleteSection(Number(sectionId));
console.log('섹션 삭제 완료:', { sectionId, removedFields: fieldIds.length });
}, [itemPages, deleteSection]);
// 필드 연결 해제 핸들러
const handleUnlinkField = useCallback(async (_pageId: string, sectionId: string, fieldId: string) => {
try {
await unlinkFieldFromSection(Number(sectionId), Number(fieldId));
console.log('필드 연결 해제 완료:', fieldId);
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('필드 연결 해제 실패:', error);
@@ -105,7 +102,6 @@ export function useDeleteManagement({ itemPages }: UseDeleteManagementProps): Us
setAttributeSubTabs([]);
console.log('🗑️ 모든 품목기준관리 데이터가 초기화되었습니다');
toast.success('✅ 모든 데이터가 초기화되었습니다!\n계층구조, 섹션, 항목, 속성이 모두 삭제되었습니다.');
setTimeout(() => {

View File

@@ -153,22 +153,8 @@ export function useFieldManagement(): UseFieldManagementReturn {
// 필드 추가 (2025-11-27: async/await 추가 - 다른 탭 실시간 동기화)
const handleAddField = async (selectedPage: ItemPage | undefined) => {
console.log('[useFieldManagement] 🟢 handleAddField 시작!', {
selectedPage: selectedPage?.id,
selectedSectionForField,
newFieldName,
newFieldKey,
fieldInputMode,
selectedMasterFieldId,
});
if (!selectedPage || !selectedSectionForField || !newFieldName.trim() || !newFieldKey.trim()) {
console.log('[useFieldManagement] ❌ 필수값 누락으로 return', {
selectedPage: !!selectedPage,
selectedSectionForField,
newFieldName: newFieldName.trim(),
newFieldKey: newFieldKey.trim(),
});
toast.error('모든 필수 항목을 입력해주세요');
return;
}
@@ -221,7 +207,6 @@ export function useFieldManagement(): UseFieldManagementReturn {
try {
if (editingFieldId) {
console.log('Updating field:', { pageId: selectedPage.id, sectionId: selectedSectionForField, fieldId: editingFieldId, fieldName: newField.field_name });
await updateField(Number(editingFieldId), newField);
// 항목관리 탭의 마스터 항목도 업데이트 (동일한 fieldKey가 있으면)
@@ -238,7 +223,6 @@ export function useFieldManagement(): UseFieldManagementReturn {
toast.success('항목이 섹션에 수정되었습니다!');
} else {
console.log('Adding field to section:', { pageId: selectedPage.id, sectionId: selectedSectionForField, fieldName: newField.field_name });
// 섹션에 항목 추가 (await로 완료 대기)
// 2025-11-27: addItemMasterField 호출 제거 - 중복 필드 생성 방지
@@ -256,8 +240,6 @@ export function useFieldManagement(): UseFieldManagementReturn {
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어, field_name 중복 등)
if (error instanceof ApiError) {
console.log('🔍 ApiError.errors:', error.errors); // 디버깅용
// errors 객체에서 첫 번째 에러 메시지 추출 → AlertDialog로 표시
if (error.errors && Object.keys(error.errors).length > 0) {
const firstKey = Object.keys(error.errors)[0];
@@ -306,7 +288,6 @@ export function useFieldManagement(): UseFieldManagementReturn {
// 필드 삭제
const handleDeleteField = (pageId: string, sectionId: string, fieldId: string) => {
deleteField(Number(fieldId));
console.log('필드 삭제 완료:', fieldId);
};
// 폼 초기화

View File

@@ -62,7 +62,6 @@ export function useInitialDataLoading({
// Context와 병행 운영 - 점진적 마이그레이션
try {
await initFromApi();
console.log('✅ [Zustand] Store initialized');
} catch (zustandError) {
// Zustand 초기화 실패해도 Context로 fallback
console.warn('⚠️ [Zustand] Init failed, falling back to Context:', zustandError);
@@ -78,7 +77,6 @@ export function useInitialDataLoading({
if (data.sections && data.sections.length > 0) {
const transformedSections = transformSectionsResponse(data.sections);
loadIndependentSections(transformedSections);
console.log('✅ 독립 섹션 로드:', transformedSections.length);
}
// 3. 섹션 템플릿 로드
@@ -106,10 +104,6 @@ export function useInitialDataLoading({
loadItemMasterFields(transformedFields as any);
loadIndependentFields(independentOnlyFields);
console.log('✅ 필드 로드:', {
total: transformedFields.length,
independent: independentOnlyFields.length,
});
}
// 5. 커스텀 탭 로드
@@ -124,13 +118,6 @@ export function useInitialDataLoading({
setUnitOptions(transformedUnits);
}
console.log('✅ Initial data loaded:', {
pages: data.pages?.length || 0,
sections: data.sections?.length || 0,
fields: data.fields?.length || 0,
customTabs: data.customTabs?.length || 0,
unitOptions: data.unitOptions?.length || 0,
});
} catch (err) {
if (err instanceof ApiError && err.errors) {

View File

@@ -127,8 +127,6 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn {
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어) → AlertDialog로 표시
if (error instanceof ApiError) {
console.log('🔍 ApiError.errors:', error.errors); // 디버깅용
// errors 객체에서 첫 번째 에러 메시지 추출
if (error.errors && Object.keys(error.errors).length > 0) {
const firstKey = Object.keys(error.errors)[0];
@@ -202,8 +200,6 @@ export function useMasterFieldManagement(): UseMasterFieldManagementReturn {
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어, field_name 중복 등) → AlertDialog로 표시
if (error instanceof ApiError) {
console.log('🔍 ApiError.errors:', error.errors); // 디버깅용
// errors 객체에서 첫 번째 에러 메시지 추출
if (error.errors && Object.keys(error.errors).length > 0) {
const firstKey = Object.keys(error.errors)[0];

View File

@@ -80,7 +80,6 @@ export function usePageManagement(): UsePageManagementReturn {
migrationDoneRef.current.add(page.id);
});
console.log(`절대경로가 자동으로 생성되었습니다 (${pagesToMigrate.length}개 페이지)`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemPages.length]); // itemPages 길이가 변경될 때만 체크
@@ -180,11 +179,6 @@ export function usePageManagement(): UsePageManagementReturn {
setSelectedPageId(remainingPages[0]?.id || null);
}
console.log('페이지 삭제 완료:', {
pageId,
sectionsMovedToIndependent: sectionCount,
fieldsPreserved: fieldCount
});
};
// 페이지명 수정

View File

@@ -85,13 +85,6 @@ export function useSectionManagement(): UseSectionManagementReturn {
bom_items: sectionType === 'BOM' ? [] : undefined
};
console.log('Adding section to page:', {
pageId: selectedPage.id,
page_name: selectedPage.page_name,
sectionTitle: newSection.title,
sectionType: newSection.section_type,
currentSectionCount: selectedPage.sections.length,
});
try {
// 페이지에 섹션 추가 (API 호출)
@@ -99,9 +92,6 @@ export function useSectionManagement(): UseSectionManagementReturn {
// 별도의 addSectionTemplate 호출 불필요 (자동 동기화)
await addSectionToPage(selectedPage.id, newSection);
console.log('Section added to page:', {
sectionTitle: newSection.title
});
resetSectionForm();
toast.success(`${newSectionType === 'bom' ? 'BOM' : '일반'} 섹션이 추가되었습니다!`);
@@ -127,12 +117,6 @@ export function useSectionManagement(): UseSectionManagementReturn {
return;
}
console.log('Linking existing section to page:', {
sectionId: template.id,
sectionName: template.template_name,
pageId: selectedPage.id,
orderNo: selectedPage.sections.length + 1,
});
try {
// 기존 섹션을 페이지에 연결 (entity_relationships에 레코드 추가)
@@ -176,7 +160,6 @@ export function useSectionManagement(): UseSectionManagementReturn {
const handleUnlinkSection = async (pageId: number, sectionId: number) => {
try {
await unlinkSectionFromPage(pageId, sectionId);
console.log('섹션 연결 해제 완료:', { pageId, sectionId });
toast.success('섹션 연결이 해제되었습니다');
} catch (error) {
if (isNextRedirectError(error)) throw error;
@@ -193,10 +176,6 @@ export function useSectionManagement(): UseSectionManagementReturn {
try {
await deleteSection(sectionId);
console.log('섹션 삭제 완료:', {
sectionId,
removedFields: fieldIds.length
});
toast.success('섹션이 삭제되었습니다!');
} catch (error) {
if (isNextRedirectError(error)) throw error;

View File

@@ -174,7 +174,6 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
is_default: false,
};
console.log('Adding independent section (from section tab):', newSectionData);
try {
await createIndependentSection(newSectionData);
@@ -211,7 +210,6 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
section_type: (newSectionTemplateType === 'bom' ? 'BOM' : 'BASIC') as 'BASIC' | 'BOM' | 'CUSTOM'
};
console.log('Updating section (from template handler):', { id: editingSectionTemplateId, updateData });
try {
// updateSection 호출 (템플릿이 아닌 실제 섹션 API)
await updateSection(editingSectionTemplateId, updateData);
@@ -266,7 +264,6 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
bom_items: template.section_type === 'BOM' ? [] : undefined
};
console.log('Loading template to section:', template.template_name, 'newSection:', newSection);
addSectionToPage(selectedPage.id, newSection);
setSelectedTemplateId(null);
setIsLoadTemplateDialogOpen(false);
@@ -365,8 +362,6 @@ export function useTemplateManagement(): UseTemplateManagementReturn {
// 422 ValidationException 상세 메시지 처리 (field_key 중복/예약어, field_name 중복 등) → AlertDialog로 표시
if (error instanceof ApiError) {
console.log('🔍 ApiError.errors:', error.errors); // 디버깅용
// errors 객체에서 첫 번째 에러 메시지 추출
if (error.errors && Object.keys(error.errors).length > 0) {
const firstKey = Object.keys(error.errors)[0];

View File

@@ -1,5 +1,5 @@
import type { Dispatch, SetStateAction } from 'react';
import type { ItemPage, ItemSection, BOMItem } from '@/contexts/ItemMasterContext';
import type { ItemPage, ItemSection, ItemField, BOMItem } from '@/contexts/ItemMasterContext';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -33,12 +33,12 @@ interface HierarchyTabProps {
setEditingSectionTitle: (title: string) => void;
hasUnsavedChanges: boolean;
pendingChanges: {
pages: any[];
sections: any[];
fields: any[];
masterFields: any[];
attributes: any[];
sectionTemplates: any[];
pages: Record<string, unknown>[];
sections: Record<string, unknown>[];
fields: Record<string, unknown>[];
masterFields: Record<string, unknown>[];
attributes: Record<string, unknown>[];
sectionTemplates: Record<string, unknown>[];
};
selectedSectionForField: number | null;
setSelectedSectionForField: (id: number | null) => void;
@@ -46,8 +46,8 @@ interface HierarchyTabProps {
setNewSectionType: Dispatch<SetStateAction<'fields' | 'bom'>>;
// Functions
updateItemPage: (id: number, data: any) => void;
trackChange: (type: 'pages' | 'sections' | 'fields' | 'masterFields' | 'attributes' | 'sectionTemplates', id: string, action: 'add' | 'update', data: any, attributeType?: string) => void;
updateItemPage: (id: number, data: Partial<ItemPage>) => void;
trackChange: (type: 'pages' | 'sections' | 'fields' | 'masterFields' | 'attributes' | 'sectionTemplates', id: string, action: 'add' | 'update', data: Record<string, unknown>, attributeType?: string) => void;
deleteItemPage: (id: number) => void;
duplicatePage: (id: number) => void;
setIsPageDialogOpen: (open: boolean) => void;
@@ -59,7 +59,7 @@ interface HierarchyTabProps {
unlinkSection: (pageId: number, sectionId: number) => void; // 연결 해제 (삭제 아님)
updateSection: (sectionId: number, updates: Partial<ItemSection>) => Promise<void>;
deleteField: (pageId: string, sectionId: string, fieldId: string) => void; // 2025-11-27: 연결 해제로 변경 (삭제 아님, 항목 탭에 유지)
handleEditField: (sectionId: string, field: any) => void;
handleEditField: (sectionId: string, field: ItemField) => void;
// 2025-12-03: ID 기반으로 변경 (index stale 문제 해결)
moveField: (sectionId: number, dragFieldId: number, hoverFieldId: number) => void | Promise<void>;
// 2025-11-26 추가: 섹션/필드 불러오기
@@ -372,7 +372,6 @@ export function HierarchyTab({
bomItems={section.bom_items || []}
onAddItem={async (item) => {
// 2025-11-27: API 함수로 BOM 항목 추가
console.log('[HierarchyTab] BOM 추가 시작:', { sectionId: section.id, item, addBOMItemExists: !!addBOMItem });
if (addBOMItem) {
try {
await addBOMItem(section.id, {
@@ -383,7 +382,6 @@ export function HierarchyTab({
unit: item.unit,
spec: item.spec,
});
console.log('[HierarchyTab] BOM 추가 성공');
toast.success('BOM 항목이 추가되었습니다');
} catch (error) {
if (isNextRedirectError(error)) throw error;

View File

@@ -71,14 +71,6 @@ export function SectionsTab({
}: SectionsTabProps) {
// 2025-11-27: prop 변경 추적 (디버깅용)
useEffect(() => {
console.log('[SectionsTab] 📥 sectionTemplates prop changed:', {
count: sectionTemplates.length,
sections: sectionTemplates.map(s => ({
id: s.id,
name: s.template_name,
fieldsCount: s.fields?.length || 0
}))
});
}, [sectionTemplates]);
return (
@@ -118,16 +110,6 @@ export function SectionsTab({
{/* 일반 섹션 탭 */}
<TabsContent value="general">
{(() => {
console.log('[SectionsTab] 🔄 Rendering section templates:', {
totalTemplates: sectionTemplates.length,
generalTemplates: sectionTemplates.filter(t => t.section_type !== 'BOM').length,
templates: sectionTemplates.map(t => ({
id: t.id,
template_name: t.template_name,
section_type: t.section_type,
fieldsCount: t.fields?.length || 0 // 필드 개수 추가
}))
});
return null;
})()}
{sectionTemplates.filter(t => t.section_type !== 'BOM').length === 0 ? (