319 lines
9.3 KiB
TypeScript
319 lines
9.3 KiB
TypeScript
|
|
/**
|
||
|
|
* 탑씰(company_7) 버튼 스타일 일괄 변경 스크립트
|
||
|
|
*
|
||
|
|
* 사용법:
|
||
|
|
* npx ts-node scripts/btn-bulk-update-company7.ts --test # 1건만 테스트 (ROLLBACK)
|
||
|
|
* npx ts-node scripts/btn-bulk-update-company7.ts --run # 전체 실행 (COMMIT)
|
||
|
|
* npx ts-node scripts/btn-bulk-update-company7.ts --backup # 백업 테이블만 생성
|
||
|
|
* npx ts-node scripts/btn-bulk-update-company7.ts --restore # 백업에서 원복
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { Pool } from "pg";
|
||
|
|
|
||
|
|
// ── 배포 DB 연결 ──
|
||
|
|
const pool = new Pool({
|
||
|
|
connectionString:
|
||
|
|
"postgresql://postgres:vexplor0909!!@211.115.91.141:11134/vexplor",
|
||
|
|
});
|
||
|
|
|
||
|
|
const COMPANY_CODE = "COMPANY_7";
|
||
|
|
const BACKUP_TABLE = "screen_layouts_v2_backup_20260313";
|
||
|
|
|
||
|
|
// ── 액션별 기본 아이콘 매핑 (frontend/lib/button-icon-map.tsx 기준) ──
|
||
|
|
const actionIconMap: Record<string, string> = {
|
||
|
|
save: "Check",
|
||
|
|
delete: "Trash2",
|
||
|
|
edit: "Pencil",
|
||
|
|
navigate: "ArrowRight",
|
||
|
|
modal: "Maximize2",
|
||
|
|
transferData: "SendHorizontal",
|
||
|
|
excel_download: "Download",
|
||
|
|
excel_upload: "Upload",
|
||
|
|
quickInsert: "Zap",
|
||
|
|
control: "Settings",
|
||
|
|
barcode_scan: "ScanLine",
|
||
|
|
operation_control: "Truck",
|
||
|
|
event: "Send",
|
||
|
|
copy: "Copy",
|
||
|
|
};
|
||
|
|
const FALLBACK_ICON = "SquareMousePointer";
|
||
|
|
|
||
|
|
function getIconForAction(actionType?: string): string {
|
||
|
|
if (actionType && actionIconMap[actionType]) {
|
||
|
|
return actionIconMap[actionType];
|
||
|
|
}
|
||
|
|
return FALLBACK_ICON;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 버튼 컴포넌트인지 판별 (최상위 + 탭 내부 둘 다 지원) ──
|
||
|
|
function isTopLevelButton(comp: any): boolean {
|
||
|
|
return (
|
||
|
|
comp.url?.includes("v2-button-primary") ||
|
||
|
|
comp.overrides?.type === "v2-button-primary"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function isTabChildButton(comp: any): boolean {
|
||
|
|
return comp.componentType === "v2-button-primary";
|
||
|
|
}
|
||
|
|
|
||
|
|
function isButtonComponent(comp: any): boolean {
|
||
|
|
return isTopLevelButton(comp) || isTabChildButton(comp);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 탭 위젯인지 판별 ──
|
||
|
|
function isTabsWidget(comp: any): boolean {
|
||
|
|
return (
|
||
|
|
comp.url?.includes("v2-tabs-widget") ||
|
||
|
|
comp.overrides?.type === "v2-tabs-widget"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 버튼 스타일 변경 (최상위 버튼용: overrides 사용) ──
|
||
|
|
function applyButtonStyle(config: any, actionType: string | undefined) {
|
||
|
|
const iconName = getIconForAction(actionType);
|
||
|
|
|
||
|
|
config.displayMode = "icon-text";
|
||
|
|
|
||
|
|
config.icon = {
|
||
|
|
name: iconName,
|
||
|
|
type: "lucide",
|
||
|
|
size: "보통",
|
||
|
|
...(config.icon?.color ? { color: config.icon.color } : {}),
|
||
|
|
};
|
||
|
|
|
||
|
|
config.iconTextPosition = "right";
|
||
|
|
config.iconGap = 6;
|
||
|
|
|
||
|
|
if (!config.style) config.style = {};
|
||
|
|
delete config.style.width; // 레거시 하드코딩 너비 제거 (size.width만 사용)
|
||
|
|
config.style.borderRadius = "8px";
|
||
|
|
config.style.labelColor = "#FFFFFF";
|
||
|
|
config.style.fontSize = "12px";
|
||
|
|
config.style.fontWeight = "normal";
|
||
|
|
config.style.labelTextAlign = "left";
|
||
|
|
|
||
|
|
if (actionType === "delete") {
|
||
|
|
config.style.backgroundColor = "#F04544";
|
||
|
|
} else if (actionType === "excel_upload" || actionType === "excel_download") {
|
||
|
|
config.style.backgroundColor = "#212121";
|
||
|
|
} else {
|
||
|
|
config.style.backgroundColor = "#3B83F6";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateButtonStyle(comp: any): boolean {
|
||
|
|
if (isTopLevelButton(comp)) {
|
||
|
|
const overrides = comp.overrides || {};
|
||
|
|
const actionType = overrides.action?.type;
|
||
|
|
|
||
|
|
if (!comp.size) comp.size = {};
|
||
|
|
comp.size.height = 40;
|
||
|
|
|
||
|
|
applyButtonStyle(overrides, actionType);
|
||
|
|
comp.overrides = overrides;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isTabChildButton(comp)) {
|
||
|
|
const config = comp.componentConfig || {};
|
||
|
|
const actionType = config.action?.type;
|
||
|
|
|
||
|
|
if (!comp.size) comp.size = {};
|
||
|
|
comp.size.height = 40;
|
||
|
|
|
||
|
|
applyButtonStyle(config, actionType);
|
||
|
|
comp.componentConfig = config;
|
||
|
|
|
||
|
|
// 탭 내부 버튼은 렌더러가 comp.style (최상위)에서 스타일을 읽음
|
||
|
|
if (!comp.style) comp.style = {};
|
||
|
|
comp.style.borderRadius = "8px";
|
||
|
|
comp.style.labelColor = "#FFFFFF";
|
||
|
|
comp.style.fontSize = "12px";
|
||
|
|
comp.style.fontWeight = "normal";
|
||
|
|
comp.style.labelTextAlign = "left";
|
||
|
|
comp.style.backgroundColor = config.style.backgroundColor;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 백업 테이블 생성 ──
|
||
|
|
async function createBackup() {
|
||
|
|
console.log(`\n=== 백업 테이블 생성: ${BACKUP_TABLE} ===`);
|
||
|
|
|
||
|
|
const exists = await pool.query(
|
||
|
|
`SELECT to_regclass($1) AS tbl`,
|
||
|
|
[BACKUP_TABLE],
|
||
|
|
);
|
||
|
|
if (exists.rows[0].tbl) {
|
||
|
|
console.log(`백업 테이블이 이미 존재합니다: ${BACKUP_TABLE}`);
|
||
|
|
const count = await pool.query(`SELECT COUNT(*) FROM ${BACKUP_TABLE}`);
|
||
|
|
console.log(`기존 백업 레코드 수: ${count.rows[0].count}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
await pool.query(
|
||
|
|
`CREATE TABLE ${BACKUP_TABLE} AS
|
||
|
|
SELECT * FROM screen_layouts_v2
|
||
|
|
WHERE company_code = $1`,
|
||
|
|
[COMPANY_CODE],
|
||
|
|
);
|
||
|
|
|
||
|
|
const count = await pool.query(`SELECT COUNT(*) FROM ${BACKUP_TABLE}`);
|
||
|
|
console.log(`백업 완료. 레코드 수: ${count.rows[0].count}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 백업에서 원복 ──
|
||
|
|
async function restoreFromBackup() {
|
||
|
|
console.log(`\n=== 백업에서 원복: ${BACKUP_TABLE} ===`);
|
||
|
|
|
||
|
|
const result = await pool.query(
|
||
|
|
`UPDATE screen_layouts_v2 AS target
|
||
|
|
SET layout_data = backup.layout_data,
|
||
|
|
updated_at = backup.updated_at
|
||
|
|
FROM ${BACKUP_TABLE} AS backup
|
||
|
|
WHERE target.screen_id = backup.screen_id
|
||
|
|
AND target.company_code = backup.company_code
|
||
|
|
AND target.layer_id = backup.layer_id`,
|
||
|
|
);
|
||
|
|
console.log(`원복 완료. 변경된 레코드 수: ${result.rowCount}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 메인: 버튼 일괄 변경 ──
|
||
|
|
async function updateButtons(testMode: boolean) {
|
||
|
|
const modeLabel = testMode ? "테스트 (1건, ROLLBACK)" : "전체 실행 (COMMIT)";
|
||
|
|
console.log(`\n=== 버튼 일괄 변경 시작 [${modeLabel}] ===`);
|
||
|
|
|
||
|
|
// company_7 레코드 조회
|
||
|
|
const rows = await pool.query(
|
||
|
|
`SELECT screen_id, layer_id, company_code, layout_data
|
||
|
|
FROM screen_layouts_v2
|
||
|
|
WHERE company_code = $1
|
||
|
|
ORDER BY screen_id, layer_id`,
|
||
|
|
[COMPANY_CODE],
|
||
|
|
);
|
||
|
|
console.log(`대상 레코드 수: ${rows.rowCount}`);
|
||
|
|
|
||
|
|
if (!rows.rowCount) {
|
||
|
|
console.log("변경할 레코드가 없습니다.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
|
||
|
|
let totalUpdated = 0;
|
||
|
|
let totalButtons = 0;
|
||
|
|
const targetRows = testMode ? [rows.rows[0]] : rows.rows;
|
||
|
|
|
||
|
|
for (const row of targetRows) {
|
||
|
|
const layoutData = row.layout_data;
|
||
|
|
if (!layoutData?.components || !Array.isArray(layoutData.components)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
let buttonsInRow = 0;
|
||
|
|
for (const comp of layoutData.components) {
|
||
|
|
// 최상위 버튼 처리
|
||
|
|
if (updateButtonStyle(comp)) {
|
||
|
|
buttonsInRow++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 탭 위젯 내부 버튼 처리
|
||
|
|
if (isTabsWidget(comp)) {
|
||
|
|
const tabs = comp.overrides?.tabs || [];
|
||
|
|
for (const tab of tabs) {
|
||
|
|
const tabComps = tab.components || [];
|
||
|
|
for (const tabComp of tabComps) {
|
||
|
|
if (updateButtonStyle(tabComp)) {
|
||
|
|
buttonsInRow++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (buttonsInRow > 0) {
|
||
|
|
await client.query(
|
||
|
|
`UPDATE screen_layouts_v2
|
||
|
|
SET layout_data = $1, updated_at = NOW()
|
||
|
|
WHERE screen_id = $2 AND company_code = $3 AND layer_id = $4`,
|
||
|
|
[JSON.stringify(layoutData), row.screen_id, row.company_code, row.layer_id],
|
||
|
|
);
|
||
|
|
totalUpdated++;
|
||
|
|
totalButtons += buttonsInRow;
|
||
|
|
|
||
|
|
console.log(
|
||
|
|
` screen_id=${row.screen_id}, layer_id=${row.layer_id} → 버튼 ${buttonsInRow}개 변경`,
|
||
|
|
);
|
||
|
|
|
||
|
|
// 테스트 모드: 변경 전후 비교를 위해 첫 번째 버튼 출력
|
||
|
|
if (testMode) {
|
||
|
|
const sampleBtn = layoutData.components.find(isButtonComponent);
|
||
|
|
if (sampleBtn) {
|
||
|
|
console.log("\n--- 변경 후 샘플 버튼 ---");
|
||
|
|
console.log(JSON.stringify(sampleBtn, null, 2));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`\n--- 결과 ---`);
|
||
|
|
console.log(`변경된 레코드: ${totalUpdated}개`);
|
||
|
|
console.log(`변경된 버튼: ${totalButtons}개`);
|
||
|
|
|
||
|
|
if (testMode) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
console.log("\n[테스트 모드] ROLLBACK 완료. 실제 DB 변경 없음.");
|
||
|
|
} else {
|
||
|
|
await client.query("COMMIT");
|
||
|
|
console.log("\nCOMMIT 완료.");
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
console.error("\n에러 발생. ROLLBACK 완료.", err);
|
||
|
|
throw err;
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── CLI 진입점 ──
|
||
|
|
async function main() {
|
||
|
|
const arg = process.argv[2];
|
||
|
|
|
||
|
|
if (!arg || !["--test", "--run", "--backup", "--restore"].includes(arg)) {
|
||
|
|
console.log("사용법:");
|
||
|
|
console.log(" --test : 1건 테스트 (ROLLBACK, DB 변경 없음)");
|
||
|
|
console.log(" --run : 전체 실행 (COMMIT)");
|
||
|
|
console.log(" --backup : 백업 테이블 생성");
|
||
|
|
console.log(" --restore : 백업에서 원복");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (arg === "--backup") {
|
||
|
|
await createBackup();
|
||
|
|
} else if (arg === "--restore") {
|
||
|
|
await restoreFromBackup();
|
||
|
|
} else if (arg === "--test") {
|
||
|
|
await createBackup();
|
||
|
|
await updateButtons(true);
|
||
|
|
} else if (arg === "--run") {
|
||
|
|
await createBackup();
|
||
|
|
await updateButtons(false);
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
console.error("스크립트 실행 실패:", err);
|
||
|
|
process.exit(1);
|
||
|
|
} finally {
|
||
|
|
await pool.end();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main();
|