autofill 기능 구현
This commit is contained in:
parent
4dde008c6d
commit
39080dff59
|
|
@ -12,6 +12,7 @@ import {
|
|||
ColumnListResponse,
|
||||
ColumnSettingsResponse,
|
||||
} 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(
|
||||
req: AuthenticatedRequest,
|
||||
|
|
@ -520,12 +605,14 @@ export async function getTableData(
|
|||
search = {},
|
||||
sortBy,
|
||||
sortOrder = "asc",
|
||||
autoFilter, // 🆕 자동 필터 설정 추가 (컴포넌트에서 직접 전달)
|
||||
} = req.body;
|
||||
|
||||
logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`);
|
||||
logger.info(`페이징: page=${page}, size=${size}`);
|
||||
logger.info(`검색 조건:`, search);
|
||||
logger.info(`정렬: ${sortBy} ${sortOrder}`);
|
||||
logger.info(`자동 필터:`, autoFilter); // 🆕
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
|
|
@ -542,11 +629,35 @@ export async function getTableData(
|
|||
|
||||
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, {
|
||||
page: parseInt(page),
|
||||
size: parseInt(size),
|
||||
search,
|
||||
search: enhancedSearch, // 🆕 필터가 적용된 search 사용
|
||||
sortBy,
|
||||
sortOrder,
|
||||
});
|
||||
|
|
@ -1216,9 +1327,7 @@ export async function getLogData(
|
|||
originalId: originalId as string,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`
|
||||
);
|
||||
logger.info(`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`);
|
||||
|
||||
const response: ApiResponse<typeof result> = {
|
||||
success: true,
|
||||
|
|
@ -1254,7 +1363,9 @@ export async function toggleLogTable(
|
|||
const { tableName } = req.params;
|
||||
const { isActive } = req.body;
|
||||
|
||||
logger.info(`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`);
|
||||
logger.info(
|
||||
`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`
|
||||
);
|
||||
|
||||
if (!tableName) {
|
||||
const response: ApiResponse<null> = {
|
||||
|
|
@ -1288,9 +1399,7 @@ export async function toggleLogTable(
|
|||
isActive === "Y" || isActive === true
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`
|
||||
);
|
||||
logger.info(`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`);
|
||||
|
||||
const response: ApiResponse<null> = {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
updateColumnInputType,
|
||||
updateTableLabel,
|
||||
getTableData,
|
||||
getTableRecord, // 🆕 단일 레코드 조회
|
||||
addTableData,
|
||||
editTableData,
|
||||
deleteTableData,
|
||||
|
|
@ -134,6 +135,12 @@ router.get("/health", checkDatabaseConnection);
|
|||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -147,6 +147,57 @@ export default function ScreenViewPage() {
|
|||
}
|
||||
}, [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(() => {
|
||||
// 모바일 환경에서는 스케일 조정 비활성화 (반응형만 작동)
|
||||
|
|
|
|||
|
|
@ -485,6 +485,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
page,
|
||||
size: pageSize,
|
||||
search: searchParams,
|
||||
autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달
|
||||
});
|
||||
|
||||
setData(result.data);
|
||||
|
|
@ -576,7 +577,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
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 { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm";
|
||||
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 { enhancedFormService } from "@/lib/services/enhancedFormService";
|
||||
import { FormValidationIndicator } from "@/components/common/FormValidationIndicator";
|
||||
|
|
@ -237,14 +237,46 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
// 자동입력 필드들의 값을 formData에 초기 설정
|
||||
React.useEffect(() => {
|
||||
// console.log("🚀 자동입력 초기화 useEffect 실행 - allComponents 개수:", allComponents.length);
|
||||
const initAutoInputFields = () => {
|
||||
const initAutoInputFields = async () => {
|
||||
// console.log("🔧 initAutoInputFields 실행 시작");
|
||||
allComponents.forEach(comp => {
|
||||
if (comp.type === 'widget') {
|
||||
for (const comp of allComponents) {
|
||||
// 🆕 type: "component" 또는 type: "widget" 모두 처리
|
||||
if (comp.type === 'widget' || comp.type === 'component') {
|
||||
const widget = comp as WidgetComponent;
|
||||
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') &&
|
||||
widget.webTypeConfig) {
|
||||
const config = widget.webTypeConfig as TextTypeConfig;
|
||||
|
|
@ -278,12 +310,12 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 초기 로드 시 자동입력 필드들 설정
|
||||
initAutoInputFields();
|
||||
}, [allComponents, generateAutoValue]); // formData는 의존성에서 제외 (무한 루프 방지)
|
||||
}, [allComponents, generateAutoValue, user]); // formData는 의존성에서 제외 (무한 루프 방지)
|
||||
|
||||
// 날짜 값 업데이트
|
||||
const updateDateValue = (fieldName: string, date: Date | undefined) => {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,21 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
// formData 결정 (외부에서 전달받은 것이 있으면 우선 사용)
|
||||
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(
|
||||
(autoValueType: string): string => {
|
||||
|
|
@ -105,6 +120,50 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
[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(() => {
|
||||
if (popupScreen?.screenId) {
|
||||
|
|
|
|||
|
|
@ -2198,6 +2198,90 @@ const DataTableConfigPanelComponent: React.FC<DataTableConfigPanelProps> = ({
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<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">
|
||||
<h4 className="text-sm font-medium">페이지네이션 설정</h4>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
"use client";
|
||||
|
||||
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 { 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 { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent";
|
||||
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>
|
||||
|
|
@ -1202,7 +1336,144 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
|||
</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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -118,9 +118,9 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
{/* 안내 메시지 */}
|
||||
<Separator className="my-4" />
|
||||
<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" />
|
||||
<p className="text-[10px] text-muted-foreground">컴포넌트를 선택하여</p>
|
||||
<p className="text-[10px] text-muted-foreground">속성을 편집하세요</p>
|
||||
<Settings className="text-muted-foreground/30 mb-2 h-8 w-8" />
|
||||
<p className="text-muted-foreground text-[10px]">컴포넌트를 선택하여</p>
|
||||
<p className="text-muted-foreground text-[10px]">속성을 편집하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -412,8 +412,11 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
|
||||
// 상세 설정 탭 (DetailSettingsPanel의 전체 로직 통합)
|
||||
const renderDetailTab = () => {
|
||||
console.log("🔍 [renderDetailTab] selectedComponent.type:", selectedComponent.type);
|
||||
|
||||
// 1. DataTable 컴포넌트
|
||||
if (selectedComponent.type === "datatable") {
|
||||
console.log("✅ [renderDetailTab] DataTable 컴포넌트");
|
||||
return (
|
||||
<DataTableConfigPanel
|
||||
component={selectedComponent as DataTableComponent}
|
||||
|
|
@ -470,6 +473,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
|
||||
// 5. 새로운 컴포넌트 시스템 (type: "component")
|
||||
if (selectedComponent.type === "component") {
|
||||
console.log("✅ [renderDetailTab] Component 타입");
|
||||
const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type;
|
||||
const webType = selectedComponent.componentConfig?.webType;
|
||||
|
||||
|
|
@ -479,7 +483,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
if (!componentId) {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
@ -511,7 +515,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
<SelectItem key={option.value} value={option.value}>
|
||||
<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>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
// 6. Widget 컴포넌트
|
||||
if (selectedComponent.type === "widget") {
|
||||
console.log("✅ [renderDetailTab] Widget 타입");
|
||||
const widget = selectedComponent as WidgetComponent;
|
||||
console.log("🔍 [renderDetailTab] widget.widgetType:", widget.widgetType);
|
||||
|
||||
// Widget에 webType이 있는 경우
|
||||
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 등)
|
||||
// 새로운 컴포넌트 시스템 (widgetType이 button, card 등) - 먼저 체크
|
||||
if (
|
||||
widget.widgetType &&
|
||||
["button", "card", "dashboard", "stats-card", "progress-bar", "chart", "alert", "badge"].includes(
|
||||
widget.widgetType,
|
||||
)
|
||||
) {
|
||||
console.log("✅ [renderDetailTab] DynamicComponent 반환 (widgetType)");
|
||||
return (
|
||||
<DynamicComponentConfigPanel
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
|
@ -602,9 +871,9 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
|||
return (
|
||||
<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" && (
|
||||
<div className="truncate text-[10px] text-muted-foreground">
|
||||
<div className="text-muted-foreground truncate text-[10px]">
|
||||
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -313,6 +313,21 @@ export const tableTypeApi = {
|
|||
deleteTableData: async (tableName: string, data: Record<string, any>[] | { ids: string[] }): Promise<void> => {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -84,6 +84,15 @@ export interface WidgetComponent extends BaseComponent {
|
|||
entityConfig?: EntityTypeConfig;
|
||||
buttonConfig?: ButtonTypeConfig;
|
||||
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;
|
||||
sortable?: boolean;
|
||||
filters?: DataTableFilter[];
|
||||
|
||||
// 🆕 현재 사용자 정보로 자동 필터링
|
||||
autoFilter?: {
|
||||
enabled: boolean; // 자동 필터 활성화 여부
|
||||
filterColumn: string; // 필터링할 테이블 컬럼 (예: company_code, dept_code)
|
||||
userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보에서 가져올 필드
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue