feat: enhance audit logging and add company name to audit entries
- Integrated detailed audit logging for update and delete actions in the CommonCodeController and DDLController. - Added company name retrieval to the audit log entries for better traceability. - Updated the audit log service to include company name in the log entries. - Modified the frontend audit log page to display company names alongside company codes for improved clarity. Made-with: Cursor
This commit is contained in:
@@ -19,10 +19,11 @@ import { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||
interface V2InputConfigPanelProps {
|
||||
config: Record<string, any>;
|
||||
onChange: (config: Record<string, any>) => void;
|
||||
menuObjid?: number; // 메뉴 OBJID (채번 규칙 필터링용)
|
||||
menuObjid?: number;
|
||||
allComponents?: any[];
|
||||
}
|
||||
|
||||
export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config, onChange, menuObjid }) => {
|
||||
export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config, onChange, menuObjid, allComponents = [] }) => {
|
||||
// 채번 규칙 목록 상태
|
||||
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
||||
const [loadingRules, setLoadingRules] = useState(false);
|
||||
@@ -483,73 +484,202 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
|
||||
|
||||
{/* 데이터 바인딩 설정 */}
|
||||
<Separator className="my-2" />
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="dataBindingEnabled"
|
||||
checked={!!config.dataBinding?.sourceComponentId}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
updateConfig("dataBinding", {
|
||||
sourceComponentId: config.dataBinding?.sourceComponentId || "",
|
||||
sourceColumn: config.dataBinding?.sourceColumn || "",
|
||||
});
|
||||
} else {
|
||||
updateConfig("dataBinding", undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="dataBindingEnabled" className="text-xs font-semibold">
|
||||
테이블 선택 데이터 바인딩
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{config.dataBinding && (
|
||||
<div className="space-y-2 rounded border p-2">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
v2-table-list에서 행 선택 시 해당 컬럼 값이 자동으로 채워집니다
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium">소스 컴포넌트 ID</Label>
|
||||
<Input
|
||||
value={config.dataBinding?.sourceComponentId || ""}
|
||||
onChange={(e) => {
|
||||
updateConfig("dataBinding", {
|
||||
...config.dataBinding,
|
||||
sourceComponentId: e.target.value,
|
||||
});
|
||||
}}
|
||||
placeholder="예: tbl_items"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
같은 화면 내 v2-table-list 컴포넌트의 ID
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium">소스 컬럼명</Label>
|
||||
<Input
|
||||
value={config.dataBinding?.sourceColumn || ""}
|
||||
onChange={(e) => {
|
||||
updateConfig("dataBinding", {
|
||||
...config.dataBinding,
|
||||
sourceColumn: e.target.value,
|
||||
});
|
||||
}}
|
||||
placeholder="예: item_number"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
선택된 행에서 가져올 컬럼명
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DataBindingSection config={config} onChange={onChange} allComponents={allComponents} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
V2InputConfigPanel.displayName = "V2InputConfigPanel";
|
||||
|
||||
/**
|
||||
* 데이터 바인딩 설정 섹션
|
||||
* 같은 화면의 v2-table-list 컴포넌트를 자동 감지하여 드롭다운으로 표시
|
||||
*/
|
||||
function DataBindingSection({
|
||||
config,
|
||||
onChange,
|
||||
allComponents,
|
||||
}: {
|
||||
config: Record<string, any>;
|
||||
onChange: (config: Record<string, any>) => void;
|
||||
allComponents: any[];
|
||||
}) {
|
||||
const [tableColumns, setTableColumns] = useState<string[]>([]);
|
||||
const [loadingColumns, setLoadingColumns] = useState(false);
|
||||
|
||||
// 같은 화면의 v2-table-list 컴포넌트만 필터링
|
||||
const tableListComponents = React.useMemo(() => {
|
||||
return allComponents.filter((comp) => {
|
||||
const type =
|
||||
comp.componentType ||
|
||||
comp.widgetType ||
|
||||
comp.componentConfig?.type ||
|
||||
(comp.url && comp.url.split("/").pop());
|
||||
return type === "v2-table-list";
|
||||
});
|
||||
}, [allComponents]);
|
||||
|
||||
// 선택된 테이블 컴포넌트의 테이블명 추출
|
||||
const selectedTableComponent = React.useMemo(() => {
|
||||
if (!config.dataBinding?.sourceComponentId) return null;
|
||||
return tableListComponents.find((comp) => comp.id === config.dataBinding.sourceComponentId);
|
||||
}, [tableListComponents, config.dataBinding?.sourceComponentId]);
|
||||
|
||||
const selectedTableName = React.useMemo(() => {
|
||||
if (!selectedTableComponent) return null;
|
||||
return (
|
||||
selectedTableComponent.componentConfig?.selectedTable ||
|
||||
selectedTableComponent.selectedTable ||
|
||||
null
|
||||
);
|
||||
}, [selectedTableComponent]);
|
||||
|
||||
// 선택된 테이블의 컬럼 목록 로드
|
||||
useEffect(() => {
|
||||
if (!selectedTableName) {
|
||||
setTableColumns([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const loadColumns = async () => {
|
||||
setLoadingColumns(true);
|
||||
try {
|
||||
const { tableTypeApi } = await import("@/lib/api/screen");
|
||||
const response = await tableTypeApi.getTableTypeColumns(selectedTableName);
|
||||
if (response.success && response.data) {
|
||||
const cols = response.data.map((col: any) => col.column_name).filter(Boolean);
|
||||
setTableColumns(cols);
|
||||
}
|
||||
} catch {
|
||||
// 컬럼 정보를 못 가져오면 테이블 컴포넌트의 columns에서 추출
|
||||
const configColumns = selectedTableComponent?.componentConfig?.columns;
|
||||
if (Array.isArray(configColumns)) {
|
||||
setTableColumns(configColumns.map((c: any) => c.columnName).filter(Boolean));
|
||||
}
|
||||
} finally {
|
||||
setLoadingColumns(false);
|
||||
}
|
||||
};
|
||||
loadColumns();
|
||||
}, [selectedTableName, selectedTableComponent]);
|
||||
|
||||
const updateConfig = (field: string, value: any) => {
|
||||
onChange({ ...config, [field]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="dataBindingEnabled"
|
||||
checked={!!config.dataBinding?.sourceComponentId}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
const firstTable = tableListComponents[0];
|
||||
updateConfig("dataBinding", {
|
||||
sourceComponentId: firstTable?.id || "",
|
||||
sourceColumn: "",
|
||||
});
|
||||
} else {
|
||||
updateConfig("dataBinding", undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="dataBindingEnabled" className="text-xs font-semibold">
|
||||
테이블 선택 데이터 바인딩
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{config.dataBinding && (
|
||||
<div className="space-y-2 rounded border p-2">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
테이블에서 행 선택 시 해당 컬럼 값이 자동으로 채워집니다
|
||||
</p>
|
||||
|
||||
{/* 소스 테이블 컴포넌트 선택 */}
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium">소스 테이블</Label>
|
||||
{tableListComponents.length === 0 ? (
|
||||
<p className="text-[10px] text-amber-500">이 화면에 v2-table-list 컴포넌트가 없습니다</p>
|
||||
) : (
|
||||
<Select
|
||||
value={config.dataBinding?.sourceComponentId || ""}
|
||||
onValueChange={(value) => {
|
||||
updateConfig("dataBinding", {
|
||||
...config.dataBinding,
|
||||
sourceComponentId: value,
|
||||
sourceColumn: "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="테이블 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableListComponents.map((comp) => {
|
||||
const tblName =
|
||||
comp.componentConfig?.selectedTable || comp.selectedTable || "";
|
||||
const label = comp.componentConfig?.label || comp.label || comp.id;
|
||||
return (
|
||||
<SelectItem key={comp.id} value={comp.id}>
|
||||
{label} ({tblName || comp.id})
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 소스 컬럼 선택 */}
|
||||
{config.dataBinding?.sourceComponentId && (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium">가져올 컬럼</Label>
|
||||
{loadingColumns ? (
|
||||
<p className="text-[10px] text-muted-foreground">컬럼 로딩 중...</p>
|
||||
) : tableColumns.length === 0 ? (
|
||||
<>
|
||||
<Input
|
||||
value={config.dataBinding?.sourceColumn || ""}
|
||||
onChange={(e) => {
|
||||
updateConfig("dataBinding", {
|
||||
...config.dataBinding,
|
||||
sourceColumn: e.target.value,
|
||||
});
|
||||
}}
|
||||
placeholder="컬럼명 직접 입력"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">컬럼 정보를 불러올 수 없어 직접 입력</p>
|
||||
</>
|
||||
) : (
|
||||
<Select
|
||||
value={config.dataBinding?.sourceColumn || ""}
|
||||
onValueChange={(value) => {
|
||||
updateConfig("dataBinding", {
|
||||
...config.dataBinding,
|
||||
sourceColumn: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-xs">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tableColumns.map((col) => (
|
||||
<SelectItem key={col} value={col}>
|
||||
{col}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default V2InputConfigPanel;
|
||||
|
||||
Reference in New Issue
Block a user