autofill 기능 구현
This commit is contained in:
parent
4dde008c6d
commit
39080dff59
|
|
@ -12,6 +12,7 @@ import {
|
||||||
ColumnListResponse,
|
ColumnListResponse,
|
||||||
ColumnSettingsResponse,
|
ColumnSettingsResponse,
|
||||||
} from "../types/tableManagement";
|
} from "../types/tableManagement";
|
||||||
|
import { query } from "../database/db"; // 🆕 query 함수 import
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 테이블 목록 조회
|
* 테이블 목록 조회
|
||||||
|
|
@ -506,7 +507,91 @@ export async function updateColumnInputType(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 테이블 데이터 조회 (페이징 + 검색)
|
* 단일 레코드 조회 (자동 입력용)
|
||||||
|
*/
|
||||||
|
export async function getTableRecord(
|
||||||
|
req: AuthenticatedRequest,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { tableName } = req.params;
|
||||||
|
const { filterColumn, filterValue, displayColumn } = req.body;
|
||||||
|
|
||||||
|
logger.info(`=== 단일 레코드 조회 시작: ${tableName} ===`);
|
||||||
|
logger.info(`필터: ${filterColumn} = ${filterValue}`);
|
||||||
|
logger.info(`표시 컬럼: ${displayColumn}`);
|
||||||
|
|
||||||
|
if (!tableName || !filterColumn || !filterValue || !displayColumn) {
|
||||||
|
const response: ApiResponse<null> = {
|
||||||
|
success: false,
|
||||||
|
message: "필수 파라미터가 누락되었습니다.",
|
||||||
|
error: {
|
||||||
|
code: "MISSING_PARAMETERS",
|
||||||
|
details:
|
||||||
|
"tableName, filterColumn, filterValue, displayColumn이 필요합니다.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res.status(400).json(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableManagementService = new TableManagementService();
|
||||||
|
|
||||||
|
// 단일 레코드 조회 (WHERE filterColumn = filterValue)
|
||||||
|
const result = await tableManagementService.getTableData(tableName, {
|
||||||
|
page: 1,
|
||||||
|
size: 1,
|
||||||
|
search: {
|
||||||
|
[filterColumn]: filterValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.data || result.data.length === 0) {
|
||||||
|
const response: ApiResponse<null> = {
|
||||||
|
success: false,
|
||||||
|
message: "데이터를 찾을 수 없습니다.",
|
||||||
|
error: {
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
details: `${filterColumn} = ${filterValue}에 해당하는 데이터가 없습니다.`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
res.status(404).json(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = result.data[0];
|
||||||
|
const displayValue = record[displayColumn];
|
||||||
|
|
||||||
|
logger.info(`레코드 조회 완료: ${displayColumn} = ${displayValue}`);
|
||||||
|
|
||||||
|
const response: ApiResponse<{ value: any; record: any }> = {
|
||||||
|
success: true,
|
||||||
|
message: "레코드를 성공적으로 조회했습니다.",
|
||||||
|
data: {
|
||||||
|
value: displayValue,
|
||||||
|
record: record,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("레코드 조회 중 오류 발생:", error);
|
||||||
|
|
||||||
|
const response: ApiResponse<null> = {
|
||||||
|
success: false,
|
||||||
|
message: "레코드 조회 중 오류가 발생했습니다.",
|
||||||
|
error: {
|
||||||
|
code: "RECORD_ERROR",
|
||||||
|
details: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(500).json(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 테이블 데이터 조회 (페이징 + 검색 + 필터링)
|
||||||
*/
|
*/
|
||||||
export async function getTableData(
|
export async function getTableData(
|
||||||
req: AuthenticatedRequest,
|
req: AuthenticatedRequest,
|
||||||
|
|
@ -520,12 +605,14 @@ export async function getTableData(
|
||||||
search = {},
|
search = {},
|
||||||
sortBy,
|
sortBy,
|
||||||
sortOrder = "asc",
|
sortOrder = "asc",
|
||||||
|
autoFilter, // 🆕 자동 필터 설정 추가 (컴포넌트에서 직접 전달)
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`);
|
logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`);
|
||||||
logger.info(`페이징: page=${page}, size=${size}`);
|
logger.info(`페이징: page=${page}, size=${size}`);
|
||||||
logger.info(`검색 조건:`, search);
|
logger.info(`검색 조건:`, search);
|
||||||
logger.info(`정렬: ${sortBy} ${sortOrder}`);
|
logger.info(`정렬: ${sortBy} ${sortOrder}`);
|
||||||
|
logger.info(`자동 필터:`, autoFilter); // 🆕
|
||||||
|
|
||||||
if (!tableName) {
|
if (!tableName) {
|
||||||
const response: ApiResponse<null> = {
|
const response: ApiResponse<null> = {
|
||||||
|
|
@ -542,11 +629,35 @@ export async function getTableData(
|
||||||
|
|
||||||
const tableManagementService = new TableManagementService();
|
const tableManagementService = new TableManagementService();
|
||||||
|
|
||||||
|
// 🆕 현재 사용자 필터 적용
|
||||||
|
let enhancedSearch = { ...search };
|
||||||
|
if (autoFilter?.enabled && req.user) {
|
||||||
|
const filterColumn = autoFilter.filterColumn || "company_code";
|
||||||
|
const userField = autoFilter.userField || "companyCode";
|
||||||
|
const userValue = (req.user as any)[userField];
|
||||||
|
|
||||||
|
if (userValue) {
|
||||||
|
enhancedSearch[filterColumn] = userValue;
|
||||||
|
|
||||||
|
logger.info("🔍 현재 사용자 필터 적용:", {
|
||||||
|
filterColumn,
|
||||||
|
userField,
|
||||||
|
userValue,
|
||||||
|
tableName,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.warn("⚠️ 사용자 정보 필드 값 없음:", {
|
||||||
|
userField,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 데이터 조회
|
// 데이터 조회
|
||||||
const result = await tableManagementService.getTableData(tableName, {
|
const result = await tableManagementService.getTableData(tableName, {
|
||||||
page: parseInt(page),
|
page: parseInt(page),
|
||||||
size: parseInt(size),
|
size: parseInt(size),
|
||||||
search,
|
search: enhancedSearch, // 🆕 필터가 적용된 search 사용
|
||||||
sortBy,
|
sortBy,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
});
|
});
|
||||||
|
|
@ -1216,9 +1327,7 @@ export async function getLogData(
|
||||||
originalId: originalId as string,
|
originalId: originalId as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info(`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`);
|
||||||
`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`
|
|
||||||
);
|
|
||||||
|
|
||||||
const response: ApiResponse<typeof result> = {
|
const response: ApiResponse<typeof result> = {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -1254,7 +1363,9 @@ export async function toggleLogTable(
|
||||||
const { tableName } = req.params;
|
const { tableName } = req.params;
|
||||||
const { isActive } = req.body;
|
const { isActive } = req.body;
|
||||||
|
|
||||||
logger.info(`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`);
|
logger.info(
|
||||||
|
`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`
|
||||||
|
);
|
||||||
|
|
||||||
if (!tableName) {
|
if (!tableName) {
|
||||||
const response: ApiResponse<null> = {
|
const response: ApiResponse<null> = {
|
||||||
|
|
@ -1288,9 +1399,7 @@ export async function toggleLogTable(
|
||||||
isActive === "Y" || isActive === true
|
isActive === "Y" || isActive === true
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(
|
logger.info(`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`);
|
||||||
`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const response: ApiResponse<null> = {
|
const response: ApiResponse<null> = {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
updateColumnInputType,
|
updateColumnInputType,
|
||||||
updateTableLabel,
|
updateTableLabel,
|
||||||
getTableData,
|
getTableData,
|
||||||
|
getTableRecord, // 🆕 단일 레코드 조회
|
||||||
addTableData,
|
addTableData,
|
||||||
editTableData,
|
editTableData,
|
||||||
deleteTableData,
|
deleteTableData,
|
||||||
|
|
@ -134,6 +135,12 @@ router.get("/health", checkDatabaseConnection);
|
||||||
*/
|
*/
|
||||||
router.post("/tables/:tableName/data", getTableData);
|
router.post("/tables/:tableName/data", getTableData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 단일 레코드 조회 (자동 입력용)
|
||||||
|
* POST /api/table-management/tables/:tableName/record
|
||||||
|
*/
|
||||||
|
router.post("/tables/:tableName/record", getTableRecord);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 테이블 데이터 추가
|
* 테이블 데이터 추가
|
||||||
* POST /api/table-management/tables/:tableName/add
|
* POST /api/table-management/tables/:tableName/add
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,57 @@ export default function ScreenViewPage() {
|
||||||
}
|
}
|
||||||
}, [screenId]);
|
}, [screenId]);
|
||||||
|
|
||||||
|
// 🆕 autoFill 자동 입력 초기화
|
||||||
|
useEffect(() => {
|
||||||
|
const initAutoFill = async () => {
|
||||||
|
if (!layout || !layout.components || !user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const comp of layout.components) {
|
||||||
|
// type: "component" 또는 type: "widget" 모두 처리
|
||||||
|
if (comp.type === 'widget' || comp.type === 'component') {
|
||||||
|
const widget = comp as any;
|
||||||
|
const fieldName = widget.columnName || widget.id;
|
||||||
|
|
||||||
|
// autoFill 처리
|
||||||
|
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
|
||||||
|
const autoFillConfig = widget.autoFill || (comp as any).autoFill;
|
||||||
|
const currentValue = formData[fieldName];
|
||||||
|
|
||||||
|
if (currentValue === undefined || currentValue === '') {
|
||||||
|
const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig;
|
||||||
|
|
||||||
|
// 사용자 정보에서 필터 값 가져오기
|
||||||
|
const userValue = user?.[userField as keyof typeof user];
|
||||||
|
|
||||||
|
if (userValue && sourceTable && filterColumn && displayColumn) {
|
||||||
|
try {
|
||||||
|
const { tableTypeApi } = await import("@/lib/api/screen");
|
||||||
|
const result = await tableTypeApi.getTableRecord(
|
||||||
|
sourceTable,
|
||||||
|
filterColumn,
|
||||||
|
userValue,
|
||||||
|
displayColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[fieldName]: result.value,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`autoFill 조회 실패: ${fieldName}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initAutoFill();
|
||||||
|
}, [layout, user]);
|
||||||
|
|
||||||
// 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일) - 모바일에서는 비활성화
|
// 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일) - 모바일에서는 비활성화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 모바일 환경에서는 스케일 조정 비활성화 (반응형만 작동)
|
// 모바일 환경에서는 스케일 조정 비활성화 (반응형만 작동)
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||||
page,
|
page,
|
||||||
size: pageSize,
|
size: pageSize,
|
||||||
search: searchParams,
|
search: searchParams,
|
||||||
|
autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달
|
||||||
});
|
});
|
||||||
|
|
||||||
setData(result.data);
|
setData(result.data);
|
||||||
|
|
@ -576,7 +577,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[component.tableName, pageSize],
|
[component.tableName, pageSize, component.autoFilter], // 🆕 autoFilter 추가
|
||||||
);
|
);
|
||||||
|
|
||||||
// 현재 사용자 정보 로드
|
// 현재 사용자 정보 로드
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import { InteractiveDataTable } from "./InteractiveDataTable";
|
||||||
import { FileUpload } from "./widgets/FileUpload";
|
import { FileUpload } from "./widgets/FileUpload";
|
||||||
import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
|
import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { screenApi } from "@/lib/api/screen";
|
import { screenApi, tableTypeApi } from "@/lib/api/screen";
|
||||||
import { DynamicWebTypeRenderer } from "@/lib/registry/DynamicWebTypeRenderer";
|
import { DynamicWebTypeRenderer } from "@/lib/registry/DynamicWebTypeRenderer";
|
||||||
import { enhancedFormService } from "@/lib/services/enhancedFormService";
|
import { enhancedFormService } from "@/lib/services/enhancedFormService";
|
||||||
import { FormValidationIndicator } from "@/components/common/FormValidationIndicator";
|
import { FormValidationIndicator } from "@/components/common/FormValidationIndicator";
|
||||||
|
|
@ -237,14 +237,46 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
// 자동입력 필드들의 값을 formData에 초기 설정
|
// 자동입력 필드들의 값을 formData에 초기 설정
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// console.log("🚀 자동입력 초기화 useEffect 실행 - allComponents 개수:", allComponents.length);
|
// console.log("🚀 자동입력 초기화 useEffect 실행 - allComponents 개수:", allComponents.length);
|
||||||
const initAutoInputFields = () => {
|
const initAutoInputFields = async () => {
|
||||||
// console.log("🔧 initAutoInputFields 실행 시작");
|
// console.log("🔧 initAutoInputFields 실행 시작");
|
||||||
allComponents.forEach(comp => {
|
for (const comp of allComponents) {
|
||||||
if (comp.type === 'widget') {
|
// 🆕 type: "component" 또는 type: "widget" 모두 처리
|
||||||
|
if (comp.type === 'widget' || comp.type === 'component') {
|
||||||
const widget = comp as WidgetComponent;
|
const widget = comp as WidgetComponent;
|
||||||
const fieldName = widget.columnName || widget.id;
|
const fieldName = widget.columnName || widget.id;
|
||||||
|
|
||||||
// 텍스트 타입 위젯의 자동입력 처리
|
// 🆕 autoFill 처리 (테이블 조회 기반 자동 입력)
|
||||||
|
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
|
||||||
|
const autoFillConfig = widget.autoFill || (comp as any).autoFill;
|
||||||
|
const currentValue = formData[fieldName];
|
||||||
|
if (currentValue === undefined || currentValue === '') {
|
||||||
|
const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig;
|
||||||
|
|
||||||
|
// 사용자 정보에서 필터 값 가져오기
|
||||||
|
const userValue = user?.[userField];
|
||||||
|
|
||||||
|
if (userValue && sourceTable && filterColumn && displayColumn) {
|
||||||
|
try {
|
||||||
|
const result = await tableTypeApi.getTableRecord(
|
||||||
|
sourceTable,
|
||||||
|
filterColumn,
|
||||||
|
userValue,
|
||||||
|
displayColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFormData(fieldName, result.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`autoFill 조회 실패: ${fieldName}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue; // autoFill이 활성화되면 일반 자동입력은 건너뜀
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 widget 타입 전용 로직은 widget인 경우만
|
||||||
|
if (comp.type !== 'widget') continue;
|
||||||
|
|
||||||
|
// 텍스트 타입 위젯의 자동입력 처리 (기존 로직)
|
||||||
if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') &&
|
if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') &&
|
||||||
widget.webTypeConfig) {
|
widget.webTypeConfig) {
|
||||||
const config = widget.webTypeConfig as TextTypeConfig;
|
const config = widget.webTypeConfig as TextTypeConfig;
|
||||||
|
|
@ -278,12 +310,12 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 초기 로드 시 자동입력 필드들 설정
|
// 초기 로드 시 자동입력 필드들 설정
|
||||||
initAutoInputFields();
|
initAutoInputFields();
|
||||||
}, [allComponents, generateAutoValue]); // formData는 의존성에서 제외 (무한 루프 방지)
|
}, [allComponents, generateAutoValue, user]); // formData는 의존성에서 제외 (무한 루프 방지)
|
||||||
|
|
||||||
// 날짜 값 업데이트
|
// 날짜 값 업데이트
|
||||||
const updateDateValue = (fieldName: string, date: Date | undefined) => {
|
const updateDateValue = (fieldName: string, date: Date | undefined) => {
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,21 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
// formData 결정 (외부에서 전달받은 것이 있으면 우선 사용)
|
// formData 결정 (외부에서 전달받은 것이 있으면 우선 사용)
|
||||||
const formData = externalFormData || localFormData;
|
const formData = externalFormData || localFormData;
|
||||||
|
|
||||||
|
// formData 업데이트 함수
|
||||||
|
const updateFormData = useCallback(
|
||||||
|
(fieldName: string, value: any) => {
|
||||||
|
if (onFormDataChange) {
|
||||||
|
onFormDataChange(fieldName, value);
|
||||||
|
} else {
|
||||||
|
setLocalFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[fieldName]: value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onFormDataChange],
|
||||||
|
);
|
||||||
|
|
||||||
// 자동값 생성 함수
|
// 자동값 생성 함수
|
||||||
const generateAutoValue = useCallback(
|
const generateAutoValue = useCallback(
|
||||||
(autoValueType: string): string => {
|
(autoValueType: string): string => {
|
||||||
|
|
@ -105,6 +120,50 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
[userName],
|
[userName],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🆕 autoFill 자동 입력 초기화
|
||||||
|
React.useEffect(() => {
|
||||||
|
const initAutoInputFields = async () => {
|
||||||
|
for (const comp of allComponents) {
|
||||||
|
// type: "component" 또는 type: "widget" 모두 처리
|
||||||
|
if (comp.type === 'widget' || comp.type === 'component') {
|
||||||
|
const widget = comp as any;
|
||||||
|
const fieldName = widget.columnName || widget.id;
|
||||||
|
|
||||||
|
// autoFill 처리 (테이블 조회 기반 자동 입력)
|
||||||
|
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
|
||||||
|
const autoFillConfig = widget.autoFill || (comp as any).autoFill;
|
||||||
|
const currentValue = formData[fieldName];
|
||||||
|
|
||||||
|
if (currentValue === undefined || currentValue === '') {
|
||||||
|
const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig;
|
||||||
|
|
||||||
|
// 사용자 정보에서 필터 값 가져오기
|
||||||
|
const userValue = user?.[userField];
|
||||||
|
|
||||||
|
if (userValue && sourceTable && filterColumn && displayColumn) {
|
||||||
|
try {
|
||||||
|
const { tableTypeApi } = await import("@/lib/api/screen");
|
||||||
|
const result = await tableTypeApi.getTableRecord(
|
||||||
|
sourceTable,
|
||||||
|
filterColumn,
|
||||||
|
userValue,
|
||||||
|
displayColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
updateFormData(fieldName, result.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`autoFill 조회 실패: ${fieldName}`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initAutoInputFields();
|
||||||
|
}, [allComponents, user]);
|
||||||
|
|
||||||
// 팝업 화면 레이아웃 로드
|
// 팝업 화면 레이아웃 로드
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (popupScreen?.screenId) {
|
if (popupScreen?.screenId) {
|
||||||
|
|
|
||||||
|
|
@ -2198,6 +2198,90 @@ const DataTableConfigPanelComponent: React.FC<DataTableConfigPanelProps> = ({
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
|
{/* 🆕 자동 필터 설정 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
<Filter className="h-4 w-4" />
|
||||||
|
현재 사용자 정보로 필터링
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3 rounded-md border border-gray-200 p-3">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="auto-filter-enabled"
|
||||||
|
checked={component.autoFilter?.enabled || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
onUpdateComponent({
|
||||||
|
autoFilter: {
|
||||||
|
enabled: checked as boolean,
|
||||||
|
filterColumn: component.autoFilter?.filterColumn || 'company_code',
|
||||||
|
userField: component.autoFilter?.userField || 'companyCode',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-filter-enabled" className="font-normal">
|
||||||
|
현재 사용자 정보로 자동 필터링
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{component.autoFilter?.enabled && (
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filter-column" className="text-xs">
|
||||||
|
필터링할 테이블 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="filter-column"
|
||||||
|
value={component.autoFilter?.filterColumn || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdateComponent({
|
||||||
|
autoFilter: {
|
||||||
|
...component.autoFilter!,
|
||||||
|
filterColumn: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="company_code"
|
||||||
|
className="text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
예: company_code, dept_code, user_id
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="user-field" className="text-xs">
|
||||||
|
사용자 정보 필드 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={component.autoFilter?.userField || 'companyCode'}
|
||||||
|
onValueChange={(value: any) => {
|
||||||
|
onUpdateComponent({
|
||||||
|
autoFilter: {
|
||||||
|
...component.autoFilter!,
|
||||||
|
userField: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="user-field" className="text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="companyCode">현재 로그인한 사용자 회사 코드</SelectItem>
|
||||||
|
<SelectItem value="userId">현재 로그인한 사용자 ID</SelectItem>
|
||||||
|
<SelectItem value="deptCode">현재 로그인한 사용자 부서 코드</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
로그인한 사용자 정보에서 가져올 필드
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 페이지네이션 설정 */}
|
{/* 페이지네이션 설정 */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="text-sm font-medium">페이지네이션 설정</h4>
|
<h4 className="text-sm font-medium">페이지네이션 설정</h4>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Settings } from "lucide-react";
|
import { Settings, Database } from "lucide-react";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
import { useWebTypes } from "@/hooks/admin/useWebTypes";
|
||||||
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
|
import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
|
||||||
import {
|
import {
|
||||||
|
|
@ -1125,6 +1129,136 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 🆕 테이블 데이터 자동 입력 섹션 (component 타입용) */}
|
||||||
|
<div className="space-y-4 rounded-md border border-gray-200 p-4">
|
||||||
|
<h4 className="flex items-center gap-2 text-sm font-medium">
|
||||||
|
<Database className="h-4 w-4" />
|
||||||
|
테이블 데이터 자동 입력
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="auto-fill-enabled-component"
|
||||||
|
checked={selectedComponent.autoFill?.enabled || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
onUpdateProperty(selectedComponent.id, "autoFill", {
|
||||||
|
enabled: checked as boolean,
|
||||||
|
sourceTable: selectedComponent.autoFill?.sourceTable || "",
|
||||||
|
filterColumn: selectedComponent.autoFill?.filterColumn || "company_code",
|
||||||
|
userField: selectedComponent.autoFill?.userField || "companyCode",
|
||||||
|
displayColumn: selectedComponent.autoFill?.displayColumn || "",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-fill-enabled-component" className="text-xs font-normal">
|
||||||
|
현재 사용자 정보로 테이블 조회하여 자동 입력
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedComponent.autoFill?.enabled && (
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="source-table-component" className="text-xs">
|
||||||
|
조회할 테이블 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedComponent.autoFill?.sourceTable || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
onUpdateProperty(selectedComponent.id, "autoFill", {
|
||||||
|
...selectedComponent.autoFill!,
|
||||||
|
sourceTable: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="source-table-component" className="text-xs">
|
||||||
|
<SelectValue placeholder="테이블 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{tables.map((table) => (
|
||||||
|
<SelectItem key={table.tableName} value={table.tableName}>
|
||||||
|
{table.tableName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-muted-foreground">데이터를 조회할 테이블</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filter-column-autofill-component" className="text-xs">
|
||||||
|
필터링할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="filter-column-autofill-component"
|
||||||
|
value={selectedComponent.autoFill?.filterColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdateProperty(selectedComponent.id, "autoFill", {
|
||||||
|
...selectedComponent.autoFill!,
|
||||||
|
filterColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="company_code"
|
||||||
|
className="text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">예: company_code, dept_code, user_id</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="user-field-autofill-component" className="text-xs">
|
||||||
|
사용자 정보 필드 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedComponent.autoFill?.userField || "companyCode"}
|
||||||
|
onValueChange={(value: any) => {
|
||||||
|
onUpdateProperty(selectedComponent.id, "autoFill", {
|
||||||
|
...selectedComponent.autoFill!,
|
||||||
|
userField: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="user-field-autofill-component" className="text-xs">
|
||||||
|
<SelectValue placeholder="사용자 정보 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="companyCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 회사 코드
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="userId" className="text-xs">
|
||||||
|
현재 로그인한 사용자 ID
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="deptCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 부서 코드
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-muted-foreground">로그인한 사용자의 정보를 필터로 사용</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="display-column-autofill-component" className="text-xs">
|
||||||
|
표시할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="display-column-autofill-component"
|
||||||
|
value={selectedComponent.autoFill?.displayColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdateProperty(selectedComponent.id, "autoFill", {
|
||||||
|
...selectedComponent.autoFill!,
|
||||||
|
displayColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="company_name"
|
||||||
|
className="text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
조회된 레코드에서 표시할 컬럼 (예: company_name)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1202,7 +1336,144 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 상세 설정 영역 */}
|
{/* 상세 설정 영역 */}
|
||||||
<div className="flex-1 overflow-y-auto p-4">{renderWebTypeConfig(widget)}</div>
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
|
<div className="space-y-6">
|
||||||
|
{console.log("🔍 [DetailSettingsPanel] widget 타입:", selectedComponent?.type, "autoFill:", widget.autoFill)}
|
||||||
|
{/* 🆕 자동 입력 섹션 */}
|
||||||
|
<div className="space-y-4 rounded-md border border-red-500 bg-yellow-50 p-4">
|
||||||
|
<h4 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
<Database className="h-4 w-4" />
|
||||||
|
🔥 테이블 데이터 자동 입력 (테스트)
|
||||||
|
</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="auto-fill-enabled"
|
||||||
|
checked={widget.autoFill?.enabled || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
onUpdateProperty(widget.id, "autoFill", {
|
||||||
|
enabled: checked as boolean,
|
||||||
|
sourceTable: widget.autoFill?.sourceTable || '',
|
||||||
|
filterColumn: widget.autoFill?.filterColumn || 'company_code',
|
||||||
|
userField: widget.autoFill?.userField || 'companyCode',
|
||||||
|
displayColumn: widget.autoFill?.displayColumn || '',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-fill-enabled" className="font-normal text-xs">
|
||||||
|
현재 사용자 정보로 테이블 조회하여 자동 입력
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{widget.autoFill?.enabled && (
|
||||||
|
<div className="space-y-3 pt-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="source-table" className="text-xs">
|
||||||
|
조회할 테이블 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={widget.autoFill?.sourceTable || ''}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
onUpdateProperty(widget.id, "autoFill", {
|
||||||
|
...widget.autoFill!,
|
||||||
|
sourceTable: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="source-table" className="text-xs">
|
||||||
|
<SelectValue placeholder="테이블 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{tables.map((table) => (
|
||||||
|
<SelectItem key={table.tableName} value={table.tableName}>
|
||||||
|
{table.tableName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
데이터를 조회할 테이블
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filter-column-autofill" className="text-xs">
|
||||||
|
필터링할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="filter-column-autofill"
|
||||||
|
value={widget.autoFill?.filterColumn || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdateProperty(widget.id, "autoFill", {
|
||||||
|
...widget.autoFill!,
|
||||||
|
filterColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="company_code"
|
||||||
|
className="text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
예: company_code, dept_code, user_id
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="user-field-autofill" className="text-xs">
|
||||||
|
사용자 정보 필드 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={widget.autoFill?.userField || 'companyCode'}
|
||||||
|
onValueChange={(value: any) => {
|
||||||
|
onUpdateProperty(widget.id, "autoFill", {
|
||||||
|
...widget.autoFill!,
|
||||||
|
userField: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="user-field-autofill" className="text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="companyCode">현재 로그인한 사용자 회사 코드</SelectItem>
|
||||||
|
<SelectItem value="userId">현재 로그인한 사용자 ID</SelectItem>
|
||||||
|
<SelectItem value="deptCode">현재 로그인한 사용자 부서 코드</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
로그인한 사용자 정보에서 가져올 필드
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="display-column" className="text-xs">
|
||||||
|
표시할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="display-column"
|
||||||
|
value={widget.autoFill?.displayColumn || ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
onUpdateProperty(widget.id, "autoFill", {
|
||||||
|
...widget.autoFill!,
|
||||||
|
displayColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="company_name"
|
||||||
|
className="text-xs"
|
||||||
|
/>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
Input에 표시할 컬럼명 (예: company_name, dept_name)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 웹타입 설정 */}
|
||||||
|
<Separator />
|
||||||
|
{renderWebTypeConfig(widget)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,9 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
{/* 안내 메시지 */}
|
{/* 안내 메시지 */}
|
||||||
<Separator className="my-4" />
|
<Separator className="my-4" />
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||||
<Settings className="mb-2 h-8 w-8 text-muted-foreground/30" />
|
<Settings className="text-muted-foreground/30 mb-2 h-8 w-8" />
|
||||||
<p className="text-[10px] text-muted-foreground">컴포넌트를 선택하여</p>
|
<p className="text-muted-foreground text-[10px]">컴포넌트를 선택하여</p>
|
||||||
<p className="text-[10px] text-muted-foreground">속성을 편집하세요</p>
|
<p className="text-muted-foreground text-[10px]">속성을 편집하세요</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -412,8 +412,11 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
|
|
||||||
// 상세 설정 탭 (DetailSettingsPanel의 전체 로직 통합)
|
// 상세 설정 탭 (DetailSettingsPanel의 전체 로직 통합)
|
||||||
const renderDetailTab = () => {
|
const renderDetailTab = () => {
|
||||||
|
console.log("🔍 [renderDetailTab] selectedComponent.type:", selectedComponent.type);
|
||||||
|
|
||||||
// 1. DataTable 컴포넌트
|
// 1. DataTable 컴포넌트
|
||||||
if (selectedComponent.type === "datatable") {
|
if (selectedComponent.type === "datatable") {
|
||||||
|
console.log("✅ [renderDetailTab] DataTable 컴포넌트");
|
||||||
return (
|
return (
|
||||||
<DataTableConfigPanel
|
<DataTableConfigPanel
|
||||||
component={selectedComponent as DataTableComponent}
|
component={selectedComponent as DataTableComponent}
|
||||||
|
|
@ -470,6 +473,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
|
|
||||||
// 5. 새로운 컴포넌트 시스템 (type: "component")
|
// 5. 새로운 컴포넌트 시스템 (type: "component")
|
||||||
if (selectedComponent.type === "component") {
|
if (selectedComponent.type === "component") {
|
||||||
|
console.log("✅ [renderDetailTab] Component 타입");
|
||||||
const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type;
|
const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type;
|
||||||
const webType = selectedComponent.componentConfig?.webType;
|
const webType = selectedComponent.componentConfig?.webType;
|
||||||
|
|
||||||
|
|
@ -479,7 +483,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
if (!componentId) {
|
if (!componentId) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-8 text-center">
|
<div className="flex h-full items-center justify-center p-8 text-center">
|
||||||
<p className="text-sm text-muted-foreground">컴포넌트 ID가 설정되지 않았습니다</p>
|
<p className="text-muted-foreground text-sm">컴포넌트 ID가 설정되지 않았습니다</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -511,7 +515,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
<SelectItem key={option.value} value={option.value}>
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{option.label}</div>
|
<div className="font-medium">{option.label}</div>
|
||||||
<div className="text-xs text-muted-foreground">{option.description}</div>
|
<div className="text-muted-foreground text-xs">{option.description}</div>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
|
@ -535,45 +539,154 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 🆕 테이블 데이터 자동 입력 (component 타입용) */}
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Database className="text-primary h-4 w-4" />
|
||||||
|
<h4 className="text-xs font-semibold">테이블 데이터 자동 입력</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 활성화 체크박스 */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="autoFill-enabled-component"
|
||||||
|
checked={selectedComponent.autoFill?.enabled || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...selectedComponent.autoFill,
|
||||||
|
enabled: Boolean(checked),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="autoFill-enabled-component" className="cursor-pointer text-xs">
|
||||||
|
현재 사용자 정보로 테이블 조회하여 자동 입력
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedComponent.autoFill?.enabled && (
|
||||||
|
<>
|
||||||
|
{/* 조회할 테이블 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-sourceTable-component" className="text-xs">
|
||||||
|
조회할 테이블 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedComponent.autoFill?.sourceTable || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...selectedComponent.autoFill,
|
||||||
|
enabled: selectedComponent.autoFill?.enabled || false,
|
||||||
|
sourceTable: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs">
|
||||||
|
<SelectValue placeholder="테이블 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{tables.map((table) => (
|
||||||
|
<SelectItem key={table.tableName} value={table.tableName} className="text-xs">
|
||||||
|
{table.tableName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 필터링할 컬럼 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-filterColumn-component" className="text-xs">
|
||||||
|
필터링할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="autoFill-filterColumn-component"
|
||||||
|
value={selectedComponent.autoFill?.filterColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...selectedComponent.autoFill,
|
||||||
|
enabled: selectedComponent.autoFill?.enabled || false,
|
||||||
|
filterColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="예: company_code"
|
||||||
|
className="h-6 w-full px-2 py-0 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 사용자 정보 필드 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-userField-component" className="text-xs">
|
||||||
|
사용자 정보 필드 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedComponent.autoFill?.userField || ""}
|
||||||
|
onValueChange={(value: "companyCode" | "userId" | "deptCode") => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...selectedComponent.autoFill,
|
||||||
|
enabled: selectedComponent.autoFill?.enabled || false,
|
||||||
|
userField: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs">
|
||||||
|
<SelectValue placeholder="사용자 정보 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="companyCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 회사 코드
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="userId" className="text-xs">
|
||||||
|
현재 로그인한 사용자 ID
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="deptCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 부서 코드
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 표시할 컬럼 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-displayColumn-component" className="text-xs">
|
||||||
|
표시할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="autoFill-displayColumn-component"
|
||||||
|
value={selectedComponent.autoFill?.displayColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...selectedComponent.autoFill,
|
||||||
|
enabled: selectedComponent.autoFill?.enabled || false,
|
||||||
|
displayColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="예: company_name"
|
||||||
|
className="h-6 w-full px-2 py-0 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Widget 컴포넌트
|
// 6. Widget 컴포넌트
|
||||||
if (selectedComponent.type === "widget") {
|
if (selectedComponent.type === "widget") {
|
||||||
|
console.log("✅ [renderDetailTab] Widget 타입");
|
||||||
const widget = selectedComponent as WidgetComponent;
|
const widget = selectedComponent as WidgetComponent;
|
||||||
|
console.log("🔍 [renderDetailTab] widget.widgetType:", widget.widgetType);
|
||||||
|
|
||||||
// Widget에 webType이 있는 경우
|
// 새로운 컴포넌트 시스템 (widgetType이 button, card 등) - 먼저 체크
|
||||||
if (widget.webType) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{/* WebType 선택 */}
|
|
||||||
<div>
|
|
||||||
<Label>입력 타입</Label>
|
|
||||||
<Select value={widget.webType} onValueChange={(value) => handleUpdate("webType", value)}>
|
|
||||||
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs" style={{ fontSize: "12px" }}>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{webTypes.map((wt) => (
|
|
||||||
<SelectItem key={wt.web_type} value={wt.web_type}>
|
|
||||||
{wt.web_type_name_kor || wt.web_type}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 새로운 컴포넌트 시스템 (widgetType이 button, card 등)
|
|
||||||
if (
|
if (
|
||||||
widget.widgetType &&
|
widget.widgetType &&
|
||||||
["button", "card", "dashboard", "stats-card", "progress-bar", "chart", "alert", "badge"].includes(
|
["button", "card", "dashboard", "stats-card", "progress-bar", "chart", "alert", "badge"].includes(
|
||||||
widget.widgetType,
|
widget.widgetType,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
console.log("✅ [renderDetailTab] DynamicComponent 반환 (widgetType)");
|
||||||
return (
|
return (
|
||||||
<DynamicComponentConfigPanel
|
<DynamicComponentConfigPanel
|
||||||
componentId={widget.widgetType}
|
componentId={widget.widgetType}
|
||||||
|
|
@ -589,12 +702,168 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 일반 위젯 (webType 기반)
|
||||||
|
console.log("✅ [renderDetailTab] 일반 위젯 렌더링 시작");
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{console.log("🔍 [UnifiedPropertiesPanel] widget.webType:", widget.webType, "widget:", widget)}
|
||||||
|
{/* WebType 선택 (있는 경우만) */}
|
||||||
|
{widget.webType && (
|
||||||
|
<div>
|
||||||
|
<Label>입력 타입</Label>
|
||||||
|
<Select value={widget.webType} onValueChange={(value) => handleUpdate("webType", value)}>
|
||||||
|
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs" style={{ fontSize: "12px" }}>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{webTypes.map((wt) => (
|
||||||
|
<SelectItem key={wt.web_type} value={wt.web_type}>
|
||||||
|
{wt.web_type_name_kor || wt.web_type}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 🆕 테이블 데이터 자동 입력 (모든 widget 컴포넌트) */}
|
||||||
|
<Separator />
|
||||||
|
<div className="space-y-3 border-4 border-red-500 bg-yellow-100 p-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Database className="text-primary h-4 w-4" />
|
||||||
|
<h4 className="text-xs font-semibold">테이블 데이터 자동 입력</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 활성화 체크박스 */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="autoFill-enabled"
|
||||||
|
checked={widget.autoFill?.enabled || false}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...widget.autoFill,
|
||||||
|
enabled: Boolean(checked),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="autoFill-enabled" className="cursor-pointer text-xs">
|
||||||
|
현재 사용자 정보로 테이블 조회하여 자동 입력
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{widget.autoFill?.enabled && (
|
||||||
|
<>
|
||||||
|
{/* 조회할 테이블 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-sourceTable" className="text-xs">
|
||||||
|
조회할 테이블 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={widget.autoFill?.sourceTable || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...widget.autoFill,
|
||||||
|
enabled: widget.autoFill?.enabled || false,
|
||||||
|
sourceTable: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs">
|
||||||
|
<SelectValue placeholder="테이블 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{tables.map((table) => (
|
||||||
|
<SelectItem key={table.tableName} value={table.tableName} className="text-xs">
|
||||||
|
{table.tableName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 필터링할 컬럼 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-filterColumn" className="text-xs">
|
||||||
|
필터링할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="autoFill-filterColumn"
|
||||||
|
value={widget.autoFill?.filterColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...widget.autoFill,
|
||||||
|
enabled: widget.autoFill?.enabled || false,
|
||||||
|
filterColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="예: company_code"
|
||||||
|
className="h-6 w-full px-2 py-0 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 사용자 정보 필드 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-userField" className="text-xs">
|
||||||
|
사용자 정보 필드 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={widget.autoFill?.userField || ""}
|
||||||
|
onValueChange={(value: "companyCode" | "userId" | "deptCode") => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...widget.autoFill,
|
||||||
|
enabled: widget.autoFill?.enabled || false,
|
||||||
|
userField: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-6 w-full px-2 py-0 text-xs">
|
||||||
|
<SelectValue placeholder="사용자 정보 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="companyCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 회사 코드
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="userId" className="text-xs">
|
||||||
|
현재 로그인한 사용자 ID
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="deptCode" className="text-xs">
|
||||||
|
현재 로그인한 사용자 부서 코드
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 표시할 컬럼 */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="autoFill-displayColumn" className="text-xs">
|
||||||
|
표시할 컬럼 <span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="autoFill-displayColumn"
|
||||||
|
value={widget.autoFill?.displayColumn || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleUpdate("autoFill", {
|
||||||
|
...widget.autoFill,
|
||||||
|
enabled: widget.autoFill?.enabled || false,
|
||||||
|
displayColumn: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="예: company_name"
|
||||||
|
className="h-6 w-full px-2 py-0 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기본 메시지
|
// 기본 메시지
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-8 text-center">
|
<div className="flex h-full items-center justify-center p-8 text-center">
|
||||||
<p className="text-sm text-muted-foreground">이 컴포넌트는 추가 설정이 없습니다</p>
|
<p className="text-muted-foreground text-sm">이 컴포넌트는 추가 설정이 없습니다</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -602,9 +871,9 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col bg-white">
|
<div className="flex h-full flex-col bg-white">
|
||||||
{/* 헤더 - 간소화 */}
|
{/* 헤더 - 간소화 */}
|
||||||
<div className="border-b border-border px-3 py-2">
|
<div className="border-border border-b px-3 py-2">
|
||||||
{selectedComponent.type === "widget" && (
|
{selectedComponent.type === "widget" && (
|
||||||
<div className="truncate text-[10px] text-muted-foreground">
|
<div className="text-muted-foreground truncate text-[10px]">
|
||||||
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
|
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,21 @@ export const tableTypeApi = {
|
||||||
deleteTableData: async (tableName: string, data: Record<string, any>[] | { ids: string[] }): Promise<void> => {
|
deleteTableData: async (tableName: string, data: Record<string, any>[] | { ids: string[] }): Promise<void> => {
|
||||||
await apiClient.delete(`/table-management/tables/${tableName}/delete`, { data });
|
await apiClient.delete(`/table-management/tables/${tableName}/delete`, { data });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 🆕 단일 레코드 조회 (자동 입력용)
|
||||||
|
getTableRecord: async (
|
||||||
|
tableName: string,
|
||||||
|
filterColumn: string,
|
||||||
|
filterValue: any,
|
||||||
|
displayColumn: string,
|
||||||
|
): Promise<{ value: any; record: Record<string, any> }> => {
|
||||||
|
const response = await apiClient.post(`/table-management/tables/${tableName}/record`, {
|
||||||
|
filterColumn,
|
||||||
|
filterValue,
|
||||||
|
displayColumn,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 메뉴-화면 할당 관련 API
|
// 메뉴-화면 할당 관련 API
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,15 @@ export interface WidgetComponent extends BaseComponent {
|
||||||
entityConfig?: EntityTypeConfig;
|
entityConfig?: EntityTypeConfig;
|
||||||
buttonConfig?: ButtonTypeConfig;
|
buttonConfig?: ButtonTypeConfig;
|
||||||
arrayConfig?: ArrayTypeConfig;
|
arrayConfig?: ArrayTypeConfig;
|
||||||
|
|
||||||
|
// 🆕 자동 입력 설정 (테이블 조회 기반)
|
||||||
|
autoFill?: {
|
||||||
|
enabled: boolean; // 자동 입력 활성화
|
||||||
|
sourceTable: string; // 조회할 테이블 (예: company_mng)
|
||||||
|
filterColumn: string; // 필터링할 컬럼 (예: company_code)
|
||||||
|
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보 필드
|
||||||
|
displayColumn: string; // 표시할 컬럼 (예: company_name)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,6 +130,13 @@ export interface DataTableComponent extends BaseComponent {
|
||||||
searchable?: boolean;
|
searchable?: boolean;
|
||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
filters?: DataTableFilter[];
|
filters?: DataTableFilter[];
|
||||||
|
|
||||||
|
// 🆕 현재 사용자 정보로 자동 필터링
|
||||||
|
autoFilter?: {
|
||||||
|
enabled: boolean; // 자동 필터 활성화 여부
|
||||||
|
filterColumn: string; // 필터링할 테이블 컬럼 (예: company_code, dept_code)
|
||||||
|
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보에서 가져올 필드
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue