refactor: 코드 정리 및 불필요한 주석 제거
- EntityJoinController에서 중복 제거 설정 관련 주석 및 코드 삭제 - screenGroupController와 tableManagementController에서 AuthenticatedRequest 타입을 일반 Request로 변경 - 불필요한 로그 및 주석 제거로 코드 가독성 향상 - tableManagementController에서 에러 메시지 개선
This commit is contained in:
@@ -6,7 +6,6 @@ import { WebType } from "@/types/common";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { codeCache } from "@/lib/caching/codeCache";
|
||||
import { getCategoryLabelsByCodes } from "@/lib/api/tableCategoryValue";
|
||||
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
|
||||
import { getFullImageUrl } from "@/lib/api/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -67,7 +66,6 @@ import { useAuth } from "@/hooks/useAuth";
|
||||
import { useScreenContextOptional } from "@/contexts/ScreenContext";
|
||||
import { useSplitPanelContext, SplitPanelPosition } from "@/contexts/SplitPanelContext";
|
||||
import type { DataProvidable, DataReceivable, DataReceiverConfig } from "@/types/data-transfer";
|
||||
import { useScreenMultiLang } from "@/contexts/ScreenMultiLangContext";
|
||||
|
||||
// ========================================
|
||||
// 인터페이스
|
||||
@@ -244,11 +242,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
parentTabsComponentId,
|
||||
companyCode,
|
||||
}) => {
|
||||
// ========================================
|
||||
// 다국어 번역 훅
|
||||
// ========================================
|
||||
const { getTranslatedText } = useScreenMultiLang();
|
||||
|
||||
// ========================================
|
||||
// 설정 및 스타일
|
||||
// ========================================
|
||||
@@ -481,7 +474,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
}
|
||||
|
||||
// 2. 헤더 필터 적용 (joinColumnMapping 사용 안 함 - 직접 컬럼명 사용)
|
||||
// 🆕 다중 값 지원: 셀 값이 "A,B,C" 형태일 때, 필터에서 "A"를 선택하면 해당 행도 표시
|
||||
if (Object.keys(headerFilters).length > 0) {
|
||||
result = result.filter((row) => {
|
||||
return Object.entries(headerFilters).every(([columnName, values]) => {
|
||||
@@ -491,16 +483,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const cellValue = row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()];
|
||||
const cellStr = cellValue !== null && cellValue !== undefined ? String(cellValue) : "";
|
||||
|
||||
// 정확히 일치하는 경우
|
||||
if (values.has(cellStr)) return true;
|
||||
|
||||
// 다중 값인 경우: 콤마로 분리해서 하나라도 포함되면 true
|
||||
if (cellStr.includes(",")) {
|
||||
const cellValues = cellStr.split(",").map(v => v.trim());
|
||||
return cellValues.some(v => values.has(v));
|
||||
}
|
||||
|
||||
return false;
|
||||
return values.has(cellStr);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -871,55 +854,17 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
};
|
||||
|
||||
// 화면 컨텍스트에 데이터 제공자/수신자로 등록
|
||||
// 🔧 dataProvider와 dataReceiver를 의존성에 포함하지 않고,
|
||||
// 대신 data와 selectedRows가 변경될 때마다 재등록하여 최신 클로저 참조
|
||||
useEffect(() => {
|
||||
if (screenContext && component.id) {
|
||||
// 🔧 매번 새로운 dataProvider를 등록하여 최신 selectedRows 참조
|
||||
const currentDataProvider: DataProvidable = {
|
||||
componentId: component.id,
|
||||
componentType: "table-list",
|
||||
getSelectedData: () => {
|
||||
const selectedData = filteredData.filter((row) => {
|
||||
const rowId = String(row.id || row[tableConfig.selectedTable + "_id"] || "");
|
||||
return selectedRows.has(rowId);
|
||||
});
|
||||
console.log("📊 [TableList] getSelectedData 호출:", {
|
||||
componentId: component.id,
|
||||
selectedRowsSize: selectedRows.size,
|
||||
filteredDataLength: filteredData.length,
|
||||
resultLength: selectedData.length,
|
||||
});
|
||||
return selectedData;
|
||||
},
|
||||
getAllData: () => filteredData,
|
||||
clearSelection: () => {
|
||||
setSelectedRows(new Set());
|
||||
setIsAllSelected(false);
|
||||
},
|
||||
};
|
||||
|
||||
const currentDataReceiver: DataReceivable = {
|
||||
componentId: component.id,
|
||||
componentType: "table",
|
||||
receiveData: dataReceiver.receiveData,
|
||||
getData: () => data,
|
||||
};
|
||||
|
||||
screenContext.registerDataProvider(component.id, currentDataProvider);
|
||||
screenContext.registerDataReceiver(component.id, currentDataReceiver);
|
||||
|
||||
console.log("✅ [TableList] ScreenContext에 등록:", {
|
||||
componentId: component.id,
|
||||
selectedRowsSize: selectedRows.size,
|
||||
});
|
||||
screenContext.registerDataProvider(component.id, dataProvider);
|
||||
screenContext.registerDataReceiver(component.id, dataReceiver);
|
||||
|
||||
return () => {
|
||||
screenContext.unregisterDataProvider(component.id);
|
||||
screenContext.unregisterDataReceiver(component.id);
|
||||
};
|
||||
}
|
||||
}, [screenContext, component.id, data, selectedRows, filteredData, tableConfig.selectedTable]);
|
||||
}, [screenContext, component.id, data, selectedRows]);
|
||||
|
||||
// 분할 패널 컨텍스트에 데이터 수신자로 등록
|
||||
// useSplitPanelPosition 훅으로 위치 가져오기 (중첩된 화면에서도 작동)
|
||||
@@ -1086,16 +1031,14 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
onGroupSumChange: setGroupSumConfig, // 그룹별 합산 설정
|
||||
// 틀고정 컬럼 관련
|
||||
frozenColumnCount, // 현재 틀고정 컬럼 수
|
||||
onFrozenColumnCountChange: (count: number, updatedColumns?: Array<{ columnName: string; visible: boolean }>) => {
|
||||
onFrozenColumnCountChange: (count: number) => {
|
||||
setFrozenColumnCount(count);
|
||||
// 체크박스 컬럼은 항상 틀고정에 포함
|
||||
const checkboxColumn = (tableConfig.checkbox?.enabled ?? true) ? ["__checkbox__"] : [];
|
||||
// 표시 가능한 컬럼 중 처음 N개를 틀고정 컬럼으로 설정
|
||||
// updatedColumns가 전달되면 그것을 사용, 아니면 columnsToRegister 사용
|
||||
const colsToUse = updatedColumns || columnsToRegister;
|
||||
const visibleCols = colsToUse
|
||||
const visibleCols = columnsToRegister
|
||||
.filter((col) => col.visible !== false)
|
||||
.map((col) => col.columnName || (col as any).field);
|
||||
.map((col) => col.columnName || col.field);
|
||||
const newFrozenColumns = [...checkboxColumn, ...visibleCols.slice(0, count)];
|
||||
setFrozenColumns(newFrozenColumns);
|
||||
},
|
||||
@@ -2106,7 +2049,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return row.id || row.uuid || `row-${index}`;
|
||||
};
|
||||
|
||||
const handleRowSelection = (rowKey: string, checked: boolean, rowData?: any) => {
|
||||
const handleRowSelection = (rowKey: string, checked: boolean) => {
|
||||
const newSelectedRows = new Set(selectedRows);
|
||||
if (checked) {
|
||||
newSelectedRows.add(rowKey);
|
||||
@@ -2149,31 +2092,6 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
// 🆕 분할 패널 컨텍스트에 선택된 데이터 저장/해제 (체크박스 선택 시에도 작동)
|
||||
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (checked && selectedRowsData.length > 0) {
|
||||
// 선택된 경우: 첫 번째 선택된 데이터 저장 (또는 전달된 rowData)
|
||||
const dataToStore = rowData || selectedRowsData[selectedRowsData.length - 1];
|
||||
splitPanelContext.setSelectedLeftData(dataToStore);
|
||||
console.log("🔗 [TableList] handleRowSelection - 분할 패널 좌측 데이터 저장:", {
|
||||
rowKey,
|
||||
dataToStore,
|
||||
});
|
||||
} else if (!checked && selectedRowsData.length === 0) {
|
||||
// 모든 선택이 해제된 경우: 데이터 초기화
|
||||
splitPanelContext.setSelectedLeftData(null);
|
||||
console.log("🔗 [TableList] handleRowSelection - 분할 패널 좌측 데이터 초기화");
|
||||
} else if (selectedRowsData.length > 0) {
|
||||
// 일부 선택 해제된 경우: 남은 첫 번째 데이터로 업데이트
|
||||
splitPanelContext.setSelectedLeftData(selectedRowsData[0]);
|
||||
console.log("🔗 [TableList] handleRowSelection - 분할 패널 좌측 데이터 업데이트:", {
|
||||
remainingCount: selectedRowsData.length,
|
||||
firstData: selectedRowsData[0],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const allRowsSelected = filteredData.every((row, index) => newSelectedRows.has(getRowKey(row, index)));
|
||||
setIsAllSelected(allRowsSelected && filteredData.length > 0);
|
||||
};
|
||||
@@ -2243,8 +2161,35 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const rowKey = getRowKey(row, index);
|
||||
const isCurrentlySelected = selectedRows.has(rowKey);
|
||||
|
||||
// handleRowSelection에서 분할 패널 데이터 처리도 함께 수행됨
|
||||
handleRowSelection(rowKey, !isCurrentlySelected, row);
|
||||
handleRowSelection(rowKey, !isCurrentlySelected);
|
||||
|
||||
// 🆕 분할 패널 컨텍스트에 선택된 데이터 저장 (좌측 화면인 경우)
|
||||
// disableAutoDataTransfer가 true이면 자동 전달 비활성화 (버튼 클릭으로만 전달)
|
||||
// currentSplitPosition을 사용하여 정확한 위치 확인 (splitPanelPosition이 없을 수 있음)
|
||||
const effectiveSplitPosition = splitPanelPosition || currentSplitPosition;
|
||||
|
||||
console.log("🔗 [TableList] 행 클릭 - 분할 패널 위치 확인:", {
|
||||
splitPanelPosition,
|
||||
currentSplitPosition,
|
||||
effectiveSplitPosition,
|
||||
hasSplitPanelContext: !!splitPanelContext,
|
||||
disableAutoDataTransfer: splitPanelContext?.disableAutoDataTransfer,
|
||||
});
|
||||
|
||||
if (splitPanelContext && effectiveSplitPosition === "left" && !splitPanelContext.disableAutoDataTransfer) {
|
||||
if (!isCurrentlySelected) {
|
||||
// 선택된 경우: 데이터 저장
|
||||
splitPanelContext.setSelectedLeftData(row);
|
||||
console.log("🔗 [TableList] 분할 패널 좌측 데이터 저장:", {
|
||||
row,
|
||||
parentDataMapping: splitPanelContext.parentDataMapping,
|
||||
});
|
||||
} else {
|
||||
// 선택 해제된 경우: 데이터 초기화
|
||||
splitPanelContext.setSelectedLeftData(null);
|
||||
console.log("🔗 [TableList] 분할 패널 좌측 데이터 초기화");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("행 클릭:", { row, index, isSelected: !isCurrentlySelected });
|
||||
};
|
||||
@@ -2311,176 +2256,30 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 🆕 편집 모드 진입 placeholder (실제 구현은 visibleColumns 정의 후)
|
||||
const startEditingRef = useRef<() => void>(() => {});
|
||||
|
||||
// 🆕 카테고리 라벨 매핑 (API에서 가져온 것)
|
||||
const [categoryLabelCache, setCategoryLabelCache] = useState<Record<string, string>>({});
|
||||
|
||||
// 🆕 각 컬럼의 고유값 목록 계산 (라벨 포함)
|
||||
// 🆕 각 컬럼의 고유값 목록 계산
|
||||
const columnUniqueValues = useMemo(() => {
|
||||
const result: Record<string, Array<{ value: string; label: string }>> = {};
|
||||
const result: Record<string, string[]> = {};
|
||||
|
||||
if (data.length === 0) return result;
|
||||
|
||||
// 🆕 전체 데이터에서 개별 값 -> 라벨 매핑 테이블 구축 (다중 값 처리용)
|
||||
const globalLabelMap: Record<string, Map<string, string>> = {};
|
||||
|
||||
(tableConfig.columns || []).forEach((column: { columnName: string }) => {
|
||||
if (column.columnName === "__checkbox__") return;
|
||||
|
||||
const mappedColumnName = joinColumnMapping[column.columnName] || column.columnName;
|
||||
// 라벨 컬럼 후보들 (백엔드에서 _name, _label, _value_label 등으로 반환할 수 있음)
|
||||
const labelColumnCandidates = [
|
||||
`${column.columnName}_name`, // 예: division_name
|
||||
`${column.columnName}_label`, // 예: division_label
|
||||
`${column.columnName}_value_label`, // 예: division_value_label
|
||||
];
|
||||
const valuesMap = new Map<string, string>(); // value -> label
|
||||
const singleValueLabelMap = new Map<string, string>(); // 개별 값 -> 라벨 (다중값 처리용)
|
||||
const values = new Set<string>();
|
||||
|
||||
// 1차: 모든 데이터에서 개별 값 -> 라벨 매핑 수집 (단일값 + 다중값 모두)
|
||||
data.forEach((row) => {
|
||||
const val = row[mappedColumnName];
|
||||
if (val !== null && val !== undefined && val !== "") {
|
||||
const valueStr = String(val);
|
||||
|
||||
// 라벨 컬럼에서 라벨 찾기
|
||||
let labelStr = "";
|
||||
for (const labelCol of labelColumnCandidates) {
|
||||
if (row[labelCol] && row[labelCol] !== "") {
|
||||
labelStr = String(row[labelCol]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 단일 값인 경우
|
||||
if (!valueStr.includes(",")) {
|
||||
if (labelStr) {
|
||||
singleValueLabelMap.set(valueStr, labelStr);
|
||||
}
|
||||
} else {
|
||||
// 다중 값인 경우: 값과 라벨을 각각 분리해서 매핑
|
||||
const individualValues = valueStr.split(",").map(v => v.trim());
|
||||
const individualLabels = labelStr ? labelStr.split(",").map(l => l.trim()) : [];
|
||||
|
||||
// 값과 라벨 개수가 같으면 1:1 매핑
|
||||
if (individualValues.length === individualLabels.length) {
|
||||
individualValues.forEach((v, idx) => {
|
||||
if (individualLabels[idx] && !singleValueLabelMap.has(v)) {
|
||||
singleValueLabelMap.set(v, individualLabels[idx]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
values.add(String(val));
|
||||
}
|
||||
});
|
||||
|
||||
// 2차: 모든 값 처리 (다중 값 포함) - 필터 목록용
|
||||
data.forEach((row) => {
|
||||
const val = row[mappedColumnName];
|
||||
if (val !== null && val !== undefined && val !== "") {
|
||||
const valueStr = String(val);
|
||||
|
||||
// 콤마로 구분된 다중 값인지 확인
|
||||
if (valueStr.includes(",")) {
|
||||
// 다중 값: 각각 분리해서 개별 라벨 찾기
|
||||
const individualValues = valueStr.split(",").map(v => v.trim());
|
||||
// 🆕 singleValueLabelMap → categoryLabelCache 순으로 라벨 찾기
|
||||
const individualLabels = individualValues.map(v =>
|
||||
singleValueLabelMap.get(v) || categoryLabelCache[v] || v
|
||||
);
|
||||
valuesMap.set(valueStr, individualLabels.join(", "));
|
||||
} else {
|
||||
// 단일 값: 매핑에서 찾거나 캐시에서 찾거나 원본 사용
|
||||
const label = singleValueLabelMap.get(valueStr) || categoryLabelCache[valueStr] || valueStr;
|
||||
valuesMap.set(valueStr, label);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalLabelMap[column.columnName] = singleValueLabelMap;
|
||||
|
||||
// value-label 쌍으로 저장하고 라벨 기준 정렬
|
||||
result[column.columnName] = Array.from(valuesMap.entries())
|
||||
.map(([value, label]) => ({ value, label }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
result[column.columnName] = Array.from(values).sort();
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [data, tableConfig.columns, joinColumnMapping, categoryLabelCache]);
|
||||
|
||||
// 🆕 라벨을 못 찾은 CATEGORY_ 코드들을 API로 조회
|
||||
useEffect(() => {
|
||||
const unlabeledCodes = new Set<string>();
|
||||
|
||||
// columnUniqueValues에서 라벨이 코드 그대로인 항목 찾기
|
||||
Object.values(columnUniqueValues).forEach(items => {
|
||||
items.forEach(item => {
|
||||
// 라벨에 CATEGORY_가 포함되어 있으면 라벨을 못 찾은 것
|
||||
if (item.label.includes("CATEGORY_")) {
|
||||
// 콤마로 분리해서 개별 코드 추출
|
||||
const codes = item.label.split(",").map(c => c.trim());
|
||||
codes.forEach(code => {
|
||||
if (code.startsWith("CATEGORY_") && !categoryLabelCache[code]) {
|
||||
unlabeledCodes.add(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (unlabeledCodes.size === 0) return;
|
||||
|
||||
// API로 라벨 조회
|
||||
const fetchLabels = async () => {
|
||||
try {
|
||||
const response = await getCategoryLabelsByCodes(Array.from(unlabeledCodes));
|
||||
if (response.success && response.data) {
|
||||
setCategoryLabelCache(prev => ({ ...prev, ...response.data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("카테고리 라벨 조회 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLabels();
|
||||
}, [columnUniqueValues, categoryLabelCache]);
|
||||
|
||||
// 🆕 데이터에서 CATEGORY_ 코드를 찾아 라벨 미리 로드 (테이블 셀 렌더링용)
|
||||
useEffect(() => {
|
||||
if (data.length === 0) return;
|
||||
|
||||
const categoryCodesToFetch = new Set<string>();
|
||||
|
||||
// 모든 데이터 행에서 CATEGORY_ 코드 수집
|
||||
data.forEach((row) => {
|
||||
Object.entries(row).forEach(([key, value]) => {
|
||||
if (value && typeof value === "string") {
|
||||
// 콤마로 구분된 다중 값도 처리
|
||||
const codes = value.split(",").map((v) => v.trim());
|
||||
codes.forEach((code) => {
|
||||
if (code.startsWith("CATEGORY_") && !categoryLabelCache[code]) {
|
||||
categoryCodesToFetch.add(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (categoryCodesToFetch.size === 0) return;
|
||||
|
||||
// API로 라벨 조회
|
||||
const fetchLabels = async () => {
|
||||
try {
|
||||
const response = await getCategoryLabelsByCodes(Array.from(categoryCodesToFetch));
|
||||
if (response.success && response.data && Object.keys(response.data).length > 0) {
|
||||
setCategoryLabelCache((prev) => ({ ...prev, ...response.data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("CATEGORY_ 라벨 조회 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLabels();
|
||||
}, [data, categoryLabelCache]);
|
||||
}, [data, tableConfig.columns, joinColumnMapping]);
|
||||
|
||||
// 🆕 헤더 필터 토글
|
||||
const toggleHeaderFilter = useCallback((columnName: string, value: string) => {
|
||||
@@ -4125,7 +3924,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
if (enterRow) {
|
||||
const rowKey = getRowKey(enterRow, rowIndex);
|
||||
const isCurrentlySelected = selectedRows.has(rowKey);
|
||||
handleRowSelection(rowKey, !isCurrentlySelected, enterRow);
|
||||
handleRowSelection(rowKey, !isCurrentlySelected);
|
||||
}
|
||||
break;
|
||||
case " ": // Space
|
||||
@@ -4135,7 +3934,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
if (spaceRow) {
|
||||
const currentRowKey = getRowKey(spaceRow, rowIndex);
|
||||
const isChecked = selectedRows.has(currentRowKey);
|
||||
handleRowSelection(currentRowKey, !isChecked, spaceRow);
|
||||
handleRowSelection(currentRowKey, !isChecked);
|
||||
}
|
||||
break;
|
||||
case "F2":
|
||||
@@ -4349,7 +4148,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
return (
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
onCheckedChange={(checked) => handleRowSelection(rowKey, checked as boolean, row)}
|
||||
onCheckedChange={(checked) => handleRowSelection(rowKey, checked as boolean)}
|
||||
aria-label={`행 ${index + 1} 선택`}
|
||||
/>
|
||||
);
|
||||
@@ -4638,36 +4437,10 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
case "boolean":
|
||||
return value ? "예" : "아니오";
|
||||
default:
|
||||
// 🆕 CATEGORY_ 코드 자동 변환 (inputType이 category가 아니어도)
|
||||
const strValue = String(value);
|
||||
if (strValue.startsWith("CATEGORY_")) {
|
||||
// rowData에서 _label 필드 찾기
|
||||
if (rowData) {
|
||||
const labelFieldCandidates = [
|
||||
`${column.columnName}_label`,
|
||||
`${column.columnName}_name`,
|
||||
`${column.columnName}_value_label`,
|
||||
];
|
||||
for (const labelField of labelFieldCandidates) {
|
||||
if (rowData[labelField] && rowData[labelField] !== "") {
|
||||
return String(rowData[labelField]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// categoryMappings에서 찾기
|
||||
const mapping = categoryMappings[column.columnName];
|
||||
if (mapping && mapping[strValue]) {
|
||||
return mapping[strValue].label;
|
||||
}
|
||||
// categoryLabelCache에서 찾기 (필터용 캐시)
|
||||
if (categoryLabelCache[strValue]) {
|
||||
return categoryLabelCache[strValue];
|
||||
}
|
||||
}
|
||||
return strValue;
|
||||
return String(value);
|
||||
}
|
||||
},
|
||||
[columnMeta, joinedColumnMeta, optimizedConvertCode, categoryMappings, categoryLabelCache],
|
||||
[columnMeta, joinedColumnMeta, optimizedConvertCode, categoryMappings],
|
||||
);
|
||||
|
||||
// ========================================
|
||||
@@ -4806,22 +4579,9 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
});
|
||||
setColumnWidths(newWidths);
|
||||
|
||||
// 틀고정 컬럼 업데이트 (보이는 컬럼 기준으로 처음 N개를 틀고정)
|
||||
// 기존 frozen 개수를 유지하면서, 숨겨진 컬럼을 제외한 보이는 컬럼 중 처음 N개를 틀고정
|
||||
const checkboxColumn = (tableConfig.checkbox?.enabled ?? true) ? ["__checkbox__"] : [];
|
||||
const visibleCols = config.columns
|
||||
.filter((col) => col.visible && col.columnName !== "__checkbox__")
|
||||
.map((col) => col.columnName);
|
||||
|
||||
// 현재 설정된 frozen 컬럼 개수 (체크박스 제외)
|
||||
const currentFrozenCount = config.columns.filter(
|
||||
(col) => col.frozen && col.columnName !== "__checkbox__"
|
||||
).length;
|
||||
|
||||
// 보이는 컬럼 중 처음 currentFrozenCount개를 틀고정으로 설정
|
||||
const newFrozenColumns = [...checkboxColumn, ...visibleCols.slice(0, currentFrozenCount)];
|
||||
// 틀고정 컬럼 업데이트
|
||||
const newFrozenColumns = config.columns.filter((col) => col.frozen).map((col) => col.columnName);
|
||||
setFrozenColumns(newFrozenColumns);
|
||||
setFrozenColumnCount(currentFrozenCount);
|
||||
|
||||
// 그리드선 표시 업데이트
|
||||
setShowGridLines(config.showGridLines);
|
||||
@@ -5865,10 +5625,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
rowSpan={2}
|
||||
className="border-primary/10 border-r px-2 py-1 text-center text-xs font-semibold sm:px-4 sm:text-sm"
|
||||
>
|
||||
{/* langKey가 있으면 다국어 번역 사용 */}
|
||||
{(column as any).langKey
|
||||
? getTranslatedText((column as any).langKey, columnLabels[column.columnName] || column.columnName)
|
||||
: columnLabels[column.columnName] || column.columnName}
|
||||
{columnLabels[column.columnName] || column.columnName}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
@@ -5887,18 +5644,13 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
{visibleColumns.map((column, columnIndex) => {
|
||||
const columnWidth = columnWidths[column.columnName];
|
||||
const isFrozen = frozenColumns.includes(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산 (보이는 컬럼 기준으로 계산)
|
||||
// 숨겨진 컬럼은 제외하고 보이는 틀고정 컬럼만 포함
|
||||
const visibleFrozenColumns = visibleColumns
|
||||
.filter(col => frozenColumns.includes(col.columnName))
|
||||
.map(col => col.columnName);
|
||||
const frozenIndex = visibleFrozenColumns.indexOf(column.columnName);
|
||||
|
||||
const frozenIndex = frozenColumns.indexOf(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산
|
||||
let leftPosition = 0;
|
||||
if (isFrozen && frozenIndex > 0) {
|
||||
for (let i = 0; i < frozenIndex; i++) {
|
||||
const frozenCol = visibleFrozenColumns[i];
|
||||
const frozenCol = frozenColumns[i];
|
||||
// 체크박스 컬럼은 48px 고정
|
||||
const frozenColWidth = frozenCol === "__checkbox__" ? 48 : columnWidths[frozenCol] || 150;
|
||||
leftPosition += frozenColWidth;
|
||||
@@ -5964,12 +5716,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
<Lock className="text-muted-foreground h-3 w-3" />
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{/* langKey가 있으면 다국어 번역 사용 */}
|
||||
{(column as any).langKey
|
||||
? getTranslatedText((column as any).langKey, columnLabels[column.columnName] || column.displayName || column.columnName)
|
||||
: columnLabels[column.columnName] || column.displayName}
|
||||
</span>
|
||||
<span>{columnLabels[column.columnName] || column.displayName}</span>
|
||||
{column.sortable !== false && sortColumn === column.columnName && (
|
||||
<span>{sortDirection === "asc" ? "↑" : "↓"}</span>
|
||||
)}
|
||||
@@ -6017,16 +5764,16 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="max-h-48 space-y-1 overflow-y-auto">
|
||||
{columnUniqueValues[column.columnName]?.slice(0, 50).map((item) => {
|
||||
const isSelected = headerFilters[column.columnName]?.has(item.value);
|
||||
{columnUniqueValues[column.columnName]?.slice(0, 50).map((val) => {
|
||||
const isSelected = headerFilters[column.columnName]?.has(val);
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
key={val}
|
||||
className={cn(
|
||||
"hover:bg-muted flex cursor-pointer items-center gap-2 rounded px-2 py-1 text-xs",
|
||||
isSelected && "bg-primary/10",
|
||||
)}
|
||||
onClick={() => toggleHeaderFilter(column.columnName, item.value)}
|
||||
onClick={() => toggleHeaderFilter(column.columnName, val)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -6036,7 +5783,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
>
|
||||
{isSelected && <Check className="text-primary-foreground h-3 w-3" />}
|
||||
</div>
|
||||
<span className="truncate">{item.label || "(빈 값)"}</span>
|
||||
<span className="truncate">{val || "(빈 값)"}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -6209,17 +5956,13 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const isNumeric = inputType === "number" || inputType === "decimal";
|
||||
|
||||
const isFrozen = frozenColumns.includes(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산 (보이는 컬럼 기준으로 계산)
|
||||
const visibleFrozenColumns = visibleColumns
|
||||
.filter(col => frozenColumns.includes(col.columnName))
|
||||
.map(col => col.columnName);
|
||||
const frozenIndex = visibleFrozenColumns.indexOf(column.columnName);
|
||||
const frozenIndex = frozenColumns.indexOf(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산
|
||||
let leftPosition = 0;
|
||||
if (isFrozen && frozenIndex > 0) {
|
||||
for (let i = 0; i < frozenIndex; i++) {
|
||||
const frozenCol = visibleFrozenColumns[i];
|
||||
const frozenCol = frozenColumns[i];
|
||||
// 체크박스 컬럼은 48px 고정
|
||||
const frozenColWidth =
|
||||
frozenCol === "__checkbox__" ? 48 : columnWidths[frozenCol] || 150;
|
||||
@@ -6366,12 +6109,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const isNumeric = inputType === "number" || inputType === "decimal";
|
||||
|
||||
const isFrozen = frozenColumns.includes(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산 (보이는 컬럼 기준으로 계산)
|
||||
const visibleFrozenColumns = visibleColumns
|
||||
.filter(col => frozenColumns.includes(col.columnName))
|
||||
.map(col => col.columnName);
|
||||
const frozenIndex = visibleFrozenColumns.indexOf(column.columnName);
|
||||
const frozenIndex = frozenColumns.indexOf(column.columnName);
|
||||
|
||||
// 셀 포커스 상태
|
||||
const isCellFocused = focusedCell?.rowIndex === index && focusedCell?.colIndex === colIndex;
|
||||
@@ -6385,10 +6123,11 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
// 🆕 검색 하이라이트 여부
|
||||
const isSearchHighlighted = searchHighlights.has(`${index}-${colIndex}`);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산
|
||||
let leftPosition = 0;
|
||||
if (isFrozen && frozenIndex > 0) {
|
||||
for (let i = 0; i < frozenIndex; i++) {
|
||||
const frozenCol = visibleFrozenColumns[i];
|
||||
const frozenCol = frozenColumns[i];
|
||||
// 체크박스 컬럼은 48px 고정
|
||||
const frozenColWidth =
|
||||
frozenCol === "__checkbox__" ? 48 : columnWidths[frozenCol] || 150;
|
||||
@@ -6548,17 +6287,13 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||
const summary = summaryData[column.columnName];
|
||||
const columnWidth = columnWidths[column.columnName];
|
||||
const isFrozen = frozenColumns.includes(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산 (보이는 컬럼 기준으로 계산)
|
||||
const visibleFrozenColumns = visibleColumns
|
||||
.filter(col => frozenColumns.includes(col.columnName))
|
||||
.map(col => col.columnName);
|
||||
const frozenIndex = visibleFrozenColumns.indexOf(column.columnName);
|
||||
const frozenIndex = frozenColumns.indexOf(column.columnName);
|
||||
|
||||
// 틀고정된 컬럼의 left 위치 계산
|
||||
let leftPosition = 0;
|
||||
if (isFrozen && frozenIndex > 0) {
|
||||
for (let i = 0; i < frozenIndex; i++) {
|
||||
const frozenCol = visibleFrozenColumns[i];
|
||||
const frozenCol = frozenColumns[i];
|
||||
// 체크박스 컬럼은 48px 고정
|
||||
const frozenColWidth = frozenCol === "__checkbox__" ? 48 : columnWidths[frozenCol] || 150;
|
||||
leftPosition += frozenColWidth;
|
||||
|
||||
Reference in New Issue
Block a user