wing-ops/scripts/fix-satellite-request.mjs
leedano 38d931db65 refactor(mpa): 탭 디렉토리를 MPA 컴포넌트 구조로 재편
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 17:38:49 +09:00

259 lines
11 KiB
JavaScript

/**
* SatelliteRequest.tsx 디자인 시스템 적용 스크립트
* 실행: node scripts/fix-satellite-request.mjs
*/
import { readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const filePath = resolve(__dirname, '../frontend/src/components/aerial/components/SatelliteRequest.tsx');
let src = readFileSync(filePath, 'utf-8');
// ────────────────────────────────────────────────────
// 작업 1: UI 개선
// ────────────────────────────────────────────────────
// 1-1. stats 배열 color 필드 제거
src = src.replace(
`const stats = [
{ value: '3', label: '요청 대기', color: 'var(--color-info)' },
{ value: '1', label: '촬영 진행 중', color: 'var(--color-caution)' },
{ value: '7', label: '수신 완료', color: 'var(--color-success)' },
{ value: '0.5m', label: '최고 해상도', color: 'var(--color-accent)' },
];`,
`const stats = [
{ value: '3', label: '요청 대기' },
{ value: '1', label: '촬영 진행 중' },
{ value: '7', label: '수신 완료' },
{ value: '0.5m', label: '최고 해상도' },
];`
);
// 1-1. stats 값 div: style color 제거, text-fg 추가
src = src.replace(
`<div className="text-[22px] font-bold font-mono" style={{ color: s.color }}>`,
`<div className="text-[22px] font-bold font-mono text-fg">`
);
// 1-2. 예상수신 컬럼 style 제거
src = src.replace(
` <div
className="text-caption font-semibold font-mono"
style={{
color: r.status === '촬영중' ? 'var(--color-caution)' : 'var(--fg-sub)',
}}
>
{r.expectedReceive}
</div>`,
` <div className="text-caption font-mono text-fg">
{r.expectedReceive}
</div>`
);
// 1-2. 해상도 컬럼 style 제거
src = src.replace(
` <div
className="text-label-2 font-bold font-mono"
style={{
color: r.status === '완료' ? 'var(--fg-disabled)' : 'var(--color-accent)',
}}
>
{r.resolution}
</div>`,
` <div className="text-label-2 font-mono text-fg">
{r.resolution}
</div>`
);
// 1-3. statusBadge 아이콘 제거
src = src.replace(` ⏳ 대기`, ` 대기`);
src = src.replace(` ✕ 취소`, ` 취소`);
src = src.replace(` ✅ 완료`, ` 완료`);
// 1-4. 촬영 가능 시간 스타일 변경
src = src.replace(
` <div className="flex flex-col gap-1.5">
{passSchedules.map((ps, i) => (
<div
key={i}
className="flex items-center gap-2 px-2.5 py-[7px] rounded-[5px]"
style={{
background: ps.today ? 'rgba(34,197,94,.05)' : 'rgba(59,130,246,.05)',
border: ps.today
? '1px solid rgba(34,197,94,.15)'
: '1px solid rgba(59,130,246,.15)',
}}
>
<span
className="text-caption font-bold font-mono min-w-[90px]"
style={{ color: ps.today ? 'var(--color-accent)' : 'var(--color-info)' }}
>
{ps.time}
</span>
<span className="text-caption text-fg font-korean">{ps.desc}</span>
</div>
))}`,
` <div className="flex flex-col gap-1.5">
{passSchedules.map((ps, i) => (
<div
key={i}
className="flex items-center gap-2.5 px-3 py-2 bg-bg-card rounded-md"
style={{ border: '1px solid var(--stroke-default)' }}
>
<span
className={\`text-label-2 font-semibold font-mono min-w-[90px] \${ps.today ? 'text-color-accent' : 'text-fg'}\`}
>
{ps.time}
</span>
<span className="text-caption text-fg-disabled font-korean">{ps.desc}</span>
</div>
))}`
);
// ────────────────────────────────────────────────────
// 작업 2: rgba 비-accent → color-mix 전환
// ────────────────────────────────────────────────────
// 제외 구간 마킹: up42Satellites 배열 (L181-310 범위)은 보호
// 전략: 배열 부분을 임시 플레이스홀더로 치환 → bulk 작업 → 복원
const UP42_PLACEHOLDER = '___UP42_SATELLITES_PLACEHOLDER___';
const up42StartMarker = '// UP42 위성 카탈로그 데이터\nconst up42Satellites = [';
const up42EndMarker = '];\n\n// up42Passes';
const up42Start = src.indexOf(up42StartMarker);
const up42End = src.indexOf(up42EndMarker) + up42EndMarker.length;
if (up42Start === -1 || up42End === -1) {
console.error('up42Satellites 배열을 찾지 못했습니다. 스크립트를 중단합니다.');
process.exit(1);
}
const up42Block = src.slice(up42Start, up42End);
src = src.slice(0, up42Start) + UP42_PLACEHOLDER + src.slice(up42End);
// --- rgba → color-mix 전환 ---
// 매핑 함수
function pct(alpha) {
// alpha: 문자열 (예: ".15", "0.15", ".5", "0.5")
const n = parseFloat(alpha);
return Math.round(n * 100) + '%';
}
// 정규식 패턴: rgba(R,G,B,A) 형태를 매칭 (공백 허용)
// 각 색상별로 처리
// rgba(59,130,246,N) → info
src = src.replace(/rgba\(59,\s*130,\s*246,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--color-info) ${pct(alpha)}, transparent)`;
});
// rgba(99,102,241,N) → info (보라 계열도 info로)
src = src.replace(/rgba\(99,\s*102,\s*241,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--color-info) ${pct(alpha)}, transparent)`;
});
// rgba(34,197,94,N) → success
src = src.replace(/rgba\(34,\s*197,\s*94,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--color-success) ${pct(alpha)}, transparent)`;
});
// rgba(239,68,68,N) → danger
src = src.replace(/rgba\(239,\s*68,\s*68,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--color-danger) ${pct(alpha)}, transparent)`;
});
// rgba(234,179,8,N) → caution
src = src.replace(/rgba\(234,\s*179,\s*8,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--color-caution) ${pct(alpha)}, transparent)`;
});
// rgba(100,116,139,N) → fg-disabled
src = src.replace(/rgba\(100,\s*116,\s*139,\s*([\d.]+)\)/g, (match, alpha) => {
return `color-mix(in srgb, var(--fg-disabled) ${pct(alpha)}, transparent)`;
});
// Tailwind className 내 변환
// border-[rgba(59,130,246,.3)] → border-[rgba(6,182,212,0.3)]
src = src.replace(/border-\[rgba\(59,130,246,\.3\)\]/g, 'border-[rgba(6,182,212,0.3)]');
src = src.replace(/hover:border-\[rgba\(59,130,246,\.5\)\]/g, 'hover:border-[rgba(6,182,212,0.4)]');
src = src.replace(/hover:bg-\[rgba\(59,130,246,\.04\)\]/g, 'hover:bg-[rgba(6,182,212,0.04)]');
// className 내 border-[rgba(99,102,241,...)] 는 그대로 유지 (위성 식별용이 아닌 UI 요소)
// → 이미 위 정규식으로 style 속성 내는 변환됨. className 내의 것은 별도 처리
src = src.replace(/border-\[rgba\(99,102,241,\.3\)\]/g, 'border-[rgba(6,182,212,0.3)]');
src = src.replace(/hover:border-\[rgba\(99,102,241,\.5\)\]/g, 'hover:border-[rgba(6,182,212,0.4)]');
src = src.replace(/hover:bg-\[rgba\(99,102,241,\.04\)\]/g, 'hover:bg-[rgba(6,182,212,0.04)]');
// up42 블록 복원
src = src.replace(UP42_PLACEHOLDER, up42Block);
// ────────────────────────────────────────────────────
// 작업 3: 나머지 하드코딩 색상
// ────────────────────────────────────────────────────
// urgency '예정' color: '#06b6d4' → 'var(--color-accent)'
// L2122 근처: urgency === '예정' ? '#06b6d4' 패턴
src = src.replace(
` ? '#06b6d4'`,
` ? 'var(--color-accent)'`
);
// UP42 제출 버튼 전체 변경 (color '#fff' → 'var(--color-accent)', boxShadow 제거)
src = src.replace(
` <button
onClick={() => setModalPhase('none')}
className="px-6 py-2 rounded-lg text-label-2 font-bold cursor-pointer font-korean text-color-accent transition-opacity"
style={{
background: up42SelSat ? 'rgba(6,182,212,0.08)' : 'var(--stroke-light)',
opacity: up42SelSat ? 1 : 0.5,
color: up42SelSat ? '#fff' : 'var(--fg-disabled)',
boxShadow: up42SelSat ? '0 4px 16px rgba(59,130,246,.35)' : 'none',
}}
>`,
` <button
onClick={() => setModalPhase('none')}
className="px-6 py-2 rounded-lg text-label-2 font-bold cursor-pointer font-korean text-color-accent transition-opacity"
style={{
background: up42SelSat ? 'rgba(6,182,212,0.08)' : 'var(--stroke-light)',
opacity: up42SelSat ? 1 : 0.5,
color: up42SelSat ? 'var(--color-accent)' : 'var(--fg-disabled)',
}}
>`
);
// BlackSky 제출 버튼 boxShadow 제거
src = src.replace(
` style={{
background: 'rgba(6,182,212,0.08)',
boxShadow: '0 4px 16px color-mix(in srgb, var(--color-info) 35%, transparent)',
}}`,
` style={{
background: 'rgba(6,182,212,0.08)',
}}`
);
// text-blue-500 → text-color-info (2곳: L1908, L2129)
src = src.replace(/className="text-label-1 text-blue-500"/g, 'className="text-label-1 text-color-info"');
src = src.replace(/className="text-xs text-blue-500"/g, 'className="text-xs text-color-info"');
// text-green-500 → text-color-success (L1243, L1306, L1468)
src = src.replace(/className="([^"]*)\btext-green-500\b([^"]*)"/g, (match, before, after) => {
return `className="${before}text-color-success${after}"`;
});
// text-red-400 → text-color-danger
src = src.replace(/className="([^"]*)\btext-red-400\b([^"]*)"/g, (match, before, after) => {
return `className="${before}text-color-danger${after}"`;
});
// ────────────────────────────────────────────────────
// 결과 저장
// ────────────────────────────────────────────────────
writeFileSync(filePath, src, 'utf-8');
console.log('✅ SatelliteRequest.tsx 변환 완료');