공차등록성공
This commit is contained in:
parent
be2550885a
commit
8d2ec8e737
|
|
@ -22,9 +22,9 @@ router.use(authenticateToken);
|
|||
// 폼 데이터 CRUD
|
||||
router.post("/save", saveFormData); // 기존 버전 (레거시 지원)
|
||||
router.post("/save-enhanced", saveFormDataEnhanced); // 개선된 버전
|
||||
router.put("/update-field", updateFieldValue); // 특정 필드만 업데이트 (다른 테이블 지원) - /:id 보다 먼저 선언!
|
||||
router.put("/:id", updateFormData);
|
||||
router.patch("/:id/partial", updateFormDataPartial); // 부분 업데이트
|
||||
router.put("/update-field", updateFieldValue); // 특정 필드만 업데이트 (다른 테이블 지원)
|
||||
router.delete("/:id", deleteFormData);
|
||||
router.get("/:id", getFormData);
|
||||
|
||||
|
|
|
|||
|
|
@ -1662,12 +1662,47 @@ export class DynamicFormService {
|
|||
companyCode,
|
||||
});
|
||||
|
||||
// 멀티테넌시: company_code 조건 추가 (최고관리자는 제외)
|
||||
let whereClause = `"${keyField}" = $1`;
|
||||
const params: any[] = [keyValue, updateValue, userId];
|
||||
let paramIndex = 4;
|
||||
// 테이블 컬럼 정보 조회 (updated_by, updated_at 존재 여부 확인)
|
||||
const columnQuery = `
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = $1 AND column_name IN ('updated_by', 'updated_at', 'company_code')
|
||||
`;
|
||||
const columnResult = await client.query(columnQuery, [tableName]);
|
||||
const existingColumns = columnResult.rows.map((row: any) => row.column_name);
|
||||
|
||||
const hasUpdatedBy = existingColumns.includes('updated_by');
|
||||
const hasUpdatedAt = existingColumns.includes('updated_at');
|
||||
const hasCompanyCode = existingColumns.includes('company_code');
|
||||
|
||||
if (companyCode && companyCode !== "*") {
|
||||
console.log("🔍 [updateFieldValue] 테이블 컬럼 확인:", {
|
||||
hasUpdatedBy,
|
||||
hasUpdatedAt,
|
||||
hasCompanyCode,
|
||||
});
|
||||
|
||||
// 동적 SET 절 구성
|
||||
let setClause = `"${updateField}" = $1`;
|
||||
const params: any[] = [updateValue];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (hasUpdatedBy) {
|
||||
setClause += `, updated_by = $${paramIndex}`;
|
||||
params.push(userId);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (hasUpdatedAt) {
|
||||
setClause += `, updated_at = NOW()`;
|
||||
}
|
||||
|
||||
// WHERE 절 구성
|
||||
let whereClause = `"${keyField}" = $${paramIndex}`;
|
||||
params.push(keyValue);
|
||||
paramIndex++;
|
||||
|
||||
// 멀티테넌시: company_code 조건 추가 (최고관리자는 제외, 컬럼이 있는 경우만)
|
||||
if (hasCompanyCode && companyCode && companyCode !== "*") {
|
||||
whereClause += ` AND company_code = $${paramIndex}`;
|
||||
params.push(companyCode);
|
||||
paramIndex++;
|
||||
|
|
@ -1675,9 +1710,7 @@ export class DynamicFormService {
|
|||
|
||||
const sqlQuery = `
|
||||
UPDATE "${tableName}"
|
||||
SET "${updateField}" = $2,
|
||||
updated_by = $3,
|
||||
updated_at = NOW()
|
||||
SET ${setClause}
|
||||
WHERE ${whereClause}
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1774,6 +1774,255 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* 첫 번째 추가 테이블 설정 (위치정보와 함께 상태 변경) */}
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="geolocation-update-field">추가 필드 변경 (테이블 1)</Label>
|
||||
<p className="text-xs text-muted-foreground">위치정보와 함께 다른 테이블의 필드 값을 변경합니다</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="geolocation-update-field"
|
||||
checked={config.action?.geolocationUpdateField === true}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.geolocationUpdateField", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config.action?.geolocationUpdateField && (
|
||||
<div className="mt-3 space-y-3 rounded-md bg-amber-50 p-3 dark:bg-amber-950">
|
||||
<div>
|
||||
<Label>대상 테이블</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationExtraTableName || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationExtraTableName", 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-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="예: active"
|
||||
value={config.action?.geolocationExtraValue || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationExtraValue", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>키 필드 (대상 테이블)</Label>
|
||||
<Input
|
||||
placeholder="예: id"
|
||||
value={config.action?.geolocationExtraKeyField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationExtraKeyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>키 값 소스</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationExtraKeySourceField || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationExtraKeySourceField", 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 두 번째 추가 테이블 설정 */}
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="geolocation-second-table">추가 필드 변경 (테이블 2)</Label>
|
||||
<p className="text-xs text-muted-foreground">두 번째 테이블의 필드 값도 함께 변경합니다</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="geolocation-second-table"
|
||||
checked={config.action?.geolocationSecondTableEnabled === true}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.geolocationSecondTableEnabled", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config.action?.geolocationSecondTableEnabled && (
|
||||
<div className="mt-3 space-y-3 rounded-md bg-green-50 p-3 dark:bg-green-950">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>대상 테이블</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationSecondTableName || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationSecondTableName", 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>
|
||||
<Label>작업 모드</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationSecondMode || "update"}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationSecondMode", value)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="update" className="text-xs">UPDATE (기존 레코드 수정)</SelectItem>
|
||||
<SelectItem value="insert" className="text-xs">INSERT (새 레코드 생성)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>{config.action?.geolocationSecondMode === "insert" ? "저장할 필드" : "변경할 필드"}</Label>
|
||||
<Input
|
||||
placeholder="예: status"
|
||||
value={config.action?.geolocationSecondField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationSecondField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>{config.action?.geolocationSecondMode === "insert" ? "저장할 값" : "변경할 값"}</Label>
|
||||
<Input
|
||||
placeholder="예: inactive"
|
||||
value={config.action?.geolocationSecondValue || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationSecondValue", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>
|
||||
{config.action?.geolocationSecondMode === "insert"
|
||||
? "연결 필드 (대상 테이블)"
|
||||
: "키 필드 (대상 테이블)"}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder={config.action?.geolocationSecondMode === "insert" ? "예: vehicle_id" : "예: id"}
|
||||
value={config.action?.geolocationSecondKeyField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.geolocationSecondKeyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>키 값 소스</Label>
|
||||
<Select
|
||||
value={config.action?.geolocationSecondKeySourceField || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.geolocationSecondKeySourceField", 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>
|
||||
|
||||
{config.action?.geolocationSecondMode === "insert" && (
|
||||
<div className="flex items-center justify-between rounded bg-green-100 p-2 dark:bg-green-900">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-xs">위치정보도 함께 저장</Label>
|
||||
<p className="text-[10px] text-muted-foreground">위도/경도를 이 테이블에도 저장</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={config.action?.geolocationSecondInsertFields?.includeLocation === true}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.geolocationSecondInsertFields", {
|
||||
...config.action?.geolocationSecondInsertFields,
|
||||
includeLocation: checked
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-[10px] text-green-700 dark:text-green-300">
|
||||
{config.action?.geolocationSecondMode === "insert"
|
||||
? "새 레코드를 생성합니다. 연결 필드로 현재 폼 데이터와 연결됩니다."
|
||||
: "기존 레코드를 수정합니다. 키 필드로 레코드를 찾아 값을 변경합니다."}
|
||||
</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>
|
||||
|
|
@ -1784,6 +2033,11 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
|||
<br />
|
||||
3. 위도/경도가 지정된 필드에 자동으로 입력됩니다
|
||||
<br />
|
||||
4. 추가 테이블 설정이 있으면 해당 테이블의 필드도 함께 변경됩니다
|
||||
<br />
|
||||
<br />
|
||||
<strong>예시:</strong> 위치정보 저장 + vehicles.status를 inactive로 변경
|
||||
<br />
|
||||
<br />
|
||||
<strong>참고:</strong> HTTPS 환경에서만 위치정보가 작동합니다.
|
||||
</p>
|
||||
|
|
@ -1852,6 +2106,62 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 🆕 키 필드 설정 (레코드 식별용) */}
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<h5 className="mb-3 text-xs font-medium text-muted-foreground">레코드 식별 설정</h5>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="update-key-field">
|
||||
키 필드 (DB 컬럼) <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="update-key-field"
|
||||
placeholder="예: user_id"
|
||||
value={config.action?.updateKeyField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.updateKeyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">레코드를 찾을 DB 컬럼명</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="update-key-source">
|
||||
키 값 소스 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={config.action?.updateKeySourceField || ""}
|
||||
onValueChange={(value) => onUpdateProperty("componentConfig.action.updateKeySourceField", value)}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue placeholder="키 값 소스 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__userId__" className="text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-amber-500">🔑</span> 로그인 사용자 ID
|
||||
</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="__userName__" className="text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-amber-500">🔑</span> 로그인 사용자 이름
|
||||
</span>
|
||||
</SelectItem>
|
||||
<SelectItem value="__companyCode__" className="text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-amber-500">🔑</span> 회사 코드
|
||||
</span>
|
||||
</SelectItem>
|
||||
{tableColumns.map((column) => (
|
||||
<SelectItem key={column} value={column} className="text-xs">
|
||||
{column}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mt-1 text-xs text-muted-foreground">키 값을 가져올 소스</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="update-auto-save">변경 후 자동 저장</Label>
|
||||
|
|
@ -1899,15 +2209,78 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* 위치정보 수집 옵션 */}
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="update-with-geolocation">위치정보도 함께 수집</Label>
|
||||
<p className="text-xs text-muted-foreground">상태 변경과 함께 현재 GPS 좌표를 수집합니다</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="update-with-geolocation"
|
||||
checked={config.action?.updateWithGeolocation === true}
|
||||
onCheckedChange={(checked) => onUpdateProperty("componentConfig.action.updateWithGeolocation", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config.action?.updateWithGeolocation && (
|
||||
<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>위도 저장 필드 <span className="text-destructive">*</span></Label>
|
||||
<Input
|
||||
placeholder="예: latitude"
|
||||
value={config.action?.updateGeolocationLatField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.updateGeolocationLatField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>경도 저장 필드 <span className="text-destructive">*</span></Label>
|
||||
<Input
|
||||
placeholder="예: longitude"
|
||||
value={config.action?.updateGeolocationLngField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.updateGeolocationLngField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label>정확도 필드 (선택)</Label>
|
||||
<Input
|
||||
placeholder="예: accuracy"
|
||||
value={config.action?.updateGeolocationAccuracyField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.updateGeolocationAccuracyField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>타임스탬프 필드 (선택)</Label>
|
||||
<Input
|
||||
placeholder="예: location_time"
|
||||
value={config.action?.updateGeolocationTimestampField || ""}
|
||||
onChange={(e) => onUpdateProperty("componentConfig.action.updateGeolocationTimestampField", e.target.value)}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-amber-700 dark:text-amber-300">
|
||||
버튼 클릭 시 GPS 위치를 수집하여 위 필드에 저장합니다.
|
||||
</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>
|
||||
<br />
|
||||
- 운행알림 버튼: status 필드를 "active"로 변경
|
||||
- 운행알림 버튼: status를 "active"로 + 위치정보 수집
|
||||
<br />
|
||||
- 승인 버튼: approval_status 필드를 "approved"로 변경
|
||||
- 출발 버튼: status를 "inactive"로 + 위치정보 수집
|
||||
<br />
|
||||
- 완료 버튼: is_completed 필드를 "Y"로 변경
|
||||
- 완료 버튼: is_completed를 "Y"로 변경
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export function LocationSwapSelectorConfigPanel({
|
|||
}
|
||||
}, [config?.dataSource?.tableName, config?.dataSource?.type]);
|
||||
|
||||
// 코드 카테고리 로드
|
||||
// 코드 카테고리 로드 (API가 없을 수 있으므로 에러 무시)
|
||||
useEffect(() => {
|
||||
const loadCodeCategories = async () => {
|
||||
try {
|
||||
|
|
@ -103,8 +103,11 @@ export function LocationSwapSelectorConfigPanel({
|
|||
}))
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("코드 카테고리 로드 실패:", error);
|
||||
} catch (error: any) {
|
||||
// 404는 API가 없는 것이므로 무시
|
||||
if (error?.response?.status !== 404) {
|
||||
console.error("코드 카테고리 로드 실패:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
loadCodeCategories();
|
||||
|
|
@ -368,14 +371,14 @@ export function LocationSwapSelectorConfigPanel({
|
|||
<Label>출발지명 저장 컬럼 (선택)</Label>
|
||||
{tableColumns.length > 0 ? (
|
||||
<Select
|
||||
value={config?.departureLabelField || ""}
|
||||
onValueChange={(value) => handleChange("departureLabelField", value)}
|
||||
value={config?.departureLabelField || "__none__"}
|
||||
onValueChange={(value) => handleChange("departureLabelField", value === "__none__" ? "" : value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="컬럼 선택 (선택사항)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">없음</SelectItem>
|
||||
<SelectItem value="__none__">없음</SelectItem>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{col.columnLabel || col.columnName}
|
||||
|
|
@ -395,14 +398,14 @@ export function LocationSwapSelectorConfigPanel({
|
|||
<Label>도착지명 저장 컬럼 (선택)</Label>
|
||||
{tableColumns.length > 0 ? (
|
||||
<Select
|
||||
value={config?.destinationLabelField || ""}
|
||||
onValueChange={(value) => handleChange("destinationLabelField", value)}
|
||||
value={config?.destinationLabelField || "__none__"}
|
||||
onValueChange={(value) => handleChange("destinationLabelField", value === "__none__" ? "" : value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="컬럼 선택 (선택사항)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">없음</SelectItem>
|
||||
<SelectItem value="__none__">없음</SelectItem>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName}>
|
||||
{col.columnLabel || col.columnName}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,16 @@ export interface ButtonActionConfig {
|
|||
geolocationExtraValue?: string | number | boolean; // 추가로 변경할 값 (예: "active")
|
||||
geolocationExtraKeyField?: string; // 다른 테이블의 키 필드 (예: "vehicle_id")
|
||||
geolocationExtraKeySourceField?: string; // 현재 폼에서 키 값을 가져올 필드 (예: "vehicle_id")
|
||||
|
||||
// 🆕 두 번째 테이블 설정 (위치정보 + 상태변경을 각각 다른 테이블에)
|
||||
geolocationSecondTableEnabled?: boolean; // 두 번째 테이블 사용 여부
|
||||
geolocationSecondTableName?: string; // 두 번째 테이블명 (예: "vehicles")
|
||||
geolocationSecondMode?: "update" | "insert"; // 작업 모드 (기본: update)
|
||||
geolocationSecondField?: string; // 두 번째 테이블에서 변경할 필드명 (예: "status")
|
||||
geolocationSecondValue?: string | number | boolean; // 두 번째 테이블에서 변경할 값 (예: "inactive")
|
||||
geolocationSecondKeyField?: string; // 두 번째 테이블의 키 필드 (예: "id") - UPDATE 모드에서만 사용
|
||||
geolocationSecondKeySourceField?: string; // 현재 폼에서 키 값을 가져올 필드 (예: "vehicle_id") - UPDATE 모드에서만 사용
|
||||
geolocationSecondInsertFields?: Record<string, any>; // INSERT 모드에서 추가로 넣을 필드들
|
||||
|
||||
// 필드 값 교환 관련 (출발지 ↔ 목적지)
|
||||
swapFieldA?: string; // 교환할 첫 번째 필드명 (예: "departure")
|
||||
|
|
@ -121,6 +131,13 @@ export interface ButtonActionConfig {
|
|||
updateTargetValue?: string | number | boolean; // 변경할 값 (예: "active")
|
||||
updateAutoSave?: boolean; // 변경 후 자동 저장 여부 (기본: true)
|
||||
updateMultipleFields?: Array<{ field: string; value: string | number | boolean }>; // 여러 필드 동시 변경
|
||||
|
||||
// 🆕 필드 값 변경 + 위치정보 수집 (update_field 액션에서 사용)
|
||||
updateWithGeolocation?: boolean; // 위치정보도 함께 수집할지 여부
|
||||
updateGeolocationLatField?: string; // 위도 저장 필드
|
||||
updateGeolocationLngField?: string; // 경도 저장 필드
|
||||
updateGeolocationAccuracyField?: string; // 정확도 저장 필드 (선택)
|
||||
updateGeolocationTimestampField?: string; // 타임스탬프 저장 필드 (선택)
|
||||
|
||||
// 편집 관련 (수주관리 등 그룹별 다중 레코드 편집)
|
||||
editMode?: "modal" | "navigate" | "inline"; // 편집 모드
|
||||
|
|
@ -217,6 +234,44 @@ export interface ButtonActionContext {
|
|||
componentConfigs?: Record<string, any>; // 컴포넌트 ID → 컴포넌트 설정
|
||||
}
|
||||
|
||||
/**
|
||||
* 🆕 특수 키워드를 실제 값으로 변환하는 헬퍼 함수
|
||||
* 지원하는 키워드:
|
||||
* - __userId__ : 로그인한 사용자 ID
|
||||
* - __userName__ : 로그인한 사용자 이름
|
||||
* - __companyCode__ : 로그인한 사용자의 회사 코드
|
||||
* - __screenId__ : 현재 화면 ID
|
||||
* - __tableName__ : 현재 테이블명
|
||||
*/
|
||||
export function resolveSpecialKeyword(
|
||||
sourceField: string | undefined,
|
||||
context: ButtonActionContext
|
||||
): any {
|
||||
if (!sourceField) return undefined;
|
||||
|
||||
// 특수 키워드 처리
|
||||
switch (sourceField) {
|
||||
case "__userId__":
|
||||
console.log("🔑 특수 키워드 변환: __userId__ →", context.userId);
|
||||
return context.userId;
|
||||
case "__userName__":
|
||||
console.log("🔑 특수 키워드 변환: __userName__ →", context.userName);
|
||||
return context.userName;
|
||||
case "__companyCode__":
|
||||
console.log("🔑 특수 키워드 변환: __companyCode__ →", context.companyCode);
|
||||
return context.companyCode;
|
||||
case "__screenId__":
|
||||
console.log("🔑 특수 키워드 변환: __screenId__ →", context.screenId);
|
||||
return context.screenId;
|
||||
case "__tableName__":
|
||||
console.log("🔑 특수 키워드 변환: __tableName__ →", context.tableName);
|
||||
return context.tableName;
|
||||
default:
|
||||
// 일반 폼 데이터에서 가져오기
|
||||
return context.formData?.[sourceField];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 액션 실행기
|
||||
*/
|
||||
|
|
@ -3236,6 +3291,14 @@ export class ButtonActionExecutor {
|
|||
private static async handleGeolocation(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
|
||||
try {
|
||||
console.log("📍 위치정보 가져오기 액션 실행:", { config, context });
|
||||
console.log("📍 [디버그] 추가 필드 설정값:", {
|
||||
geolocationUpdateField: config.geolocationUpdateField,
|
||||
geolocationExtraField: config.geolocationExtraField,
|
||||
geolocationExtraValue: config.geolocationExtraValue,
|
||||
geolocationExtraTableName: config.geolocationExtraTableName,
|
||||
geolocationExtraKeyField: config.geolocationExtraKeyField,
|
||||
geolocationExtraKeySourceField: config.geolocationExtraKeySourceField,
|
||||
});
|
||||
|
||||
// 브라우저 Geolocation API 지원 확인
|
||||
if (!navigator.geolocation) {
|
||||
|
|
@ -3296,26 +3359,35 @@ export class ButtonActionExecutor {
|
|||
|
||||
// 🆕 추가 필드 변경 (위치정보 + 상태변경)
|
||||
let extraTableUpdated = false;
|
||||
let secondTableUpdated = false;
|
||||
|
||||
if (config.geolocationUpdateField && config.geolocationExtraField && config.geolocationExtraValue !== undefined) {
|
||||
const extraTableName = config.geolocationExtraTableName;
|
||||
const extraTableName = config.geolocationExtraTableName || context.tableName; // 🆕 대상 테이블이 없으면 현재 테이블 사용
|
||||
const currentTableName = config.geolocationTableName || context.tableName;
|
||||
const keySourceField = config.geolocationExtraKeySourceField;
|
||||
|
||||
// 다른 테이블에 UPDATE하는 경우
|
||||
if (extraTableName && extraTableName !== currentTableName) {
|
||||
console.log("📍 다른 테이블 필드 변경:", {
|
||||
// 🆕 특수 키워드가 설정되어 있으면 바로 DB UPDATE (같은 테이블이어도)
|
||||
const hasSpecialKeyword = keySourceField?.startsWith("__") && keySourceField?.endsWith("__");
|
||||
const isDifferentTable = extraTableName && extraTableName !== currentTableName;
|
||||
|
||||
// 다른 테이블이거나 특수 키워드가 설정된 경우 → 바로 DB UPDATE
|
||||
if (isDifferentTable || hasSpecialKeyword) {
|
||||
console.log("📍 DB 직접 UPDATE:", {
|
||||
targetTable: extraTableName,
|
||||
field: config.geolocationExtraField,
|
||||
value: config.geolocationExtraValue,
|
||||
keyField: config.geolocationExtraKeyField,
|
||||
keySourceField: config.geolocationExtraKeySourceField,
|
||||
keySourceField: keySourceField,
|
||||
hasSpecialKeyword,
|
||||
isDifferentTable,
|
||||
});
|
||||
|
||||
// 키 값 가져오기
|
||||
const keyValue = context.formData?.[config.geolocationExtraKeySourceField || ""];
|
||||
// 키 값 가져오기 (특수 키워드 지원)
|
||||
const keyValue = resolveSpecialKeyword(keySourceField, context);
|
||||
|
||||
if (keyValue && config.geolocationExtraKeyField) {
|
||||
try {
|
||||
// 다른 테이블 UPDATE API 호출
|
||||
// DB UPDATE API 호출
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
const response = await apiClient.put(`/dynamic-form/update-field`, {
|
||||
tableName: extraTableName,
|
||||
|
|
@ -3327,30 +3399,131 @@ export class ButtonActionExecutor {
|
|||
|
||||
if (response.data?.success) {
|
||||
extraTableUpdated = true;
|
||||
console.log("✅ 다른 테이블 UPDATE 성공:", response.data);
|
||||
console.log("✅ DB UPDATE 성공:", response.data);
|
||||
} else {
|
||||
console.error("❌ 다른 테이블 UPDATE 실패:", response.data);
|
||||
console.error("❌ DB UPDATE 실패:", response.data);
|
||||
toast.error(`${extraTableName} 테이블 업데이트에 실패했습니다.`);
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error("❌ 다른 테이블 UPDATE API 오류:", apiError);
|
||||
console.error("❌ DB UPDATE API 오류:", apiError);
|
||||
toast.error(`${extraTableName} 테이블 업데이트 중 오류가 발생했습니다.`);
|
||||
}
|
||||
} else {
|
||||
console.warn("⚠️ 키 값이 없어서 다른 테이블 UPDATE를 건너뜁니다:", {
|
||||
keySourceField: config.geolocationExtraKeySourceField,
|
||||
console.warn("⚠️ 키 값이 없어서 DB UPDATE를 건너뜁니다:", {
|
||||
keySourceField: keySourceField,
|
||||
keyValue,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 같은 테이블 (현재 폼 데이터에 추가)
|
||||
// 같은 테이블이고 특수 키워드가 없는 경우 (현재 폼 데이터에 추가)
|
||||
updates[config.geolocationExtraField] = config.geolocationExtraValue;
|
||||
console.log("📍 같은 테이블 추가 필드 변경:", {
|
||||
console.log("📍 같은 테이블 추가 필드 변경 (폼 데이터):", {
|
||||
field: config.geolocationExtraField,
|
||||
value: config.geolocationExtraValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 두 번째 테이블 INSERT 또는 UPDATE
|
||||
if (config.geolocationSecondTableEnabled &&
|
||||
config.geolocationSecondTableName) {
|
||||
|
||||
const secondMode = config.geolocationSecondMode || "update";
|
||||
|
||||
console.log("📍 두 번째 테이블 작업:", {
|
||||
mode: secondMode,
|
||||
targetTable: config.geolocationSecondTableName,
|
||||
field: config.geolocationSecondField,
|
||||
value: config.geolocationSecondValue,
|
||||
keyField: config.geolocationSecondKeyField,
|
||||
keySourceField: config.geolocationSecondKeySourceField,
|
||||
});
|
||||
|
||||
try {
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
|
||||
if (secondMode === "insert") {
|
||||
// INSERT 모드: 새 레코드 생성
|
||||
const insertData: Record<string, any> = {
|
||||
// 위치정보 포함 (선택적)
|
||||
...(config.geolocationSecondInsertFields || {}),
|
||||
};
|
||||
|
||||
// 기본 필드 추가
|
||||
if (config.geolocationSecondField && config.geolocationSecondValue !== undefined) {
|
||||
insertData[config.geolocationSecondField] = config.geolocationSecondValue;
|
||||
}
|
||||
|
||||
// 위치정보도 두 번째 테이블에 저장하려면 추가
|
||||
// (선택적으로 위도/경도도 저장)
|
||||
if (config.geolocationSecondInsertFields?.includeLocation) {
|
||||
insertData[latField] = latitude;
|
||||
insertData[lngField] = longitude;
|
||||
if (config.geolocationAccuracyField) {
|
||||
insertData[config.geolocationAccuracyField] = accuracy;
|
||||
}
|
||||
if (config.geolocationTimestampField) {
|
||||
insertData[config.geolocationTimestampField] = timestamp.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
// 현재 폼에서 키 값 가져와서 연결 (외래키) - 특수 키워드 지원
|
||||
if (config.geolocationSecondKeySourceField && config.geolocationSecondKeyField) {
|
||||
const keyValue = resolveSpecialKeyword(config.geolocationSecondKeySourceField, context);
|
||||
if (keyValue) {
|
||||
insertData[config.geolocationSecondKeyField] = keyValue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("📍 두 번째 테이블 INSERT 데이터:", insertData);
|
||||
|
||||
const response = await apiClient.post(`/dynamic-form/save`, {
|
||||
tableName: config.geolocationSecondTableName,
|
||||
data: insertData,
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
secondTableUpdated = true;
|
||||
console.log("✅ 두 번째 테이블 INSERT 성공:", response.data);
|
||||
} else {
|
||||
console.error("❌ 두 번째 테이블 INSERT 실패:", response.data);
|
||||
toast.error(`${config.geolocationSecondTableName} 테이블 저장에 실패했습니다.`);
|
||||
}
|
||||
} else {
|
||||
// UPDATE 모드: 기존 레코드 수정
|
||||
if (config.geolocationSecondField && config.geolocationSecondValue !== undefined) {
|
||||
// 특수 키워드 지원
|
||||
const secondKeyValue = resolveSpecialKeyword(config.geolocationSecondKeySourceField, context);
|
||||
|
||||
if (secondKeyValue && config.geolocationSecondKeyField) {
|
||||
const response = await apiClient.put(`/dynamic-form/update-field`, {
|
||||
tableName: config.geolocationSecondTableName,
|
||||
keyField: config.geolocationSecondKeyField,
|
||||
keyValue: secondKeyValue,
|
||||
updateField: config.geolocationSecondField,
|
||||
updateValue: config.geolocationSecondValue,
|
||||
});
|
||||
|
||||
if (response.data?.success) {
|
||||
secondTableUpdated = true;
|
||||
console.log("✅ 두 번째 테이블 UPDATE 성공:", response.data);
|
||||
} else {
|
||||
console.error("❌ 두 번째 테이블 UPDATE 실패:", response.data);
|
||||
toast.error(`${config.geolocationSecondTableName} 테이블 업데이트에 실패했습니다.`);
|
||||
}
|
||||
} else {
|
||||
console.warn("⚠️ 두 번째 테이블 키 값이 없어서 UPDATE를 건너뜁니다:", {
|
||||
keySourceField: config.geolocationSecondKeySourceField,
|
||||
keyValue: secondKeyValue,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error("❌ 두 번째 테이블 API 오류:", apiError);
|
||||
toast.error(`${config.geolocationSecondTableName} 테이블 작업 중 오류가 발생했습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
// formData 업데이트
|
||||
if (context.onFormDataChange) {
|
||||
|
|
@ -3371,6 +3544,11 @@ export class ButtonActionExecutor {
|
|||
successMsg += `\n${config.geolocationExtraField}: ${config.geolocationExtraValue}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 두 번째 테이블 변경이 있으면 메시지에 포함
|
||||
if (secondTableUpdated && config.geolocationSecondTableName) {
|
||||
successMsg += `\n[${config.geolocationSecondTableName}] ${config.geolocationSecondField}: ${config.geolocationSecondValue}`;
|
||||
}
|
||||
|
||||
// 성공 메시지 표시
|
||||
toast.success(successMsg);
|
||||
|
|
@ -3470,6 +3648,7 @@ export class ButtonActionExecutor {
|
|||
|
||||
/**
|
||||
* 필드 값 변경 액션 처리 (예: status를 active로 변경)
|
||||
* 🆕 위치정보 수집 기능 추가
|
||||
*/
|
||||
private static async handleUpdateField(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
|
||||
try {
|
||||
|
|
@ -3483,7 +3662,7 @@ export class ButtonActionExecutor {
|
|||
const multipleFields = config.updateMultipleFields || [];
|
||||
|
||||
// 단일 필드 변경이나 다중 필드 변경 중 하나는 있어야 함
|
||||
if (!targetField && multipleFields.length === 0) {
|
||||
if (!targetField && multipleFields.length === 0 && !config.updateWithGeolocation) {
|
||||
toast.error("변경할 필드가 설정되지 않았습니다.");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3510,6 +3689,69 @@ export class ButtonActionExecutor {
|
|||
updates[field] = value;
|
||||
});
|
||||
|
||||
// 🆕 위치정보 수집 (updateWithGeolocation이 true인 경우)
|
||||
if (config.updateWithGeolocation) {
|
||||
const latField = config.updateGeolocationLatField;
|
||||
const lngField = config.updateGeolocationLngField;
|
||||
|
||||
if (!latField || !lngField) {
|
||||
toast.error("위도/경도 저장 필드가 설정되지 않았습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 브라우저 Geolocation API 지원 확인
|
||||
if (!navigator.geolocation) {
|
||||
toast.error("이 브라우저는 위치정보를 지원하지 않습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 로딩 토스트 표시
|
||||
const loadingToastId = toast.loading("위치 정보를 가져오는 중...");
|
||||
|
||||
try {
|
||||
// 위치 정보 가져오기
|
||||
const position = await new Promise<GeolocationPosition>((resolve, reject) => {
|
||||
navigator.geolocation.getCurrentPosition(resolve, reject, {
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 0,
|
||||
});
|
||||
});
|
||||
|
||||
toast.dismiss(loadingToastId);
|
||||
|
||||
const { latitude, longitude, accuracy } = position.coords;
|
||||
const timestamp = new Date(position.timestamp);
|
||||
|
||||
console.log("📍 위치정보 획득:", { latitude, longitude, accuracy });
|
||||
|
||||
// 위치정보를 updates에 추가
|
||||
updates[latField] = latitude;
|
||||
updates[lngField] = longitude;
|
||||
|
||||
if (config.updateGeolocationAccuracyField && accuracy !== null) {
|
||||
updates[config.updateGeolocationAccuracyField] = accuracy;
|
||||
}
|
||||
if (config.updateGeolocationTimestampField) {
|
||||
updates[config.updateGeolocationTimestampField] = timestamp.toISOString();
|
||||
}
|
||||
} catch (geoError: any) {
|
||||
toast.dismiss(loadingToastId);
|
||||
|
||||
// GeolocationPositionError 처리
|
||||
if (geoError.code === 1) {
|
||||
toast.error("위치 정보 접근이 거부되었습니다.");
|
||||
} else if (geoError.code === 2) {
|
||||
toast.error("위치 정보를 사용할 수 없습니다.");
|
||||
} else if (geoError.code === 3) {
|
||||
toast.error("위치 정보 요청 시간이 초과되었습니다.");
|
||||
} else {
|
||||
toast.error("위치 정보를 가져오는 중 오류가 발생했습니다.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🔄 변경할 필드들:", updates);
|
||||
|
||||
// formData 업데이트
|
||||
|
|
@ -3523,6 +3765,67 @@ export class ButtonActionExecutor {
|
|||
const autoSave = config.updateAutoSave !== false;
|
||||
|
||||
if (autoSave) {
|
||||
// 🆕 키 필드 설정이 있는 경우 (특수 키워드 지원) - 직접 DB UPDATE
|
||||
const keyField = config.updateKeyField;
|
||||
const keySourceField = config.updateKeySourceField;
|
||||
const targetTableName = config.updateTableName || tableName;
|
||||
|
||||
if (keyField && keySourceField) {
|
||||
// 특수 키워드 변환 (예: __userId__ → 실제 사용자 ID)
|
||||
const keyValue = resolveSpecialKeyword(keySourceField, context);
|
||||
|
||||
console.log("🔄 필드 값 변경 - 키 필드 사용:", {
|
||||
targetTable: targetTableName,
|
||||
keyField,
|
||||
keySourceField,
|
||||
keyValue,
|
||||
updates,
|
||||
});
|
||||
|
||||
if (!keyValue) {
|
||||
console.warn("⚠️ 키 값이 없어서 업데이트를 건너뜁니다:", { keySourceField, keyValue });
|
||||
toast.error("레코드를 식별할 키 값이 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 각 필드에 대해 개별 UPDATE 호출
|
||||
const { apiClient } = await import("@/lib/api/client");
|
||||
|
||||
for (const [field, value] of Object.entries(updates)) {
|
||||
console.log(`🔄 DB UPDATE: ${targetTableName}.${field} = ${value} WHERE ${keyField} = ${keyValue}`);
|
||||
|
||||
const response = await apiClient.put(`/dynamic-form/update-field`, {
|
||||
tableName: targetTableName,
|
||||
keyField: keyField,
|
||||
keyValue: keyValue,
|
||||
updateField: field,
|
||||
updateValue: value,
|
||||
});
|
||||
|
||||
if (!response.data?.success) {
|
||||
console.error(`❌ ${field} 업데이트 실패:`, response.data);
|
||||
toast.error(`${field} 업데이트에 실패했습니다.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("✅ 모든 필드 업데이트 성공");
|
||||
toast.success(config.successMessage || "상태가 변경되었습니다.");
|
||||
|
||||
// 테이블 새로고침 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent("refreshTableData", {
|
||||
detail: { tableName: targetTableName }
|
||||
}));
|
||||
|
||||
return true;
|
||||
} catch (apiError) {
|
||||
console.error("❌ 필드 값 변경 API 호출 실패:", apiError);
|
||||
toast.error(config.errorMessage || "상태 변경 중 오류가 발생했습니다.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// onSave 콜백이 있으면 사용
|
||||
if (onSave) {
|
||||
console.log("🔄 필드 값 변경 후 자동 저장 (onSave 콜백)");
|
||||
|
|
@ -3537,7 +3840,7 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
// API를 통한 직접 저장
|
||||
// API를 통한 직접 저장 (기존 방식: formData에 PK가 있는 경우)
|
||||
if (tableName && formData) {
|
||||
console.log("🔄 필드 값 변경 후 자동 저장 (API 직접 호출)");
|
||||
try {
|
||||
|
|
@ -3546,7 +3849,7 @@ export class ButtonActionExecutor {
|
|||
const pkValue = formData[pkField] || formData.id;
|
||||
|
||||
if (!pkValue) {
|
||||
toast.error("레코드 ID를 찾을 수 없습니다.");
|
||||
toast.error("레코드 ID를 찾을 수 없습니다. 키 필드를 설정해주세요.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue