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:
kjs
2026-03-12 05:14:27 +09:00
parent fd90e3d761
commit b1e50f2e0a
14 changed files with 462 additions and 142 deletions

View File

@@ -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;