Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management
This commit is contained in:
@@ -838,18 +838,53 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
// 화면의 기본 테이블/REST API 정보 로드
|
||||
useEffect(() => {
|
||||
const loadScreenDataSource = async () => {
|
||||
console.log("🔍 [ScreenDesigner] 데이터 소스 로드 시작:", {
|
||||
screenId: selectedScreen?.screenId,
|
||||
screenName: selectedScreen?.screenName,
|
||||
dataSourceType: selectedScreen?.dataSourceType,
|
||||
tableName: selectedScreen?.tableName,
|
||||
restApiConnectionId: selectedScreen?.restApiConnectionId,
|
||||
restApiEndpoint: selectedScreen?.restApiEndpoint,
|
||||
restApiJsonPath: selectedScreen?.restApiJsonPath,
|
||||
// 전체 selectedScreen 객체도 출력
|
||||
fullScreen: selectedScreen,
|
||||
});
|
||||
|
||||
// REST API 데이터 소스인 경우
|
||||
if (selectedScreen?.dataSourceType === "restapi" && selectedScreen?.restApiConnectionId) {
|
||||
// 1. dataSourceType이 "restapi"인 경우
|
||||
// 2. tableName이 restapi_ 또는 _restapi_로 시작하는 경우
|
||||
// 3. restApiConnectionId가 있는 경우
|
||||
const isRestApi = selectedScreen?.dataSourceType === "restapi" ||
|
||||
selectedScreen?.tableName?.startsWith("restapi_") ||
|
||||
selectedScreen?.tableName?.startsWith("_restapi_") ||
|
||||
!!selectedScreen?.restApiConnectionId;
|
||||
|
||||
console.log("🔍 [ScreenDesigner] REST API 여부:", { isRestApi });
|
||||
|
||||
if (isRestApi && (selectedScreen?.restApiConnectionId || selectedScreen?.tableName)) {
|
||||
try {
|
||||
// 연결 ID 추출 (restApiConnectionId가 없으면 tableName에서 추출)
|
||||
let connectionId = selectedScreen?.restApiConnectionId;
|
||||
if (!connectionId && selectedScreen?.tableName) {
|
||||
const match = selectedScreen.tableName.match(/restapi_(\d+)/);
|
||||
connectionId = match ? parseInt(match[1]) : undefined;
|
||||
}
|
||||
|
||||
if (!connectionId) {
|
||||
throw new Error("REST API 연결 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
console.log("🌐 [ScreenDesigner] REST API 데이터 로드:", { connectionId });
|
||||
|
||||
const restApiData = await ExternalRestApiConnectionAPI.fetchData(
|
||||
selectedScreen.restApiConnectionId,
|
||||
selectedScreen.restApiEndpoint,
|
||||
selectedScreen.restApiJsonPath || "data",
|
||||
connectionId,
|
||||
selectedScreen?.restApiEndpoint,
|
||||
selectedScreen?.restApiJsonPath || "response", // 기본값을 response로 변경
|
||||
);
|
||||
|
||||
// REST API 응답에서 컬럼 정보 생성
|
||||
const columns: ColumnInfo[] = restApiData.columns.map((col) => ({
|
||||
tableName: `restapi_${selectedScreen.restApiConnectionId}`,
|
||||
tableName: `restapi_${connectionId}`,
|
||||
columnName: col.columnName,
|
||||
columnLabel: col.columnLabel,
|
||||
dataType: col.dataType === "string" ? "varchar" : col.dataType === "number" ? "numeric" : col.dataType,
|
||||
@@ -861,10 +896,17 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
}));
|
||||
|
||||
const tableInfo: TableInfo = {
|
||||
tableName: `restapi_${selectedScreen.restApiConnectionId}`,
|
||||
tableName: `restapi_${connectionId}`,
|
||||
tableLabel: restApiData.connectionInfo.connectionName || "REST API 데이터",
|
||||
columns,
|
||||
};
|
||||
|
||||
console.log("✅ [ScreenDesigner] REST API 컬럼 로드 완료:", {
|
||||
tableName: tableInfo.tableName,
|
||||
tableLabel: tableInfo.tableLabel,
|
||||
columnsCount: columns.length,
|
||||
columns: columns.map(c => c.columnName),
|
||||
});
|
||||
|
||||
setTables([tableInfo]);
|
||||
console.log("REST API 데이터 소스 로드 완료:", {
|
||||
@@ -4256,8 +4298,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
|
||||
{/* 통합 패널 */}
|
||||
{panelStates.unified?.isOpen && (
|
||||
<div className="border-border bg-card flex h-full w-[240px] flex-col border-r shadow-sm">
|
||||
<div className="border-border flex items-center justify-between border-b px-4 py-3">
|
||||
<div className="border-border bg-card flex h-full w-[240px] flex-col border-r shadow-sm overflow-hidden">
|
||||
<div className="border-border flex items-center justify-between border-b px-4 py-3 shrink-0">
|
||||
<h3 className="text-foreground text-sm font-semibold">패널</h3>
|
||||
<button
|
||||
onClick={() => closePanel("unified")}
|
||||
@@ -4266,7 +4308,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<Tabs defaultValue="components" className="flex min-h-0 flex-1 flex-col">
|
||||
<TabsList className="mx-4 mt-2 grid h-8 w-auto grid-cols-2 gap-1">
|
||||
<TabsTrigger value="components" className="text-xs">
|
||||
|
||||
@@ -41,6 +41,7 @@ import { cn } from "@/lib/utils";
|
||||
import { MoreHorizontal, Edit, Trash2, Copy, Eye, Plus, Search, Palette, RotateCcw, Trash, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { ScreenDefinition } from "@/types/screen";
|
||||
import { screenApi } from "@/lib/api/screen";
|
||||
import { ExternalRestApiConnectionAPI, ExternalRestApiConnection } from "@/lib/api/externalRestApiConnection";
|
||||
import CreateScreenModal from "./CreateScreenModal";
|
||||
import CopyScreenModal from "./CopyScreenModal";
|
||||
import dynamic from "next/dynamic";
|
||||
@@ -132,10 +133,18 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
description: "",
|
||||
isActive: "Y",
|
||||
tableName: "",
|
||||
dataSourceType: "database" as "database" | "restapi",
|
||||
restApiConnectionId: null as number | null,
|
||||
restApiEndpoint: "",
|
||||
restApiJsonPath: "data",
|
||||
});
|
||||
const [tables, setTables] = useState<Array<{ tableName: string; tableLabel: string }>>([]);
|
||||
const [loadingTables, setLoadingTables] = useState(false);
|
||||
const [tableComboboxOpen, setTableComboboxOpen] = useState(false);
|
||||
|
||||
// REST API 연결 관련 상태 (편집용)
|
||||
const [editRestApiConnections, setEditRestApiConnections] = useState<ExternalRestApiConnection[]>([]);
|
||||
const [editRestApiComboboxOpen, setEditRestApiComboboxOpen] = useState(false);
|
||||
|
||||
// 미리보기 관련 상태
|
||||
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
|
||||
@@ -272,11 +281,19 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
|
||||
const handleEdit = async (screen: ScreenDefinition) => {
|
||||
setScreenToEdit(screen);
|
||||
|
||||
// 데이터 소스 타입 결정
|
||||
const isRestApi = screen.dataSourceType === "restapi" || screen.tableName?.startsWith("_restapi_");
|
||||
|
||||
setEditFormData({
|
||||
screenName: screen.screenName,
|
||||
description: screen.description || "",
|
||||
isActive: screen.isActive,
|
||||
tableName: screen.tableName || "",
|
||||
dataSourceType: isRestApi ? "restapi" : "database",
|
||||
restApiConnectionId: (screen as any).restApiConnectionId || null,
|
||||
restApiEndpoint: (screen as any).restApiEndpoint || "",
|
||||
restApiJsonPath: (screen as any).restApiJsonPath || "data",
|
||||
});
|
||||
setEditDialogOpen(true);
|
||||
|
||||
@@ -298,14 +315,50 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
} finally {
|
||||
setLoadingTables(false);
|
||||
}
|
||||
|
||||
// REST API 연결 목록 로드
|
||||
try {
|
||||
const connections = await ExternalRestApiConnectionAPI.getConnections({ is_active: "Y" });
|
||||
setEditRestApiConnections(connections);
|
||||
} catch (error) {
|
||||
console.error("REST API 연결 목록 조회 실패:", error);
|
||||
setEditRestApiConnections([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditSave = async () => {
|
||||
if (!screenToEdit) return;
|
||||
|
||||
try {
|
||||
// 데이터 소스 타입에 따라 업데이트 데이터 구성
|
||||
const updateData: any = {
|
||||
screenName: editFormData.screenName,
|
||||
description: editFormData.description,
|
||||
isActive: editFormData.isActive,
|
||||
dataSourceType: editFormData.dataSourceType,
|
||||
};
|
||||
|
||||
if (editFormData.dataSourceType === "database") {
|
||||
updateData.tableName = editFormData.tableName;
|
||||
updateData.restApiConnectionId = null;
|
||||
updateData.restApiEndpoint = null;
|
||||
updateData.restApiJsonPath = null;
|
||||
} else {
|
||||
// REST API
|
||||
updateData.tableName = `_restapi_${editFormData.restApiConnectionId}`;
|
||||
updateData.restApiConnectionId = editFormData.restApiConnectionId;
|
||||
updateData.restApiEndpoint = editFormData.restApiEndpoint;
|
||||
updateData.restApiJsonPath = editFormData.restApiJsonPath || "data";
|
||||
}
|
||||
|
||||
console.log("📤 화면 편집 저장 요청:", {
|
||||
screenId: screenToEdit.screenId,
|
||||
editFormData,
|
||||
updateData,
|
||||
});
|
||||
|
||||
// 화면 정보 업데이트 API 호출
|
||||
await screenApi.updateScreenInfo(screenToEdit.screenId, editFormData);
|
||||
await screenApi.updateScreenInfo(screenToEdit.screenId, updateData);
|
||||
|
||||
// 선택된 테이블의 라벨 찾기
|
||||
const selectedTable = tables.find((t) => t.tableName === editFormData.tableName);
|
||||
@@ -318,10 +371,11 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
? {
|
||||
...s,
|
||||
screenName: editFormData.screenName,
|
||||
tableName: editFormData.tableName,
|
||||
tableName: updateData.tableName,
|
||||
tableLabel: tableLabel,
|
||||
description: editFormData.description,
|
||||
isActive: editFormData.isActive,
|
||||
dataSourceType: editFormData.dataSourceType,
|
||||
}
|
||||
: s,
|
||||
),
|
||||
@@ -1216,65 +1270,184 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
placeholder="화면명을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 데이터 소스 타입 선택 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-tableName">테이블 *</Label>
|
||||
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={tableComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
disabled={loadingTables}
|
||||
>
|
||||
{loadingTables
|
||||
? "로딩 중..."
|
||||
: editFormData.tableName
|
||||
? tables.find((table) => table.tableName === editFormData.tableName)?.tableLabel || editFormData.tableName
|
||||
: "테이블을 선택하세요"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
테이블을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.tableName} ${table.tableLabel}`}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, tableName: table.tableName });
|
||||
setTableComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.tableName === table.tableName ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.tableLabel}</span>
|
||||
<span className="text-[10px] text-gray-500">{table.tableName}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Label>데이터 소스 타입</Label>
|
||||
<Select
|
||||
value={editFormData.dataSourceType}
|
||||
onValueChange={(value: "database" | "restapi") => {
|
||||
setEditFormData({
|
||||
...editFormData,
|
||||
dataSourceType: value,
|
||||
tableName: "",
|
||||
restApiConnectionId: null,
|
||||
restApiEndpoint: "",
|
||||
restApiJsonPath: "data",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="database">데이터베이스</SelectItem>
|
||||
<SelectItem value="restapi">REST API</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 데이터베이스 선택 (database 타입인 경우) */}
|
||||
{editFormData.dataSourceType === "database" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-tableName">테이블 *</Label>
|
||||
<Popover open={tableComboboxOpen} onOpenChange={setTableComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={tableComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
disabled={loadingTables}
|
||||
>
|
||||
{loadingTables
|
||||
? "로딩 중..."
|
||||
: editFormData.tableName
|
||||
? tables.find((table) => table.tableName === editFormData.tableName)?.tableLabel || editFormData.tableName
|
||||
: "테이블을 선택하세요"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
테이블을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{tables.map((table) => (
|
||||
<CommandItem
|
||||
key={table.tableName}
|
||||
value={`${table.tableName} ${table.tableLabel}`}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, tableName: table.tableName });
|
||||
setTableComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.tableName === table.tableName ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{table.tableLabel}</span>
|
||||
<span className="text-[10px] text-gray-500">{table.tableName}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* REST API 선택 (restapi 타입인 경우) */}
|
||||
{editFormData.dataSourceType === "restapi" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>REST API 연결 *</Label>
|
||||
<Popover open={editRestApiComboboxOpen} onOpenChange={setEditRestApiComboboxOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={editRestApiComboboxOpen}
|
||||
className="h-8 w-full justify-between text-xs sm:h-10 sm:text-sm"
|
||||
>
|
||||
{editFormData.restApiConnectionId
|
||||
? editRestApiConnections.find((c) => c.id === editFormData.restApiConnectionId)?.connection_name || "선택된 연결"
|
||||
: "REST API 연결 선택"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0"
|
||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||
align="start"
|
||||
>
|
||||
<Command>
|
||||
<CommandInput placeholder="연결 검색..." className="text-xs sm:text-sm" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="text-xs sm:text-sm">
|
||||
연결을 찾을 수 없습니다.
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{editRestApiConnections.map((conn) => (
|
||||
<CommandItem
|
||||
key={conn.id}
|
||||
value={conn.connection_name}
|
||||
onSelect={() => {
|
||||
setEditFormData({ ...editFormData, restApiConnectionId: conn.id || null });
|
||||
setEditRestApiComboboxOpen(false);
|
||||
}}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
editFormData.restApiConnectionId === conn.id ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{conn.connection_name}</span>
|
||||
<span className="text-[10px] text-gray-500">{conn.base_url}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-restApiEndpoint">API 엔드포인트</Label>
|
||||
<Input
|
||||
id="edit-restApiEndpoint"
|
||||
value={editFormData.restApiEndpoint}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, restApiEndpoint: e.target.value })}
|
||||
placeholder="예: /api/data/list"
|
||||
/>
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
데이터를 조회할 API 엔드포인트 경로입니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-restApiJsonPath">JSON 경로</Label>
|
||||
<Input
|
||||
id="edit-restApiJsonPath"
|
||||
value={editFormData.restApiJsonPath}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, restApiJsonPath: e.target.value })}
|
||||
placeholder="예: data 또는 result.items"
|
||||
/>
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
응답 JSON에서 데이터 배열의 경로입니다 (기본: data)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-description">설명</Label>
|
||||
<Textarea
|
||||
@@ -1305,7 +1478,14 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||
<Button variant="outline" onClick={() => setEditDialogOpen(false)}>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleEditSave} disabled={!editFormData.screenName.trim() || !editFormData.tableName.trim()}>
|
||||
<Button
|
||||
onClick={handleEditSave}
|
||||
disabled={
|
||||
!editFormData.screenName.trim() ||
|
||||
(editFormData.dataSourceType === "database" && !editFormData.tableName.trim()) ||
|
||||
(editFormData.dataSourceType === "restapi" && !editFormData.restApiConnectionId)
|
||||
}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -503,7 +503,7 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
<SelectItem value="excel_upload">엑셀 업로드</SelectItem>
|
||||
<SelectItem value="barcode_scan">바코드 스캔</SelectItem>
|
||||
<SelectItem value="code_merge">코드 병합</SelectItem>
|
||||
<SelectItem value="empty_vehicle">공차등록</SelectItem>
|
||||
{/* <SelectItem value="empty_vehicle">공차등록</SelectItem> */}
|
||||
<SelectItem value="operation_control">운행알림 및 종료</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -1664,190 +1664,12 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 위치정보 가져오기 설정 */}
|
||||
{(component.componentConfig?.action?.type || "save") === "empty_vehicle" && (
|
||||
{/* 공차등록 설정 - 운행알림으로 통합되어 주석 처리 */}
|
||||
{/* {(component.componentConfig?.action?.type || "save") === "empty_vehicle" && (
|
||||
<div className="mt-4 space-y-4 rounded-lg border bg-muted/50 p-4">
|
||||
<h4 className="text-sm font-medium text-foreground">🚛 공차등록 설정</h4>
|
||||
|
||||
{/* 테이블 선택 */}
|
||||
<div>
|
||||
<Label htmlFor="geolocation-table">저장할 테이블</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationTableName || currentTableName || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationTableName", value)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="테이블 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableTables.map((table) => (
|
||||
<SelectItem key={table.name} value={table.name} className="text-xs">
|
||||
{table.label || table.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="geolocation-lat-field">
|
||||
위도 필드 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="geolocation-lat-field"
|
||||
placeholder="latitude"
|
||||
value={config.action?.geolocationLatField || "latitude"}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationLatField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="geolocation-lng-field">
|
||||
경도 필드 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="geolocation-lng-field"
|
||||
placeholder="longitude"
|
||||
value={config.action?.geolocationLngField || "longitude"}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationLngField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="geolocation-accuracy-field">정확도 필드 (선택)</Label>
|
||||
<Input
|
||||
id="geolocation-accuracy-field"
|
||||
placeholder="accuracy"
|
||||
value={config.action?.geolocationAccuracyField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationAccuracyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="geolocation-timestamp-field">타임스탬프 필드 (선택)</Label>
|
||||
<Input
|
||||
id="geolocation-timestamp-field"
|
||||
placeholder="location_time"
|
||||
value={config.action?.geolocationTimestampField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationTimestampField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="geolocation-high-accuracy">고정밀 모드</Label>
|
||||
<p className="text-xs text-muted-foreground">GPS 사용 (배터리 소모 증가)</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="geolocation-high-accuracy"
|
||||
checked={config.action?.geolocationHighAccuracy !== false}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.geolocationHighAccuracy", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 자동 저장 옵션 */}
|
||||
<div className="border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="geolocation-auto-save">DB 자동 저장</Label>
|
||||
<p className="text-xs text-muted-foreground">위치 정보를 바로 DB에 저장</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="geolocation-auto-save"
|
||||
checked={config.action?.geolocationAutoSave === true}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.geolocationAutoSave", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config.action?.geolocationAutoSave && (
|
||||
<div className="mt-3 space-y-3 rounded-md bg-amber-50 p-3 dark:bg-amber-950">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>키 필드 (WHERE 조건)</Label>
|
||||
<Input
|
||||
placeholder="user_id"
|
||||
value={config.action?.geolocationKeyField || "user_id"}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationKeyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>키 값 소스</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationKeySourceField || "__userId__"}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationKeySourceField", value)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="소스 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__userId__" className="text-xs font-medium text-blue-600">
|
||||
🔑 로그인 사용자 ID
|
||||
</SelectItem>
|
||||
<SelectItem value="__companyCode__" className="text-xs font-medium text-blue-600">
|
||||
🏢 회사 코드
|
||||
</SelectItem>
|
||||
<SelectItem value="__userName__" className="text-xs font-medium text-blue-600">
|
||||
👤 사용자 이름
|
||||
</SelectItem>
|
||||
{tableColumns.length > 0 && (
|
||||
<>
|
||||
<SelectItem value="__divider__" disabled className="text-xs text-muted-foreground">
|
||||
── 폼 필드 ──
|
||||
</SelectItem>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col} value={col} className="text-xs">
|
||||
{col}
|
||||
</SelectItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 추가 필드 변경 (status 등) */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>추가 변경 필드 (선택)</Label>
|
||||
<Input
|
||||
placeholder="status"
|
||||
value={config.action?.geolocationExtraField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationExtraField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>변경할 값</Label>
|
||||
<Input
|
||||
placeholder="inactive"
|
||||
value={config.action?.geolocationExtraValue || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationExtraValue", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-amber-700 dark:text-amber-300">
|
||||
위치 정보와 함께 status 같은 필드도 변경할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="rounded-md bg-blue-50 p-3 dark:bg-blue-950">
|
||||
<p className="text-xs text-blue-900 dark:text-blue-100">
|
||||
<strong>참고:</strong> HTTPS 환경에서만 작동합니다.
|
||||
</p>
|
||||
</div>
|
||||
... 공차등록 설정 UI 생략 ...
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* 운행알림 및 종료 설정 */}
|
||||
{(component.componentConfig?.action?.type || "save") === "operation_control" && (
|
||||
|
||||
Reference in New Issue
Block a user